wake-up-neo.com

Entity Framework: Abfragen von untergeordneten Entitäten

Ich lerne über Entity Framework auf der mo, und habe Probleme!

Kann jemand klarstellen, ob ich zu Recht denke, dass ich keine Eltern und eine Untergruppe ihrer Kinder von der Datenbank bekommen kann?

Beispielsweise...

db.Parents
.Include(p => p.Children)
.Where(p => p.Children.Any(c => c.Age >= 5))

Dies gibt alle Eltern zurück, die ein Kind ab 5 Jahren haben. Wenn ich jedoch die Parents.Children-Sammlung durchlaufe, sind alle Kinder anwesend (nicht nur die über 5 Jahre).

Jetzt macht die Abfrage für mich Sinn (ich habe darum gebeten, Kinder einzuschließen, und ich habe sie!), Kann mir aber vorstellen, dass ich in einigen Szenarien die where-Klausel auf die Child-Auflistung anwenden möchte.

Fragen:

  1. Ist das, was ich gesagt habe, richtig?
  2. Ist es möglich, die Eltern und nur eine Teilmenge von der Datenbank abzurufen, ohne viele Anrufe bei der Datenbank zu tätigen?
  3. Bin ich weit weg von der Marke? (Wäre nicht das 1. Mal) !!!!

Ich habe ein paar Blogs und SO Posts gefunden, die sich auf das Thema beziehen, aber nichts, was es meinem kleinen Gehirn gut genug erklärt.

[~ # ~] edit [~ # ~]

Nachdem ich das gelesen habe Blog (danke Daz Lewis) ....... Ich verstehe es immer noch nicht !!!

In dem Beispiel im Blog kann ich sehen, wie ich es mit einer einzelnen Instanz von Parent erreichen kann, aber ich habe Mühe, herauszufinden, wie ich es mit einer Sammlung machen kann.

Wie kann ich eine IEnumerable erhalten, in der jeder Elternteil eine gefilterte Sammlung von Kindern hat (Alter> = 5)?

Weitere Klarstellung:

Als Antwort auf DonAndres Kommentar bin ich nach a) einer Liste von Eltern, die ein Kind haben, das älter als 5 Jahre ist (und nur diese Kinder enthalten).

Jede Hilfe geschätzt,

Vielen Dank.

44
ETFairfax

Die einzige Möglichkeit, eine Sammlung von Eltern mit einer gefilterten Kindersammlung in einer einzelnen Datenbankrundreise abzurufen, ist die Verwendung einer Projektion. Es ist nicht möglich, das eifrige Laden (Include) zu verwenden, da das Filtern nicht unterstützt wird. Include lädt immer die gesamte Sammlung. Die von @Daz angezeigte Methode zum expliziten Laden erfordert eine Hin- und Rückfahrt pro übergeordneter Entität.

Beispiel:

var result = db.Parents
    .Select(p => new
    {
        Parent = p,
        Children = p.Children.Where(c => c.Age >= 5)
    })
    .ToList();

Sie können direkt mit dieser Sammlung anonymer Typobjekte arbeiten. (Sie können anstelle einer anonymen Projektion auch in Ihren eigenen benannten Typ projizieren (jedoch nicht in eine Entität wie Parent).)

Der Kontext von EF füllt auch die Children -Auflistung von Parent automatisch, wenn Sie die Änderungsnachverfolgung nicht deaktivieren (z. B. mit AsNoTracking()). In diesem Fall können Sie das übergeordnete Element aus dem anonymen Ergebnistyp projizieren (geschieht im Speicher, keine DB-Abfrage):

var parents = result.Select(a => a.Parent).ToList();

parents[i].Children Enthält Ihre gefilterten Kinder für jedes Parent.


Bearbeiten zu deiner letzten Bearbeitung in der Frage:

Ich bin nach a) einer Liste von Eltern, die ein Kind haben, das älter als 5 Jahre ist (und nur diese Kinder einschließen).

