In meiner Datenbank befinden sich zwei Tabellen " Organizations
und " OrganizationMembers
mit einer 1: N-Beziehung.
Ich möchte eine Abfrage ausdrücken, die jede Organisation mit dem Vor- und Nachnamen des ersten Organisationsbesitzers zurückgibt.
Mein aktueller Auswahlausdruck funktioniert, ist aber weder effizient noch sieht er für mich richtig aus, da jede Unterabfrage mehrmals definiert wird.
await dbContext.Organizations
.AsNoTracking()
.Select(x =>
{
return new OrganizationListItem
{
Id = x.Id,
Name = x.Name,
OwnerFirstName = (x.Members.OrderBy(member => member.CreatedAt).First(member => member.Role == RoleType.Owner)).FirstName,
OwnerLastName = (x.Members.OrderBy(member => member.CreatedAt).First(member => member.Role == RoleType.Owner)).LastName,
OwnerEmailAddress = (x.Members.OrderBy(member => member.CreatedAt).First(member => member.Role == RoleType.Owner)).EmailAddress
};
})
.ToArrayAsync();
Ist es irgendwie möglich, die Unterabfragen zusammenzufassen oder wiederzuverwenden, sodass ich sie nicht mehrmals definieren muss?
Beachten Sie, dass ich bereits versucht habe, das Unterabfrageergebnis in einer Variablen zu speichern. Dies funktioniert nicht, da der Ausdruck in einen Anweisungshauptteil konvertiert werden muss, was zu einem Compilerfehler führt.
Die Unterabfrage kann wiederverwendet werden, indem eine Zwischenprojektion ( Select
) eingeführt wird, die dem let
Operator in der Abfragesyntax entspricht.
Zum Beispiel:
dbContext.Organizations.AsNoTracking()
// intermediate projection
.Select(x => new
{
Organization = x,
Owner = x.Members
.Where(member => member.Role == RoleType.Owner)
.OrderBy(member => member.CreatedAt)
.FirstOrDefault()
})
// final projection
.Select(x => new OrganizationListItem
{
Id = x.Organization.Id,
Name = x.Organization.Name,
OwnerFirstName = Owner.FirstName,
OwnerLastName = Owner.LastName,
OwnerEmailAddress = Owner.EmailAddress
})
Beachten Sie, dass Sie in Vorgängerversionen von EF Core 3.0 FirstOrDefault
anstelle von First
verwenden FirstOrDefault
, um eine Client-Evaluierung zu vermeiden.
Auch dies verbessert / beschleunigt die generierte SQL-Abfrage nicht - es enthält immer noch eine separate Inline-Unterabfrage für jede Eigenschaft, die in der endgültigen Auswahl enthalten ist. Daher wird die Lesbarkeit verbessert, aber nicht die Effizienz.
Deshalb ist es in der Regel besser verschachteltes Objekt in abgeflachte DTO Eigenschaft zu projizieren, dh anstelle von OwnerFirstName
, OwnerLastName
, OwnerEmailAddress
hat eine Klasse mit Eigenschaften FirstName
, LastName
, EmailAddress
und Eigentum Lassen Sie sagen , Owner
dieser Art in OrganizationListItem
(ähnlich Einheit mit Bezug Navigationseigenschaft ). Auf diese Weise können Sie so etwas verwenden
dbContext.Organizations.AsNoTracking()
.Select(x => new
{
Id = x.Organization.Id,
Name = x.Organization.Name,
Owner = x.Members
.Where(member => member.Role == RoleType.Owner)
.OrderBy(member => member.CreatedAt)
.Select(member => new OwnerInfo // the new class
{
FirstName = member.FirstName,
LastName = member.LastName,
EmailAddress = member.EmailAddress
})
.FirstOrDefault()
})
Leider generiert EF Core in Versionen vor 3.0 N + 1-SQL-Abfragen für diese LINQ-Abfrage, aber in Version 3.0+ wird eine einzelne und recht effiziente SQL-Abfrage generiert.
Wie wäre es so
await dbContext.Organizations
.AsNoTracking()
.Select(x => new OrganizationListItem
{
Id = x.Id,
Name = x.Name,
OwnerFirstName = x.Members.FirstOrDefault(member => member.Role == RoleType.Owner).FirstName,
OwnerLastName = x.Members.FirstOrDefault(member => member.Role == RoleType.Owner)).LastName,
OwnerEmailAddress = x.Members.FirstOrDefault(member => member.Role == RoleType.Owner)).EmailAddress
})
.ToArrayAsync();