2010-08-04 2 views
6

Я использую пространство имен System.ComponentModel.DataAnnotations для проверки моих классов домена. Как создать настраиваемый атрибут для проверки уникальности свойства независимо от базы данных (например, через какой-либо интерфейс)?Уникальное ограничение с аннотацией данных

ответ

1

Если я правильно понимаю вас, вы должны иметь возможность создать настраиваемый атрибут ValidationAttribute и получить контекст в своем репозитории с помощью настраиваемой фабрики.

Validator:

using System.ComponentModel.DataAnnotations; 
public class DBUniqueAttribute : ValidationAttribute 
{ 
    private IRepository Repository{ get; set;} 
    public DBUniqueAttribute() 
    { 
     this.Repository = MyRepositoryFactory.Create(); 
    } 

    public override bool IsValid(object value) 
    { 
     string stringValue = Convert.ToString(value, CultureInfo.CurrentCulture); 
     return Repository.IsUnique(stringValue); 
    } 
} 

Вы бы иметь IRepository интерфейс с методом IsUnique(). MyRepositoryFactory будет иметь статический метод Create(), который создал бы конкретный репозиторий, необходимый для вашей базы данных. Если тип базы данных изменяется, вам нужно только обновить Factory, чтобы вернуть новый репозиторий для вашей новой базы данных.

8

Это решение, которое я придумал для этой ситуации, просто проверяет таблицу для записи с другим идентификатором, имеющим то же значение для проверяемого свойства. Предполагается, что вы будете использовать LinqToSQL и что любая таблица, для которой требуется эта валидация, имеет один столбец идентификатора.

Я также установил уникальное ограничение для базовой таблицы в базе данных. Этот атрибут позволяет мне добавить хорошее сообщение об ошибке в форму и связать ее с соответствующим свойством.

public class UniqueAttribute : ValidationAttribute 
{ 
    public Func<DataContext> GetDataContext { get; private set; } 
    public string IDProperty { get; private set; } 
    public string Message { get; private set; } 

    public UniqueAttribute(Type dataContextType, string idProperty, string message) 
    { 
     IDProperty = idProperty; 
     Message = message; 
     GetDataContext =() => (DataContext)Activator.CreateInstance(dataContextType); 
    } 

    public UniqueAttribute(Type dataContextType, string idProperty, string message, string connectionString) 
    { 
     IDProperty = idProperty; 
     Message = message; 
     GetDataContext =() => (DataContext)Activator.CreateInstance(dataContextType, new object[] { connectionString }); 
    } 

    protected override ValidationResult IsValid(object value, ValidationContext validationContext) 
    { 
     var idProperty = validationContext.ObjectType.GetProperty(IDProperty); 
     var idType = idProperty.PropertyType; 
     var id = idProperty.GetValue(validationContext.ObjectInstance, null); 

     // Unsightly hack due to validationContext.MemberName being null :(
     var memberName = validationContext.ObjectType.GetProperties() 
      .Where(p => p.GetCustomAttributes(false).OfType<DisplayAttribute>().Any(a => a.Name == validationContext.DisplayName)) 
      .Select(p => p.Name) 
      .FirstOrDefault(); 
     if (string.IsNullOrEmpty(memberName)) 
     { 
      memberName = validationContext.DisplayName; 
     } 
     // End of hack 

     var validateeProperty = validationContext.ObjectType.GetProperty(memberName); 
     var validateeType = validateeProperty.PropertyType; 
     var validatee = validateeProperty.GetValue(validationContext.ObjectInstance, null); 

     var idParameter = Expression.Constant(id, idType); 
     var validateeParameter = Expression.Constant(validatee, validateeType); 
     var objectParameter = Expression.Parameter(validationContext.ObjectType, "o"); 
     var objectIDProperty = Expression.Property(objectParameter, idProperty); 
     var objectValidateeProperty = Expression.Property(objectParameter, validateeProperty); 
     var idCheck = Expression.NotEqual(objectIDProperty, idParameter); 
     var validateeCheck = Expression.Equal(objectValidateeProperty, validateeParameter); 
     var compositeCheck = Expression.And(idCheck, validateeCheck); 
     var lambda = Expression.Lambda(compositeCheck, objectParameter); 
     var countMethod = typeof(Queryable).GetMethods().Single(m => m.Name == "Count" && m.GetParameters().Length == 2); 
     var genericCountMethod = countMethod.MakeGenericMethod(validationContext.ObjectType); 

     using (var context = GetDataContext()) 
     { 
      var table = context.GetTable(validationContext.ObjectType) as IQueryable<Models.Group>; 
      var count = (int)genericCountMethod.Invoke(null, new object[] { table, lambda }); 
      if (count > 0) 
      { 
       return new ValidationResult(Message); 
      } 
     } 

     return null; 
    } 
} 