Der obige Code würde alle Eltern zurückgeben und nur die Kinder mit Age> = 5 einschließen, also möglicherweise auch Eltern mit einer leeren Kindersammlung Wenn es nur Kinder mit Age <5 gibt. Sie können diese mit einer zusätzlichen Where -Klausel herausfiltern, damit die Eltern nur die Eltern erhalten, die als haben Mindestens ein (Any) Kind mit Age> = 5:

var result = db.Parents
    .Where(p => p.Children.Any(c => c.Age >= 5))
    .Select(p => new
    {
        Parent = p,
        Children = p.Children.Where(c => c.Age >= 5)
    })
    .ToList();
47
Slauma

Wenn Sie Ihr Beispiel nehmen, sollte das Folgende tun, was Sie brauchen. Schauen Sie sich hier um mehr Informationen zu erhalten.

db.Entry(Parents)
.Collection("Children")
.Query().Cast<Child>()
.Where(c => c.Age >= 5))
.Load();
3
Darren Lewis

Ich denke, Eltern und Kind sind als getrennte Einheiten nicht wirklich gut geeignet. Ein Kind kann immer auch ein Elternteil sein und normalerweise hat ein Kind zwei Elternteile (einen Vater und eine Mutter), es ist also nicht der einfachste Kontext. Aber ich gehe davon aus, dass Sie nur eine einfache 1: n-Beziehung haben, wie im folgenden Master-Slave-Modell, das ich verwendet habe.

Was Sie tun müssen, ist ein linker äußerer Join (diese Antwort hat mich auf den richtigen Weg geführt). Ein solcher Join ist etwas schwierig, aber hier ist der Code

var query = from m in ctx.Masters
            join s in ctx.Slaves
              on m.MasterId equals s.MasterId into masterSlaves
            from ms in masterSlaves.Where(x => x.Age > 5).DefaultIfEmpty()
            select new {
              Master = m,
              Slave = ms
            };

foreach (var item in query) {
  if (item.Slave == null) Console.WriteLine("{0} owns nobody.", item.Master.Name);
  else Console.WriteLine("{0} owns {1} at age {2}.", item.Master.Name, item.Slave.Name, item.Slave.Age);
}

Dies wird mit EF 4.1 in die folgende SQL-Anweisung übersetzt

SELECT 
[Extent1].[MasterId] AS [MasterId], 
[Extent1].[Name] AS [Name], 
[Extent2].[SlaveId] AS [SlaveId], 
[Extent2].[MasterId] AS [MasterId1], 
[Extent2].[Name] AS [Name1], 
[Extent2].[Age] AS [Age]
FROM  [dbo].[Master] AS [Extent1]
LEFT OUTER JOIN [dbo].[Slave] AS [Extent2]
ON ([Extent1].[MasterId] = [Extent2].[MasterId]) AND ([Extent2].[Age] > 5)

Beachten Sie, dass es wichtig ist, die zusätzliche where-Klausel zum Alter der verknüpften Sammlung auszuführen und nicht zwischen from und select.

BEARBEITEN:

WENN Sie ein hierarchisches Ergebnis wünschen, können Sie die flache Liste konvertieren, indem Sie eine Gruppierung durchführen:

var hierarchical = from line in query
                   group line by line.Master into grouped
                   select new { Master = grouped.Key, Slaves = grouped.Select(x => x.Slave).Where(x => x != null) };

foreach (var elem in hierarchical) {
   Master master = elem.Master;
   Console.WriteLine("{0}:", master.Name);
   foreach (var s in elem.Slaves) // note that it says elem.Slaves not master.Slaves here!
     Console.WriteLine("{0} at {1}", s.Name, s.Age);
}

Beachten Sie, dass ich einen anonymen Typ zum Speichern des hierarchischen Ergebnisses verwendet habe. Sie können natürlich auch einen bestimmten Typ wie diesen erstellen

class FilteredResult {
  public Master Master { get; set; }
  public IEnumerable<Slave> Slaves { get; set; }
}

und dann projizieren Sie die Gruppe in Instanzen dieser Klasse. Das macht es einfacher, wenn Sie diese Ergebnisse an andere Methoden übergeben müssen.

2
Andreas