EF Core — не удается запросить объект if.Имя существует в списке

#sql-server #.net-core #linq-to-sql #entity-framework-core

#sql-сервер #.net-ядро #linq-to-sql #entity-framework-core

Вопрос:

Использование Entity Framework Core 3.1.7

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

 public class Product 
{
   public int Id {get; set;}
   public string Name {get; set;}
}
  

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

 var searchParameters = query.SearchParameters.ToLower().Split(' ', ',', ' ').Distinct();

var result = _context.Products
                    .Where(p => searchParameters.Any()
                    amp;amp; (searchParameters.Any(x => p.Name.ToLower().Contains(x)) //Version 1
                    ).ToList();
  

или альтернатива

 searchParameters.Any(x => EF.Functions.Like(p.Name, "%"   x   "%")) //Version 2
  

Но, однако, я настраиваю эту, казалось бы, простую вещь, которую я получаю:

Выражение LINQ ‘DbSet .Где(p => __searchParameters_0 .Any(x => p.Name .Не удалось перевести ToLower().Contains(x)))’. Либо перепишите запрос в форме, которая может быть переведена, либо переключитесь на оценку клиента явно, вставив вызов AsEnumerable(), AsAsyncEnumerable(), ToList() или ToListAsync()

Я понимаю, что .ToLower() это будет проблемой, поэтому я хотел запустить оператор LIKE для поиска без учета регистра, как и для запросов SQL. Но даже в этом случае List<string> не переводится.

Комментарии:

1. Обратите внимание, что в зависимости от настроек вашего SQL Server сопоставление без учета регистра вполне может быть по умолчанию, поэтому вам ничего не нужно делать, чтобы поиск был переведен незаметно.

2. @NetMage Да, спасибо! Я попытался пренебречь ToLower, и он может найти результаты, но только если я выполняю один запрос по параметрам поиска с момента . Any не переводится.

Ответ №1:

Если вы хотите использовать LinqKit (или имитировать части построения предикатов), вы можете использовать метод расширения для расширения Any( Contains) выражение в выражение «или»:

 public static class LinqKitExt { // using LINQKit
    // keyFne - extract string key from row
    // searchTerms - IEnumerable<string> where one must be contained by a row's key
    // dbq.Where(r => searchTerms.Any(s => keyFne(r).Contains(s)))
    public static IQueryable<T> WhereContainsAny<T>(this IQueryable<T> dbq, Expression<Func<T,string>> keyFne, IEnumerable<string> searchTerms) {
        var pred = PredicateBuilder.New<T>();
        foreach (var s in searchTerms)
            pred = pred.Or(r => keyFne.Invoke(r).Contains(s));

        return dbq.Where((Expression<Func<T,bool>>)pred.Expand());
    }
}
  

(И 51 другой вариант, где/OrderBy[По убыванию] Любой / Все содержит/начинается с.)

Тогда вы можете использовать его следующим образом

 var result = _context.Products
                     .WhereContainsAny(r => r.Name, searchParameters)
                     .ToList();
  

PS Преследуя другой ответ, я понял, что перенос теста на вызывающий объект устранил большинство вариантов:

 // searchTerms - IEnumerable<TKey> where all must be in a row's key
// testFne(row,searchTerm) - test one of searchTerms against a row
// dbq.Where(r => searchTerms.Any(s => testFne(r,s)))
public static IQueryable<T> WhereAny<T,TKey>(this IQueryable<T> dbq, IEnumerable<TKey> searchTerms, Expression<Func<T, TKey, bool>> testFne) {
    var pred = PredicateBuilder.New<T>();
    foreach (var s in searchTerms)
        pred = pred.Or(r => testFne.Invoke(r, s));

    return dbq.Where((Expression<Func<T,bool>>)pred.Expand());
}
  

Затем вы просто вызываете:

 var result = _context.Products
                     .WhereAny(searchParameters, (r,s) => r.Name.Contains(s))
                     .ToList();
  

Комментарии:

1. Работает как мечта! Спасибо! Мне просто любопытно, не было ли это поведение включено для Entity Framework (не Core) ранее? В любом случае, я пока придерживаюсь predicatebuilder! Большое спасибо!

2. @RKrogh Я полагаю, что вы можете быть правы, ядро EF (даже 5) потеряло много переводов из EF 6 в стремлении быть более последовательным (и часто менее полезным)…

3. @RKrogh К вашему сведению, я нашел способ сделать его более универсальным, передав тест вызывающей стороне; а также исправлена ошибка в ExpressionStarter обработке в исходном ответе

4. Приятно! Спасибо, что вернулись ко мне. Мне нравится вторая версия. Проверит это. В чем заключалась ошибка? Я запускал его с тех пор, как вы его дали, и ничего не заметил 🙂

5. @RKrogh Не используя var with pred , первый вызов .Or or .And не понял, что нужно удалить starter, и вместо этого по умолчанию используется false который работает с .Or , но не работает с .And . (В основном компилятор вызывает неправильную версию метода .Or or .And .)