数据注释的唯一约束

Mur*_*ima 8 validation asp.net-mvc data-annotations

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

dav*_*veb 8

这是我为这种情况提出的解决方案,它只是检查表中是否有一个具有不同id的记录,该id对于要验证的属性具有相同的值.它假定您将使用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;
    }
}
Run Code Online (Sandbox Code Playgroud)

用法示例:

[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; }
    }
}
Run Code Online (Sandbox Code Playgroud)


小智 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

您将拥有一个带有 IsUnique() 方法的 IRepository 接口。MyRepositoryFactory 有一个名为 Create() 的静态方法,它将创建数据库所需的具体存储库。如果数据库类型发生变化,您只需更新 Factory 即可为新数据库返回一个新的 Repository。