数据注释的唯一约束

时间:2010-08-04 14:32:22

标签: asp.net-mvc validation data-annotations

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

4 个答案:

答案 0 :(得分: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;
    }
}

使用示例:

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

答案 1 :(得分:2)

在你的模特上做这样的事情

//----Filter-----Date Period------
$("#btndatefilter").click(function () {
    var startdate = $('#datestart').datepicker('getDate');
    var enddate = $('#dateend').datepicker('getDate');
    var dateFilter ={ StartDate: startdate.toISOString(), EndDate: enddate.toISOString() };
    $.ajax({
        url: "/AuditActivities/FilterByDate",
        type: "GET",
        data: dateFilter,
    })
    .done(function (auditActivity) {
        $("#res").html(auditActivity);
    });
});
//----Filter-----Activity------
$("#Activity").change(function () {
    var index = $('#Activity').find('option:selected').val();
    $.ajax({
        url: "/AuditActivities/FilterByActivity",
        type: "GET",
        data: { chx: index}
    })
    .done(function (auditActivity) {
        $("#res").html(auditActivity);
    });
});

答案 2 :(得分: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即可为新数据库返回新的存储库。

答案 3 :(得分: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;
    }
}