Составьте предикаты LINQ-to-SQL в один предикат - PullRequest
5 голосов
/ 24 сентября 2010

(Предыдущий вопрос, Рекурсивное (?) Объединение предикатов LINQ в один предикат , аналогичен этому, но на самом деле я задал неправильный вопрос ... решение там удовлетворило поставленный вопрос, нона самом деле это не то, что мне нужно. Но они разные. Честно.)

Учитывая следующий текст поиска:

"keyword1 keyword2 ... keywordN"

Я хочу получить следующий SQL:

SELECT [columns] FROM Customer 
  WHERE (
        Customer.Forenames LIKE '%keyword1%' 
        OR 
        Customer.Forenames LIKE '%keyword2%'
        OR
        ...
        OR
        Customer.Forenames LIKE '%keywordN%'
    ) AND (
        Customer.Surname LIKE '%keyword1%' 
        OR 
        Customer.Surname LIKE '%keyword2%'
        OR
        ....
        OR
        Customer.Surname LIKE '%keywordN%'
    )

По сути, мы разбиваем текст поиска на пробелы, обрезаем каждый токен, строим предложение OR из нескольких частей на основе каждого, а затем объединяем предложения AND.

IЯ делаю это в Linq-to-SQL, и я понятия не имею, как динамически составлять предикат на основе произвольно длинного списка субпредикатов.Для известного количества предложений легко составить предикаты вручную:

dataContext.Customers.Where(
    ( 
      Customer.Forenames.Contains("keyword1") 
      ||
      Customer.Forenames.Contains("keyword2")
    ) && (
      Customer.Surname.Contains("keyword1") 
      ||
      Customer.Surname.Contains("keyword2")
    )
);

Короче говоря, мне нужен метод, который при двух предикатах возвращает один предикат, составляющий два исходных предиката с предоставленнымоператор, но ограничен операторами, явно поддерживаемыми Linq-to-SQL.Есть идеи?

Ответы [ 2 ]

6 голосов
/ 24 сентября 2010

Вы можете использовать PredicateBuilder класс

IQueryable<Customer> SearchCustomers (params string[] keywords)
{
  var predicate = PredicateBuilder.False<Customer>();

  foreach (string keyword in keywords)
  {
    // Note that you *must* declare a variable inside the loop
    // otherwise all your lambdas end up referencing whatever
    // the value of "keyword" is when they're finally executed.
    string temp = keyword;
    predicate = predicate.Or (p => p.Forenames.Contains (temp));
  }
  return dataContext.Customers.Where (predicate);
}

(на самом деле это пример со страницы PredicateBuilder, я просто адаптировал его к вашему случаю ...)


EDIT:

На самом деле я неправильно понял ваш вопрос, и мой пример выше охватывает только часть решения ... Следующий метод должен делать то, что вы хотите:

IQueryable<Customer> SearchCustomers (string[] forenameKeyWords, string[] surnameKeywords)
{
    var predicate = PredicateBuilder.True<Customer>();

    var forenamePredicate = PredicateBuilder.False<Customer>();
    foreach (string keyword in forenameKeyWords)
    {
      string temp = keyword;
      forenamePredicate = forenamePredicate.Or (p => p.Forenames.Contains (temp));
    }
    predicate = PredicateBuilder.And(forenamePredicate);

    var surnamePredicate = PredicateBuilder.False<Customer>();
    foreach (string keyword in surnameKeyWords)
    {
      string temp = keyword;
      surnamePredicate = surnamePredicate.Or (p => p.Surnames.Contains (temp));
    }
    predicate = PredicateBuilder.And(surnamePredicate);

    return dataContext.Customers.Where(predicate);
}

Вы можете использовать это так:

var query = SearchCustomers(
    new[] { "keyword1", "keyword2" },
    new[] { "keyword3", "keyword4" });

foreach (var Customer in query)
{
    ...
}
0 голосов
/ 24 сентября 2010

Обычно вы бы связывали вызовы .Where(...). E.g.:

var a = dataContext.Customers;
if (kwd1 != null)
    a = a.Where(t => t.Customer.Forenames.Contains(kwd1));
if (kwd2 != null)
    a = a.Where(t => t.Customer.Forenames.Contains(kwd2));
// ...
return a;

LINQ-to-SQL объединит все это обратно в одно предложение WHERE.

Это не работает с OR, однако. Вы могли бы использовать объединения и пересечения, но я не уверен, достаточно ли умен LINQ-to-SQL (или SQL Server), чтобы сложить его обратно в одно предложение WHERE. OTOH, не имеет значения, если производительность не страдает. Во всяком случае, это будет выглядеть примерно так:

<The type of dataContext.Customers> ff = null, ss = null;

foreach (k in keywords) {
    if (keywords != null) {
        var f = dataContext.Customers.Where(t => t.Customer.Forenames.Contains(k));
        ff = ff == null ? f : ff.Union(f);

        var s = dataContext.Customers.Where(t => t.Customer.Surname.Contains(k));
        ss = ss == null ? s : ss.Union(s);
    }
}
return ff.Intersect(ss);
...