Пример использования:

[MetadataType(typeof(UserMetadata))] 
public partial class Group : IDatabaseRecord 
{ 
    public class UserMetadata 
    { 
     [Required(ErrorMessage = "Name is required")] 
     [StringLength(255, ErrorMessage = "Name must be under 255 characters")] 
     [Unique(typeof(MyDataContext), "GroupID", "Name must be unique")] 
     public string Name { get; set; } 
    } 
} 
0

Я любовь @ решение daveb в. К сожалению, через три года для меня потребовались довольно серьезные изменения. Вот его решение обновлено для EF6. Надеюсь, кто-то спасет час или около того.

public class UniqueAttribute : ValidationAttribute 
{ 
    public UniqueAttribute(string idProperty, string message) 
    { 
     IdProperty = idProperty; 
     Message = message; 
    } 

    [Inject] 
    public DataContext DataContext { get; set; } 
    private string IdProperty { get; set; } 
    private string Message { get; set; } 

    protected override ValidationResult IsValid(object value, ValidationContext validationContext) 
    { 
     var objectType = validationContext.ObjectType; 
     if (objectType.Namespace == "System.Data.Entity.DynamicProxies") 
     { 
      objectType = objectType.BaseType; 
     } 

     var idProperty = objectType.GetProperty(IdProperty); 
     var idType = idProperty.PropertyType; 
     var id = idProperty.GetValue(validationContext.ObjectInstance, null); 

     var memberName = validationContext.MemberName; 
     var validateeProperty = objectType.GetProperty(memberName); 
     var validateeType = validateeProperty.PropertyType; 
     var validatee = validateeProperty.GetValue(validationContext.ObjectInstance, null); 

     var idParameter = Expression.Constant(id, idType); 
     var validateeParameter = Expression.Constant(validatee, validateeType); 
     var objectParameter = Expression.Parameter(objectType, "o"); 
     var objectIdProperty = Expression.Property(objectParameter, idProperty); 
     var objectValidateeProperty = Expression.Property(objectParameter, validateeProperty); 
     var idCheck = Expression.NotEqual(objectIdProperty, idParameter); 
     var validateeCheck = Expression.Equal(objectValidateeProperty, validateeParameter); 
     var compositeCheck = Expression.And(idCheck, validateeCheck); 
     var lambda = Expression.Lambda(compositeCheck, objectParameter); 
     var countMethod = typeof(Queryable).GetMethods().Single(m => m.Name == "Count" && m.GetParameters().Length == 2); 
     var genericCountMethod = countMethod.MakeGenericMethod(objectType); 

     var table = DataContext.Set(objectType); 
     var count = (int)genericCountMethod.Invoke(null, new object[] { table, lambda }); 
     if (count > 0) 
     { 
      return new ValidationResult(Message); 
     } 

     return null; 
    } 
} 
1

просто сделать что-то вроде этого на модели

[StringLength(100)] 
[Index("IX_EntidadCodigoHabilitacion", IsUnique = true)] 
public string CodigoHabilitacion { get; set; } 
Смежные вопросы