Entity Framework, LINQ: Kann Any mit StartsWith nicht verwenden?
Ich habe eine einfache Entity Framework LINQ-Abfrage. Das Ziel ist es, alle Träger zu finden, die mit A, B oder C beginnen:
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();
Ich bekomme
System.InvalidOperationException: 'Der LINQ-Ausdruck' DbSet .Where (c => __letters_0 .Any (val => val == "" || c.Name! = Null && val! = Null && c.Name.StartsWith (val) )) 'konnte nicht übersetzt werden. Schreiben Sie die Abfrage entweder in einer Form um, die übersetzt werden kann, oder wechseln Sie explizit zur Clientbewertung, indem Sie entweder AsEnumerable (), AsAsyncEnumerable (), ToList () oder ToListAsync () aufrufen. Sehenhttps://go.microsoft.com/fwlink/?linkid=2101038 für mehr Informationen.'
Gibt es keine Möglichkeit, dies zu tun?
Antworten
In Entity Framework 6 würde diese Abfrage einwandfrei ausgeführt. EF6 unterstützt die SQL-Übersetzung von StartsWithund die Verwendung lokaler Sequenzen ( letters) in einem Abfrageausdruck.
EF Core 3 (die Version in der Frage, wie in der Ausnahmemeldung angegeben) unterstützt auch die SQL-Übersetzung von StartsWith. Das Problem hier (das die andere Antwort völlig verfehlt) ist, dass die lokale Sequenz auf eine Weise verwendet wird, die nicht unterstützt wird. Eine Abfrage wie ...
var results = db.Carriers.AsNoTracking()
.Where(c => letters.Contains(c.Name))
.ToList();
... würde unterstützt, weil letterseinfach in eine INKlausel übersetzt werden kann . Aber das ist natürlich eine ganz andere Frage.
Für die Verwendung letters.Anymuss EF lettersin "etwas" konvertiert werden, mit dem in SQL verbunden werden kann. EF6 erstellt dazu eine Ergebnismenge in der SQL-Abfrage:
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
Was funktioniert, aber überhaupt nicht skalierbar ist. EF Core 3 unterstützt dies nicht und es gibt keine einfache Umgehung, wie in der anderen Antwort vorgeschlagen.
Eine mögliche Problemumgehung besteht darin, ein Prädikat mit ||(oder) Prädikaten zu erstellen , zum Beispiel:
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();
Wo PredicateBuilderist ein Prädikat Builder wie Linqkit oder diese . Diese Methode ist aber auch nicht skalierbar. EF erstellt für jeden Eintrag in einen Parameter letters, sodass Sie den Schwellenwert für 2100 Parameter in SQL Server erreichen können.
Das Entity Framework erlaubt keine Verwendung von benutzerdefinierten Prädikaten. Das ist der Grund, warum Ihre Abfrage mit einer Nachricht fehlschlägt:
... konnte nicht übersetzt werden. Schreiben Sie die Abfrage entweder in eine Form um, die übersetzt werden kann ...
Also, das höchstwahrscheinlich bedeutet , dass Ihre EF - Version entweder nicht unterstützt StartsWithoder es ist eine Kombination, wie StartsWithund Anydass nicht übersetzt werden kann.
Eine Problemumgehung könnte sein:
- Zunächst können Sie Ihre Abfrage mithilfe der ContainsMethode auf die nächstgelegene Weise filtern .
var results = students.Where(s => letters.Any(l => s.name.Contains(l)));
Hier ist ein ähnliches Beispiel, das ich für .NET Fiddle geschrieben habe.
- Weiter, falls Sie weiterhin verwenden möchten,
StartsWithIhr EF jedoch nicht unterstützt, dass Sie Ihre Abfrage in die IN-MEMORY-Sammlung konvertieren können:
var results = filter_by_contains_query
.ToList() // convert filtered result to in-memory collection
.Where(i => letters.Any(l => i.StudentName.StartsWith(l)));
Wo filter_by_contains_queryist eine Abfrage aus dem 1. Teil.
Mehr:
- Informationen zu von EF unterstützten Methoden finden Sie hier .
- Diskussionen rund um das
StartsWith,EndsWithundContainshier