2010-08-04 120 views
6

我使用System.ComponentModel.DataAnnotations命名空间来验证我的域类。如何创建自定义属性来验证属性的唯一性,而不考虑数据库(例如通过某个接口)?数据注释的唯一约束

回答

1

如果我正确地理解了你,你应该能够创建一个自定义的ValidationAttribute并通过一个自定义工厂来获取你的仓库的上下文。

验证:

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); 
    } 
} 

你将不得不与isUnique设置()方法的IRepository接口。 MyRepositoryFactory将有一个名为Create()的静态方法,它将创建数据库所需的具体存储库。如果数据库类型发生更改,则只需更新Factory以为新数据库返回新的存储库。

8

这是我针对这种情况提出的解决方案,它只是检查表中是否有一个具有相同值的待验证属性的记录。它假定你将使用LinqToSQL,并且任何需要这种验证的表都有一个ID列。

我也想在数据库中的基础表上设置一个唯一的约束。该属性允许我在窗体上放置一个很好的错误消息并将其与相应的属性相关联。

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; }