Entity Framework, LINQ: non è possibile utilizzare Any con StartsWith?
Ho una semplice query LINQ di Entity Framework. L'obiettivo è trovare tutti i vettori che iniziano con A, B o 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();
ottengo
System.InvalidOperationException: 'The LINQ expression' DbSet .Where (c => __letters_0 .Any (val => val == "" || c.Name! = Null && val! = Null && c.Name.StartsWith (val) )) "non può essere tradotto. Riscrivi la query in una forma che può essere tradotta o passa alla valutazione del client in modo esplicito inserendo una chiamata a AsEnumerable (), AsAsyncEnumerable (), ToList () o ToListAsync (). Vederehttps://go.microsoft.com/fwlink/?linkid=2101038 per maggiori informazioni.'
Non c'è modo per farlo?
Risposte
In Entity Framework 6 questa query verrebbe eseguita correttamente. EF6 supporta la traduzione SQL di StartsWithe supporta l'utilizzo di sequenze locali ( letters) in un'espressione di query.
EF core 3 (la versione nella domanda come indica il messaggio di eccezione) supporta anche la traduzione SQL di StartsWith. Il problema qui (che l'altra risposta manca completamente) è che la sequenza locale viene utilizzata in un modo che non è supportato. Una query come ...
var results = db.Carriers.AsNoTracking()
.Where(c => letters.Contains(c.Name))
.ToList();
... sarebbe supportato perché letterspuò essere semplicemente tradotto in una INclausola. Ma ovviamente è una domanda completamente diversa.
L'utilizzo letters.Anyrichiede EF per la conversione lettersin "qualcosa" che può essere unito in SQL. EF6 lo fa creando un set di risultati nella query 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
Che funziona, ma non è affatto scalabile. EF core 3 non lo supporta e non c'è una soluzione facile come suggerito nell'altra risposta.
Una possibile soluzione consiste nel creare un predicato con predicati ||(Or), ad esempio:
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();
Dov'è PredicateBuilderun generatore di predicati come Linqkit o questo . Ma anche questo metodo non è scalabile. EF crea un parametro per ogni voce in letters, quindi potresti raggiungere la soglia di 2100 parametri in Sql Server.
Entity Framework non consente l'utilizzo di predicati personalizzati. Questo è il motivo per cui la tua query non riesce con un messaggio:
... non può essere tradotto. O riscrivi la query in una forma che può essere tradotta ...
Quindi, questo molto probabilmente significa che la tua versione EF non supporta StartsWitho c'è una combinazione, come StartsWithe Anyche non può essere tradotta.
Una soluzione alternativa potrebbe essere:
- Innanzitutto, potresti filtrare la tua query nel modo più vicino, utilizzando il Containsmetodo.
var results = students.Where(s => letters.Any(l => s.name.Contains(l)));
Ecco un esempio simile che ho scritto per .NET fiddle.
- Avanti nel caso in cui desideri ancora utilizzare
StartsWithma il tuo EF non supporta la conversione della query in raccolta 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)));
dove filter_by_contains_queryè una query dalla prima parte.
Di Più:
- Informazioni sui metodi supportati da EF controllare qui .
- Discussioni intorno a
StartsWith,EndsWitheContainsqui