Уникальное ограничение с аннотацией данных - PullRequest
8 голосов
/ 04 августа 2010

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

Ответы [ 4 ]

8 голосов
/ 15 ноября 2012

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

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

[StringLength(100)]
[Index("IX_EntidadCodigoHabilitacion", IsUnique = true)]
public string CodigoHabilitacion { get; set; }
1 голос
/ 26 апреля 2011

Если я правильно вас понимаю, вы сможете создать собственный атрибут ValidationAttribute и получить контекст для своего хранилища через собственную фабрику.

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

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

0 голосов
/ 13 июля 2015

Я люблю @ решение Дэйва. К сожалению, три года спустя мне потребовалась довольно тяжелая модификация. Вот его решение обновлено для 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;
    }
}
...