Как я могу создать универсальный атрибут UniqueValidationAttribute в C # и DataAnnotation? - PullRequest
4 голосов
/ 22 апреля 2010

Я пытаюсь создать UniqueAttribute, используя System.ComponentModel.DataAnnotations.ValidationAttribute

Я хочу, чтобы это было универсальным, так как я мог передать Linq DataContext, имя таблицы, поле и проверить, если входящийзначение уникально или нет.

Вот фрагмент кода, который невозможно скомпилировать, и я застрял прямо сейчас:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.DataAnnotations;
using System.Data.Linq;
using System.ComponentModel;

namespace LinkDev.Innovation.Miscellaneous.Validation.Attributes
{
    public class UniqueAttribute : ValidationAttribute
    {
        public string Field { get; set; }

        public override bool IsValid(object value)
        {
            string str = (string)value;
            if (String.IsNullOrEmpty(str))
                return true;

            // this is where I'm stuck
            return (!Table.Where(entity => entity.Field.Equals(str)).Any());           
        }
    }
}

Я должен использовать это в моей модели следующим образом:

[Required]
[StringLength(10)]
[Unique(new DataContext(),"Groups","name")]
public string name { get; set; }

Редактировать: Обратите внимание, что в соответствии с этим: Почему C # запрещает универсальные типы атрибутов? Я не могу использовать универсальный тип с атрибутом.

Так что мой новый подходздесь мы будем использовать деревья Reflection / Expression для построения дерева лямбда-выражений на лету.

Ответы [ 4 ]

9 голосов
/ 22 апреля 2010

Ну, после небольшого поиска я наткнулся на: http://forums.asp.net/t/1512348.aspx и я понял это, хотя это включает в себя немало кода.

Использование:

[Required]
[StringLength(10)]
[Unique(typeof(ContactsManagerDataContext),typeof(Group),"name",ErrorMessage="Group already exists")]
public string name { get; set; }

Код валидатора:

public class UniqueAttribute : ValidationAttribute
{
    public Type DataContextType { get; private set; }
    public Type EntityType { get; private set; }
    public string PropertyName { get; private set; }

    public UniqueAttribute(Type dataContextType, Type entityType, string propertyName)
    {
        DataContextType = dataContextType;
        EntityType = entityType;
        PropertyName = propertyName;
    }

    public override bool IsValid(object value)
    {
        string str = (string) value;
        if (String.IsNullOrWhiteSpace(str))
            return true;

        // Cleanup the string
        str = str.Trim();

        // Construct the data context
        ConstructorInfo constructor = DataContextType.GetConstructor(new Type[0]);
        DataContext dataContext = (DataContext)constructor.Invoke(new object[0]);

        // Get the table
        ITable table = dataContext.GetTable(EntityType);

        // Get the property
        PropertyInfo propertyInfo = EntityType.GetProperty(PropertyName);

        // Expression: "entity"
        ParameterExpression parameter = Expression.Parameter(EntityType, "entity");

        // Expression: "entity.PropertyName"
        MemberExpression property = Expression.MakeMemberAccess(parameter, propertyInfo);

        // Expression: "value"
        object convertedValue = Convert.ChangeType(value, propertyInfo.PropertyType);
        ConstantExpression rhs = Expression.Constant(convertedValue);

        // Expression: "entity.PropertyName == value"
        BinaryExpression equal = Expression.Equal(property, rhs);

        // Expression: "entity => entity.PropertyName == value"
        LambdaExpression lambda = Expression.Lambda(equal, parameter);

        // Instantiate the count method with the right TSource (our entity type)
        MethodInfo countMethod = QueryableCountMethod.MakeGenericMethod(EntityType);

        // Execute Count() and say "you're valid if you have none matching"
        int count = (int)countMethod.Invoke(null, new object[] { table, lambda });
        return count == 0;
    }

    // Gets Queryable.Count<TSource>(IQueryable<TSource>, Expression<Func<TSource, bool>>)
    private static MethodInfo QueryableCountMethod = typeof(Queryable).GetMethods().First(m => m.Name == "Count" && m.GetParameters().Length == 2);
}

Я не против того, чтобы это было некрасиво, так как я упакую его в DLL и повторно использую, гораздо лучше, чем реализовать несколько уникальных атрибутов для каждой таблицы / поля.

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

