带数据注释的唯一约束

发布于 2024-09-12 11:08:01 字数 100 浏览 9 评论 0原文

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

I'm using the System.ComponentModel.DataAnnotations namespace to validate my domain classes. How can I create a custom attribute to validate the uniqueness of a property regardless of the database (through some interface, for example)?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

两相知 2024-09-19 11:08:01

这是我针对这种情况提出的解决方案,它只是检查表中是否有具有不同 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; }
    }
}

This is the solution I came up with for this situation, it simply checks the table for a record with a different id that has the same value for the property being validated. It assumes that you will be using LinqToSQL, and that any table on which this kind of validation is required has a single ID column.

I'd also put a unique constraint on the underlying table in the database. This attribute allows me to put a nice error message on the form and associate it with the appropriate property.

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

Example usage:

[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; }
    }
}
萌逼全场 2024-09-19 11:08:01

只需在你的模型上做这样的事情

[StringLength(100)]
[Index("IX_EntidadCodigoHabilitacion", IsUnique = true)]
public string CodigoHabilitacion { get; set; }

just do something like this on your model

[StringLength(100)]
[Index("IX_EntidadCodigoHabilitacion", IsUnique = true)]
public string CodigoHabilitacion { get; set; }
满天都是小星星 2024-09-19 11:08:01

如果我正确理解您的意思,您应该能够创建自定义 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 即可为新数据库返回一个新的 Repository。

If I am understanding you properly, you should be able to create a custom ValidationAttribute and get a context to your repository through a custom factory.

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

You would have an IRepository interface with an IsUnique() method. The MyRepositoryFactory would have a static method called Create() which would create the concrete Repository necessary for your database. If the database type changes, you only need to update the Factory to return a new Repository for your new database.

淑女气质 2024-09-19 11:08:01

喜欢@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;
    }
}

I love @daveb's solution. Unfortunately, three years later it required some pretty heavy modification for me. Here's his solution updated for EF6. Hopefully will save someone an hour or so of fiddling.

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;
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文