Linq PredicateBuilder с условными фильтрами AND, OR и NOT - PullRequest
5 голосов
/ 15 июня 2010

У нас есть проект, использующий LINQ to SQL, для которого мне нужно переписать пару страниц поиска, чтобы позволить клиенту выбрать, хотят ли они выполнить и или или поиск.

Я думал о повторении запросов LINQ, используя PredicateBuilder , и думаю, что это работает довольно хорошо. У меня фактически есть класс, содержащий мои предикаты, например:

internal static Expression<Func<Job, bool>> Description(string term)
{
    return p => p.Description.Contains(term);
}

Чтобы выполнить поиск, я делаю это (некоторый код опущен для краткости):

public Expression<Func<Job, bool>> ToLinqExpression()
{
    var predicates = new List<Expression<Func<Job, bool>>>();
    // build up predicates here

    if (SearchType == SearchType.And)
    {
        query = PredicateBuilder.True<Job>();
    }
    else
    {
        query = PredicateBuilder.False<Job>();
    }

    foreach (var predicate in predicates)
    {
        if (SearchType == SearchType.And)
        {
            query = query.And(predicate);
        }
        else
        {
            query = query.Or(predicate);
        }
    }
    return query;
}

Хотя я вполне доволен этим, у меня есть две проблемы:

  1. Блоки if / else, которые оценивают свойство SearchType, чувствуют, что они могут быть потенциальным запахом кода.
  2. Клиент теперь настаивает на возможности выполнять поиск, а не «/» или нет.

Что касается пункта 2, я думаю, что я мог бы сделать это, просто переписав мои выражения, например ::

internal static Expression<Func<Job, bool>> Description(string term, bool invert)
{
    if (invert)
    {
        return p => !p.Description.Contains(term);
    }
    else
    {
        return p => p.Description.Contains(term);
    }
}

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

1 Ответ

8 голосов
/ 16 июня 2010

Если вы ищете меньше строк, вы можете заменить if / else на троичный оператор:

query = SearchType == SearchType.And ? PredicateBuilder.True<Job>() : PredicateBuilder.False<Job>();

   foreach (var predicate in predicates)
   {
        query = SearchType == SearchType.And ? query.And(predicate) : query.Or(predicate);
   }

для части 'and not' / 'or not' оператор ! должен добиться цели.

PD: Проверяли ли вы, что часть foreach правильно устанавливает предикаты? Насколько я помню, вы строите выражение, которое будет выполнено позднее, так что вы можете буквальная ссылка только на последний предикат набора в последней итерации, и поэтому вы должны использовать временную переменную для сохранения значения каждой итерации.

EDIT: Если вы хотите отрицать программные выражения, это сложно, вы можете попробовать что-то вроде:

internal static Expression<Func<Job, bool>> Description(string term, bool invert)
        {
           return NegateExp<Func<Job, bool>>(p => p.Description.Contains(term), invert);
        }

И метод NegateExp будет выглядеть примерно так:

public static Expression<TDelegate> NegateExp<TDelegate>(Expression<TDelegate> expression, bool inverse)
        {
            if (inverse)
            {
                return Expression.Lambda<TDelegate>(Expression.Not(expression.Body), expression.Parameters);
            }
            return expression;
        }

Вы можете взглянуть на этот вопрос для большего количества примеров. Есть ли способ отрицать предикат?

...