Общий атрибут проверки для проверки уникальности в контексте данных linq to sql - PullRequest
2 голосов
/ 09 января 2010

Я программирую asp.net уже пару дней. Вот вопрос, который я даже не могу понять для себя.

Надеюсь, из кода видно, чего я хочу достичь, и у меня есть, но это не красиво. Кроме того, я хотел бы использовать его в любой таблице, в любом поле, т. Е. Проверять уникальность значения по заданной мной таблице и полю, передавая все это в конструктор атрибута.

public class UniqueEmailAttribute : ValidationAttribute
{
    public UniqueEmailAttribute()
    {
    }

    public override Boolean IsValid(Object value)
    {
        //not pretty. todo: do away with this.
        var db = new CoinDataContext();
        int c = db.Emails.Count(e => e.Email1 == value.ToString());
        return (Boolean) (c == 0);
    }
}

Ответы [ 3 ]

4 голосов
/ 10 января 2010

Это только из форумов asp.net от Брэда Уилсона. Так доволен этим. Нет обработки ошибок!

using System;
using System.ComponentModel.DataAnnotations;
using System.Data.Linq;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

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

        // 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);
        ConstantExpression rhs = Expression.Constant(convertedValue);

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

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

        // 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);
}
2 голосов
/ 09 января 2010

Сначала давайте посмотрим на переписывание атрибута ...

public override bool IsValid(object value)
{
    var db = new CoinDataContext();

    //Return whether none of the email contains the specified value
    return db.Emails.Count(e => e.Email1 == value.ToString()) == 0;
}

Кроме того, не было необходимости приводить (c == 0) в качестве логического значения, поскольку в результате этой операции уже выполняется логическое преобразование. И тип bool является псевдонимом для Boolean точно так же, как int является псевдонимом для Int32. Либо приемлемо. Я предпочитаю строчную версию себя.

Как Алекс уже предложил в своем ответе , это не будет верным способом определения того, является ли адрес электронной почты уникальным, когда он входит в базу данных. Только то, что он уникален на момент проверки.

Наконец, и немного не по теме ... У меня написано некоторые расширения linq, такие как следующий класс. Его использование позволило бы мне переписать возвращаемый атрибут на db.Emails.None(e => e.Email1 == value.ToString());. Это делает его немного более читабельным.

Обновление Не существует способа определения уникальности значения в базе данных без перехода в базу данных и сравнения строк с записанными значениями. Вам все еще нужно создать экземпляр в базе данных. Однако я хотел бы взглянуть на , разделяя эти проблемы на такие области, как уровень обслуживания и уровень данных (отдельные проекты из проекта веб-сайта MVC). Ваш уровень данных будет обрабатывать исключительно все, что связано с базой данных. Если вы хотите, я могу написать несколько примеров того, как вы должны отделить CoinDataContext от самого атрибута?

Чтобы решить еще одну проблему, мы убираем необходимость запроса внутри атрибута, но вам все равно нужен вызов базы данных и указание таблицы, которую вы хотите использовать.

Однако, поскольку это атрибут, я не уверен на 100%, можете ли вы использовать лямбда-выражения linq в атрибуте it, поэтому ваш атрибут должен оставаться обобщенным таким образом.

Проект уровня данных

Этот слой будет содержать разные классы, относящиеся к разным таблицам. Класс ниже посвящен столу электронной почты.

Email Mapper class

public static class EmailMapper
{
  public static void IsValid(Func<string, bool> query)
  {
    var db = new CoinDataContext();
    return db.Emails.Count(query) == 0;
  }
}

Сервисный уровень проекта

Этот уровень отвечает за общую проверку объектов, но также используется для перехода на другие уровни, такие как внешние API.

Класс EmailService

public static class EmailService
{
  public static IsValid(string address)
  {
    bool isValid = false;

    //...Check email is valid first with regex. Not done.
    isValid = RegexHelper.IsEmailAddressValid(address);

    //Go to the database and determine it's valid ONLY if the regex passes.
    return isValid ? EmailMapper.IsValid(x=> x.Email == address) : false;
  }
}

Класс атрибутов в веб-проекте

public override Boolean IsValid(Object value)
{
    return EmailService.IsValid(value.ToString());
}
0 голосов
/ 09 января 2010

Я не в LINQ, но, похоже, вы пытаетесь обеспечить уникальность на стороне клиента. Это просто невозможно. Ограничения уникальности должны быть обязательными в базе данных. Как вы думаете, что произойдет, если параллельная транзакция отправит адрес электронной почты сразу после того, как эта проверка была выполнена?

Даже если вы проверяете просто сообщение «Извините, этот адрес уже используется», существует все еще вероятность того, что другая транзакция вставит тот же адрес.

...