Предложение LINQ multiple where

У меня есть таблица курсов, в которой мне нужно искать по ключевым словам, введенным в поле поиска. Вот пример запроса:

SELECT * FROM Courses WHERE 
Title LIKE '%word%' OR Title LIKE '%excel%' OR 
Contents LIKE '%word%' OR Contents LIKE '%excel%'

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

Я попытался использовать PredicateBuilder, он отлично работает, пока поле VARCHAR. Для полей «ТЕКСТ» кавычки не генерируются, поэтому компилятор выдает сообщение об ошибке. Вот SQL, сгенерированный PredicateBuilder

SELECT [t0].[CoursesID], [t0].[Title], [t0].[Contents], [t0].[Active], 
FROM [dbo].[Courses] AS [t0]
WHERE ([t0].[Title] LIKE '%word%') OR ([t0].[Contents] LIKE %word%) OR 
([t0].Title] LIKE '%excel%') OR ([t0].[Contents] LIKE %excel%)

Обратите внимание, что нет единой цитаты для поля «Содержание», которое является текстовым полем в базе данных.

Есть ли простой способ создать оператор WHERE и прикрепить его к запросу? Кто-нибудь знает, как я могу это сделать без PredicateBuilder?

Заранее спасибо.

Ответов (2)

Решение

Поскольку вы работаете с LINQ, я полагаю, вы работаете с контекстом данных LINQ-to-SQL, верно? У меня нет запасного DataContext, чтобы проверить это, но это должно дать вам некоторые идеи.

Я не знаю, будет ли это работать с контекстом данных, но большинство из них являются довольно простыми вещами (объединение оператора OR и вызова метода Contains), поэтому это не должно вызывать проблем при преобразовании запроса в SQL.

Сначала я создаю специальную функцию, которая будет строить мой предикат:

Func<string, Func<DataItem, bool>> buildKeywordPredicate =
    keyword =>
        x => x.Title.Contains(keyword)
            || x.Contents.Contains(keyword);

Это функция, которая принимает одно строковое ключевое слово, а затем возвращает другую функцию, которая принимает DataItem и проверяет его на соответствие ключевому слову.

В принципе, если вы передаете в «Стек», вы получите предикат: x => x.Title.Contains("Stack") || x.Contents.Contains("Stack") .

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

Func<Func<DataItem,bool>, Func<DataItem, bool>, Func<DataItem, bool>> buildOrPredicate =
    (pred1, pred2) =>
        x => pred1(x) || pred2(x);

Эта функция принимает 2 предиката и затем объединяет их с помощью операции ИЛИ.

Имея эти две функции, я могу построить свой предикат where следующим образом:

foreach (var word in keywords) {            
    filter = filter == null
        ? buildKeywordPredicate(word)
        : buildOrPredicate(filter, buildKeywordPredicate(word));
}

Первая строка внутри цикла в основном проверяет, является ли фильтр нулевым. Если да, то нам нужен простой фильтр по ключевым словам.

В противном случае, если фильтр не равен нулю, нам нужно связать существующие фильтры с помощью операции ИЛИ, поэтому мы передаем существующий фильтр и новый фильтр по ключевым словам в buildOrPredicate, чтобы сделать именно это.

И теперь мы можем создать часть запроса WHERE:

var result = data.Where(filter);

Передача только что построенного сложного предиката.

Я не знаю, будет ли это отличаться от использования PredicateBuilder, но поскольку мы откладываем перевод запроса на механизм LINQ-to-SQL, проблем возникнуть не должно.

Но, как я уже сказал, я не тестировал его на реальном контексте данных, поэтому, если есть какие-то проблемы, вы можете написать в комментариях.

Вот консольное приложение, которое я создал для тестирования: http://pastebin.com/feb8cc1e

Надеюсь это поможет!


РЕДАКТИРОВАТЬ: для более общей и многоразовой версии, которая включает в себя правильное использование деревьев выражений в LINQ, ознакомьтесь с сообщением в блоге Томаса Петричека: http://tomasp.net/articles/dynamic-linq-queries.aspx

Поскольку построитель предикатов не знает тип БД свойства, для которого вызывается метод Contains, я предполагаю, что это может быть проблемой внутри linq to sql. Вы пробовали использовать обычный запрос (не построитель предикатов) и столбец TEXT с Contains?