Как повторно использовать where clauses в запросах Linq To Sql

У меня есть пользователи, которые ищут записи типа Record. Они вводят термин поиска в текстовое поле, а затем я ищу записи, сопоставляя несколько полей с поисковым термином.

Мой запрос выглядит так:

var results = from record in DataContext.Records
              where
                   record.Field1.ToLower().Contains(term) ||
                   record.Field2.ToLower().Contains(term) ||
                   record.Field3.ToLower().Contains(term)
              select record;

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

var filter = new Func<Record, string, bool>(
                (record, term) =>
                    record.Field1.ToLower().Contains(term) ||
                    record.Field2.ToLower().Contains(term) ||
                    record.Field3.ToLower().Contains(term)
             );

var results = from record in DataContext.Records
              where filter(record, term)
              select record;

Однако это не работает, потому что:

Метод 'System.Object DynamicInvoke (System.Object [])' не поддерживает перевод на SQL.

Как я могу повторно использовать мое условие в запросах?

Ответы

Ответ 1

Используйте CompiledQuery!

var filter = CompiledQuery.Compile(
    (DatabaseDataContext dc, Record record, string term) =>
        record.Field1.ToLower().Contains(term) ||
        record.Field2.ToLower().Contains(term) ||
        record.Field3.ToLower().Contains(term)
);

var results = from record in DataContext.Records
              where filter(DataContext, record, term)
              select record;

Для получения дополнительной информации см. Практическое руководство. Сохранение и повторное использование запросов.

Ответ 2

Вам нужно создать выражение вместо функции:

Expression<Func<Record, bool>> filter = 
  record => record.Field1.ToLower().Contains(term);  // rest omitted

Выражение лямбда остается неизменным, но вам нужно вернуть его в переменную типа Expression<Func<Record, bool>> -, которая заставит компилятор С# скомпилировать его как выражение вместо делегата, что позволит ему передать LINQ в SQL.

Однако вы не сможете использовать переменную выражения с предложением С# -syntax where: вам нужно использовать метод расширения Where:

var results = DataContext.Records.Where(filter);

Отредактировано для добавления: Если вы хотите иметь возможность создавать фильтры на разных терминах, вам просто нужен метод для создания выражения из термина:

private static Expression<Func<Record, bool>> Filter(string term)
{
  return r => r.Field1.ToLower().Contains(term);
}

var results = DataContext.Records.Where(Filter(term));

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

Func<string, Expression<Func<Record, bool>>> filter =
  term => (r => r.Field1.ToLower().Contains(term));

var results = DataContext.Records.Where(filter(term));

Независимо от того, что важно в предложении Where должно быть Expression<Func<Record, bool>>, но, как показано выше, вы можете заставить выражение зависеть от term, построив подходящее выражение "на лету". Это именно то, что LINQ to SQL будет делать, если вы изложили фильтр в команде Where.

Ответ 3

В дополнение к проблеме Expression<Func<Record, bool>>, которую другие указали, я предлагаю изучить PredicateBuilder. Это очень удобно для динамического объединения лямбда-выражений.

Ответ 4

Я думаю, вам нужно сделать это Expression<Func<Record, bool>>. В противном случае он пытается перевести вызов метода С# на SQL, а не его описание. Это не гарантия того, что эта версия будет работать; Я не уверен, какие строковые функции могут быть переведены в SQL.