Entity Framework, LINQ: não é possível usar Any com StartsWith?

Sep 04 2020

Eu tenho uma consulta simples do Entity Framework LINQ. O objetivo é encontrar todas as operadoras que começam com A, B ou C:

var letters = new List<string>() { "A", "B", "C" }; // Dynamic, can be many
var results = db.Carriers.AsNoTracking()
    .Where(c => letters.Any(val => c.Name.StartsWith(val)))
    .ToList();

eu recebo

System.InvalidOperationException: 'The LINQ expression' DbSet .Where (c => __letters_0 .Any (val => val == "" || c.Name! = Null && val! = Null && c.Name.StartsWith (val) )) 'não pôde ser traduzido. Reescreva a consulta em um formato que possa ser traduzido ou alterne para a avaliação do cliente explicitamente inserindo uma chamada para AsEnumerable (), AsAsyncEnumerable (), ToList () ou ToListAsync (). Vejohttps://go.microsoft.com/fwlink/?linkid=2101038 Para maiores informações.'

Não tem como fazer isso?

Respostas

2 GertArnold Sep 08 2020 at 18:46

No Entity Framework 6, essa consulta funcionaria bem. EF6 oferece suporte à tradução de SQL StartsWithe ao uso de sequências locais ( letters) em uma expressão de consulta.

EF core 3 (a versão em questão conforme a mensagem de exceção indica) também oferece suporte à tradução de SQL StartsWith. O problema aqui (que a outra resposta perde completamente) é que a sequência local é usada de uma maneira não suportada. Uma consulta como ...

var results = db.Carriers.AsNoTracking()
    .Where(c => letters.Contains(c.Name))
    .ToList();

... seria suportado porque letterspode simplesmente ser traduzido em uma INcláusula. Mas é claro que essa é uma pergunta totalmente diferente.

O uso letters.Anyrequer EF para se converter lettersem "algo" que pode ser associado no SQL. EF6 faz isso criando um conjunto de resultados na consulta SQL:

    WHERE  EXISTS (SELECT 
        1 AS [C1]
        FROM  (SELECT 
            N'A' AS [C1]
            FROM  ( SELECT 1 AS X ) AS [SingleRowTable1]
        UNION ALL
            SELECT 
            N'B' AS [C1]
            FROM  ( SELECT 1 AS X ) AS [SingleRowTable2]
        UNION ALL
            SELECT 
            N'C' AS [C1]
            FROM  ( SELECT 1 AS X ) AS [SingleRowTable3]) AS [UnionAll2]
        WHERE ( CAST(CHARINDEX([UnionAll2].[C1], [Extent1].[Name]) AS int)) = 1

O que funciona, mas não é escalonável. O EF core 3 não o suporta e não há solução fácil, como sugerido na outra resposta.

Uma possível solução é construir um predicado com predicados ||(Or), por exemplo:

var pred = letters.Aggregate(PredicateBuilder.False<Carrier>(), 
    (p,x) => p = p.Or(c => c.Name.StartsWith(x)));
var results = db.Carriers.AsNoTracking()
    .Where(pred)
    .ToList();

Onde PredicateBuilderestá um construtor de predicado como o Linqkit ou este . Mas esse método também não é escalonável. EF cria um parâmetro para cada entrada em letters, então você pode atingir o limite de 2100 parâmetros no Sql Server.

1 ArsenKhachaturyan Sep 04 2020 at 19:47

O Entity Framework não permite o uso de predicados personalizados. Essa é a razão pela qual sua consulta está falhando com uma mensagem:

... não pôde ser traduzido. Reescreva a consulta em um formato que possa ser traduzido ...

Então, isso provavelmente significa que sua versão EF não é compatível StartsWithou há alguma combinação, como StartsWithe Anyque não pode ser traduzida.

Uma solução alternativa poderia ser:

  1. Primeiro, você pode filtrar sua consulta da maneira mais próxima, usando o Containsmétodo.
var results = students.Where(s => letters.Any(l => s.name.Contains(l)));

Aqui está um exemplo semelhante que escrevi para o .NET fiddle.


  1. Em seguida, caso você ainda queira usar, StartsWithmas seu EF não suporta, você pode converter sua consulta para a coleção IN-MEMORY:
var results = filter_by_contains_query
              .ToList() // convert filtered result to in-memory collection
              .Where(i => letters.Any(l => i.StudentName.StartsWith(l)));

onde filter_by_contains_queryestá uma consulta da 1ª parte.


Mais:

  • Sobre os métodos suportados pela EF, verifique aqui .
  • Discussões em torno do StartsWith, EndsWithe Contains aqui