Уникальное ограничение с аннотацией данных

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


person Murilo Lima    schedule 04.08.2010    source источник


Ответы (4)


Это решение, которое я придумал для этой ситуации, оно просто проверяет таблицу на наличие записи с другим идентификатором, который имеет то же значение для проверяемого свойства. Предполагается, что вы будете использовать 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; }
    }
}
person daveb    schedule 15.11.2012

просто сделайте что-то подобное на своей модели

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

Если я вас правильно понимаю, вы должны иметь возможность создать собственный атрибут 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);
    }
}

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

person Chmod    schedule 25.04.2011

Мне нравится решение @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;
    }
}
person Lee Richardson    schedule 13.07.2015