Ich versuche, eine Abfrage mit EF Core 3.1 über mehrere Tabellen hinweg zu erstellen, und es funktioniert einfach nicht.
Ich werde versuchen, mit einigen Dummy-Beispielen zu erklären.
Angenommen, in meinen SQL-Tabellen ist in der SQL Server-Datenbank Folgendes definiert:
Die Dummy-Entitäten lauten wie folgt:
Fremdschlüsselbeziehungen:
Für alle Entitäten sind die Navigationseigenschaften festgelegt, und ich habe den Primär- und den Fremdschlüssel im DB-Kontext eingerichtet.
Die überwiegende Mehrheit dieser Tabellen verfügt über ein Bitfeld "Aktiviert", sodass Zeilen deaktiviert werden können, ohne sie zu löschen
Die Abfrage, die ich zu schreiben versuche, ähnelt der folgenden:
var data = await context.Town.AsNoTracking()
.Where(t => t.TownName == request.TownName)
.Include(t => t.Inhabitants.Where(i => i.Name == request.InhabitantName && i.Enabled)
.ThenInclude(i => i.InhabitantCar.Where(ic => ic.Enabled))
.ThenInclude(ic => ic.Cars.Where(c => c.Enabled))
.ThenInclude(c => c.Manufacturer.Where(m => m.Enabled))
.Include(t => t.Inhabitants.Where(i => i.Name == request.InhabitantName && i.Enabled)
.ThenInclude(i => i.InhabitantCar.Where(ic => ic.Enabled))
.ThenInclude(ic => ic.Cars.Where(c => c.Enabled))
.ThenInclude(c => c.Mechanic.Where(m => m.Enabled && m.Name == request.AllowedMechanic))
.ToListAsync().ConfigureAwait(false);
Zusammenfassend möchte ich wissen, welche Autos von einem "John Smith" gefahren werden, der in "London" lebt und von "MechanicsAreUs" gewartet wird.
Das scheint mir ziemlich langwierig zu sein, und dort könnte mein Problem liegen.
Wie auch immer, einige der .WHERE
Klauseln zu letzteren ThenIncludes werden einfach nicht kompiliert. Wenn ich sie einzeln entferne, bis sie kompiliert sind, habe ich folgende Möglichkeiten:
var data = await context.Town.AsNoTracking()
.Where(t => t.TownName == request.TownName)
.Include(t => t.Inhabitants.Where(i => i.Name == request.InhabitantName && i.Enabled)
.ThenInclude(i => i.InhabitantCar.Where(ic => ic.Enabled))
.ThenInclude(ic => ic.Cars)
.ThenInclude(c => c.Manufacturer)
.Include(t => t.Inhabitants.Where(i => i.Name == request.InhabitantName && i.Enabled)
.ThenInclude(i => i.InhabitantCar.Where(ic => ic.Enabled))
.ThenInclude(ic => ic.Cars)
.ThenInclude(c => c.Mechanic)
.ToListAsync().ConfigureAwait(false);
Wie geschrieben, werden deaktivierte Einträge zurückgebracht, und ich gebe den Mechaniker nicht an. Wenn ich es jedoch ausführe, erhalte ich die Ausnahme:
System.InvalidOperationException: Der in Include verwendete Lambda-Ausdruck ist ungültig.
Ich habe lange Zeit die verschiedenen Microsoft-Beispiele durchgesehen, aber ich finde keine Beispiele, die von dieser Komplexität zu sein scheinen. Es sind nur eine Handvoll innerer Verbindungen. Etwas, das in einer gespeicherten Prozedur innerhalb von Minuten erreicht werden könnte. Nur dass ich dies mit Entity Framework tun möchte.
Sie können nicht filtern .Include(...)
eifrige Lasten .Include(...)
- es ist alles oder nichts. Wie David Browne in einem Kommentar zu Ihrer Frage angegeben hat, sollten Sie Abfragefilter verwenden, wenn Sie Datensätze aufgrund ihrer Enabled
Flags ausschließen möchten, z.
modelBuilder.Entity<Car>()
.HasQueryFilter(c => c.Enabled);
Ich glaube, Sie interessieren sich für Car
Entitäten. Lassen Sie uns also die Abfrage umstrukturieren, damit der Fokus:
var query = context.Cars;
Sie möchten Autos, die einem Inhabitant
mit einem bestimmten Namen zugeordnet sind, der einer bestimmten Town
aber auch von einem bestimmten Mechanic
gewartet wird. Filtern wir also nach diesen Kriterien:
query = query.Where( c =>
c.InhabitantCar.Inhabitant.Name == request.InhabitantName
&& c.InhabitantCar.Inhabitant.Town.TownName == request.TownName
&& c.Mechanic == request.AllowedMechanic );
Diese Abfrage gibt nun die gewünschten Car
Entitäten zurück. Konfigurieren Sie nun die eifrigen Lasten:
query = query.Include( c => c.Manufacturer )
.Include( c => c.Mechanic )
.Include( c => c.InhabitantCar )
.ThenInclude( ic => ic.Inhabitant )
.ThenInclude( i => i.Town );
Probieren Sie es aus.
Eine Empfehlung war die Verwendung von Abfragefiltern.
Die Idee dahinter war großartig - in meiner DB-Kontextdatei konnte ich einen gemeinsamen Satz von Filtern hinzufügen, z
builder.Entity<Town>()
.HasQueryFilter(a => a.Enabled);
builder.Entity<Car>()
.HasQueryFilter(a => a.Enabled);
builder.Entity<Manufacturer>()
.HasQueryFilter(a => a.Enabled);
Und das wäre in jeder Abfrage enthalten, die von meiner Service-Datei generiert wird - die Entwickler müssen sich nicht darum kümmern.
Bei der Analyse des resultierenden SQL stellte ich jedoch fest, dass mein Code mit mehreren Unterabfragen übersät war, z
Inner Join (Select...Where ...Enabled = 1)
Das Entfernen dieser zentralisierten Abfragefilter und das Hinzufügen dieser zu meiner WHERE-Klausel in der LINQ-Anweisung führte zu einer weitaus effizienteren Abfrage.