Я редактировал этот ... и он отлично работает с DI ..: D

public class UniqueAttribute : ValidationAttribute
{
    public UniqueAttribute(Type dataContextType, Type entityType, string propertyName)
    {
        DataContextType = dataContextType;
        EntityType = entityType;
        PropertyName = propertyName;
    }


    public Type DataContextType { get; private set; }


    public Type EntityType { get; private set; }


    public string PropertyName { get; private set; }


    public override bool IsValid(object value)
    {
        // Construct the data context
        //ConstructorInfo constructor = DataContextType.GetConstructor(new Type[0]);
        //DataContext dataContext = (DataContext)constructor.Invoke(new object[0]);
        var repository = DependencyResolver.Current.GetService(DataContextType);
        var data = repository.GetType().InvokeMember("GetAll", BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.Public, null, repository, null);

        // Get the table
        //ITable table = dataContext.GetTable(EntityType);


        // Get the property
        PropertyInfo propertyInfo = EntityType.GetProperty(PropertyName);


        // Our ultimate goal is an expression of:
        //   "entity => entity.PropertyName == value"


        // Expression: "value"
        object convertedValue = Convert.ChangeType(value, propertyInfo.PropertyType);
        var rhs = Expression.Constant(convertedValue);


        // Expression: "entity"
        var parameter = Expression.Parameter(EntityType, "entity");


        // Expression: "entity.PropertyName"
        var property = Expression.MakeMemberAccess(parameter, propertyInfo);


        // Expression: "entity.PropertyName == value"
        var equal = Expression.Equal(property, rhs);


        // Expression: "entity => entity.PropertyName == value"
        var lambda = Expression.Lambda(equal, parameter).Compile();

        // Instantiate the count method with the right TSource (our entity type)
        MethodInfo countMethod = QueryableCountMethod.MakeGenericMethod(EntityType);

        // Execute Count() and say "you're valid if you have none matching"
        int count = (int)countMethod.Invoke(null, new object[] { data, lambda });
        return count == 0;
    }


    // Gets Queryable.Count<TSource>(IQueryable<TSource>, Expression<Func<TSource, bool>>)
    //private static MethodInfo QueryableCountMethod = typeof(Enumerable).GetMethods().First(m => m.Name == "Count" && m.GetParameters().Length == 2);
    private static MethodInfo QueryableCountMethod = typeof(System.Linq.Enumerable).GetMethods().Single(
        method => method.Name == "Count" && method.IsStatic && method.GetParameters().Length == 2);
}
0 голосов
/ 22 апреля 2010

, как заметил @LBushkin, Attributes нужны постоянные времени компиляции.

Я бы изменил ваш класс с:

public class UniqueAttribute : ValidationAttribute

на:

public class UniqueAttribute<T> : ValidationAttribute  
where T : DataContext{

    protected T Context { get; private set; }

  ...    

    }

ииспользуйте его как:

[Required]
[StringLength(10)]
[Unique<DataContext>("Groups","name")]
public string name { get; set; }

Это поможет вам внедрить объект DataContext, если необходимо, вместо создания экземпляра каждый раз

HTH

Редактировать: так как атрибут не можетВозьмите универсальный параметр, это может быть другой потенциальный код:

public class UniqueAttribute : ValidationAttribute{

    public UniqueAttribute(Type dataContext, ...){
        if(dataContext.IsSubClassOf(typeof(DataContext))){
            var objDataContext = Activator.CreateInstance(dataContext);
        }
    }

}

и использовать его как:

[Required]
[StringLength(10)]
[Unique(typeof(DataContext), "Groups","name")]
public string name { get; set; }

HTH на этот раз:)

0 голосов
/ 22 апреля 2010

Одна проблема, которую я уже вижу, состоит в том, что вы не можете создавать экземпляры типов в качестве параметров атрибутов.

Атрибуты требуют, чтобы все аргументы были константами времени компиляции. Итак, использование:

[Unique(new DataContext(),"Groups","name")]

не скомпилируется. Возможно, вы сможете опустить new DataContext() - но тогда я подозреваю, что ваша логика проверки не будет содержать информацию о типах сущностей для запроса.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...