none
LINQ-Abfrage mit Where-Bedingung bei Include einer Trackable-Collection.... RRS feed

  • Frage

  • Hallo

    ich habe eine LINQ-Abfrage (LINQ-to-Entities) mit einer Where-Bedingung, die ich zur Laufzeit zusammensetze; jetzt ist noch ein weitere Include einer Entität hinzugekommen, dass eine Trackable-collection liefert. Von dieser möchte ich aber nur eine bestimme Art von Datensatz haben. Ist es möglich diese in der Where-Bedingung zu definieren?

    Hier mal mein LINQ-statement:

     IEnumerable<TerminWarteliste> query = this.GetBaseQuery().Include("TerminWartelisteKategorie")
                                      .Include("TerminWarteliste_PatFall")
                                      .Include("TerminWarteliste_PatFall.Fall_Patient")
                                      .Include("TerminWarteliste_PatFall.Fall_Patient.HauptAdresse")
                                      .Include("TerminWarteliste_PatFall.FallTexte");
          IQueryable<TerminWarteliste> whereQuery = query.AsQueryable();
    
          if (kategoriePrimaryKey != null)
            whereQuery = whereQuery.Where(p => p.Kategorie == kategoriePrimaryKey).AsQueryable();
          if (!string.IsNullOrEmpty(dringlichkeitsKategorie))
            whereQuery = whereQuery.Where(p => p.DringlichkeitsKategorie == dringlichkeitsKategorie).AsQueryable();
          if(!string.IsNullOrEmpty(ambulantStationaer))
            whereQuery = whereQuery.Where(p => p.TerminWarteliste_PatFall.FallArt == ambulantStationaer).AsQueryable();
          if(erledigt != null)
            whereQuery = whereQuery.Where(p => p.Erledigt == erledigt).AsQueryable();
    
          whereQuery = whereQuery.Where(p => p.TerminWarteliste_PatFall.FallImPool == false).AsQueryable();
    
          whereQuery = whereQuery.Where(p => p.TerminWarteliste_PatFall.FallTexte.Where(x => x.TextArt == diagnoseTextArt)).AsQueryable();
    
    
    

    es geht um den letzten Include("TerminWarteliste_PatFall.FallTexte") - von diesen sogenannten Fall-Texten möchte ich nur eine bestimmte Textart geliefert bekommen - dies hätte ich mit dem letzten whereQuere-statement verusucht, in der trackable-collection nochmals einzugrenzen.........aber so funktioniert das nicht.

    Wie kann ich denn bei dieser Art von LINQ-Abfrage auf einer Trackable-Collection nochmals eine Einschränkung treffen ??

    Kann mir dazu bitte jemand weiterhelfen ??

    Danke schon mal & schönen Gruß

    Michael


    Michael Erlinger
    Donnerstag, 24. Februar 2011 08:47

Antworten

  • Hallo Michael,

    Es geht um den letzten Include("TerminWarteliste_PatFall.FallTexte") - von diesen sogenannten Fall-Texten möchte ich nur eine bestimmte Textart geliefert bekommen.

    Mit Include() erreichst Du ein eager loading der assoziierten Entities, nichts mehr und nichts weniger. Das bedeutet aber auch dass Du immer ALLE Listenelemente einer assoziierten 1:N-Beziehung mit der Entity zurückbekommen wirst. Alles andere würde den Objektgraphen in einen ungültigen Zustand versetzen.

    In Deiner dynamischen Abfrage kannst Du also mittels Where-Abfrage höchstens erreichen, dass eine Untermenge aller TerminWarteliste-Entities zurückgegeben wird (unter Verwendung von Any()), aber nie eine Entity mit gefilterter Navigations-Eigenschaft:

    whereQuery = whereQuery.Where(p => p.TerminWarteliste_PatFall.FallTexte.Any(x => x.TextArt == diagnoseTextArt)).AsQueryable();

    Diese Abfrage würde also alle TerminWarteliste-Instanzen zurückgeben die mindestens ein FallText enthalten, auf den diagnoseTextArt zutrifft.

    Hier ein Northwind-basiertes Beispiel, das die Grenzen von EF etwas forciert, indem zunächst anonym projiziert, dann das Ergebnis materialisiert und als IQueryable<Orders> zurückgegeben wird:

    var query = (from o in context.Orders
    select new
    {
    Order = o,
    Order_Details = o.Order_Details.Where(d => d.Quantity > 22)
    });
    
    var materialisiert = query.AsEnumerable();
    var ergebnis = materialisiert.Select(anon => anon.Order).AsQueryable();
    

    Beachte bitte, dass die Ergebnisauflistung Orders für Artikel mit Quantität > 22 enthält sowie Artikel mit leerer Order_Details-Auflistung dort, wo keines der Artikel eine Bestellmenge > 22 aufwies.

    [Willst Du das noch mehr eingrenzen, so helfen nur Projektionen und DTOs weiter. Damit wären wir aber beim manuellen change tracking und die Wirkung von Include() ist gleich null sobald Projektionen verwendet werden.]

    Du könntest aber auch erwägen, ob Du nicht lieber IQueryable<FallTexte> statt IQueryable<TerminWarteliste> zurückgeben möchtest. Schließlich kannst Du, bei einem gut designten Modell über die Properties in alle Richtungen navigieren. Nur die Form (shape) der Daten wäre eine andere.

    In Zusammenhang mit Silverlight-Anwendungen sind weitere Optionen möglich.

    s.a. Update relationships when saving changes of EF4 POCO objects:
    http://stackoverflow.com/questions/3635071/update-relationships-when-saving-changes-of-ef4-poco-objects

    Using self tracking entities with Silverlight 4 and Entity Framework 4:
    http://blogs.infosupport.com/blogs/alexb/archive/2010/08/24/using-self-tracking-entities-with-silverlight-4-and-entity-framework-4.aspx

    Gruss
    Marcel

    Donnerstag, 24. Februar 2011 13:39
    Moderator
  • Hallo Michael,

    Natürlich könnte man auch JOIN verwenden. Rein technisch gesehen ist es möglich (und für den SQL-Programmierer sicherlich sehr geläufig):

    var query = 
     from o in context.Orders.Include("Orders.Order_Details")
     join d in Order_Details
     on o.OrderID equals d.OrderID
     where d.Quantity > 10
     select o;
     
    foreach(var order in query)
    {
     order.Order_Details.Load();
     // order.Order_Details verwenden ...
    }
    
    
    

    (mit vielen Spielarten...)

    Aber schau Dir das bitte genauer an. Was siehst Du? Obwohl wir Include verwenden, müssen die Details manuell nachgeladen werden (über zusätzliche Roundtrips zur Datenbank, und in Deinem Fall wären es einige mehr...).

    Man könnte alternativ eine nested query verwenden, evtl. mit Projektion in einen anonymen Typ und evtl. mit impliziter Rückkonversion zu Orders. Aber löst dies das grundsätzliche Problem? Nein, das grundsätzliche Problem liegt in der Art und Weise wie das Model strukturiert wurde. Wenn "FallText (mit bestimmter TextArt) optional sind", dann sollten sie auch eine eigene Entität darstellen (in Unkenntnis der Domäne kann ich natürlich nicht mehr dazu schreiben).

    Wie sagte Zlatko Michailov in seinem Blog so passend?

    "A well defined query against a well defined entity data model does not need JOIN."

    http://blogs.msdn.com/b/esql/archive/2007/11/01/entitysql_5f00_tip_5f00_1.aspx

    Gruss
    Marcel

    • Als Antwort markiert M.Erlinger Montag, 7. März 2011 10:02
    Sonntag, 27. Februar 2011 11:38
    Moderator

Alle Antworten

  • Hallo Michael,

    Es geht um den letzten Include("TerminWarteliste_PatFall.FallTexte") - von diesen sogenannten Fall-Texten möchte ich nur eine bestimmte Textart geliefert bekommen.

    Mit Include() erreichst Du ein eager loading der assoziierten Entities, nichts mehr und nichts weniger. Das bedeutet aber auch dass Du immer ALLE Listenelemente einer assoziierten 1:N-Beziehung mit der Entity zurückbekommen wirst. Alles andere würde den Objektgraphen in einen ungültigen Zustand versetzen.

    In Deiner dynamischen Abfrage kannst Du also mittels Where-Abfrage höchstens erreichen, dass eine Untermenge aller TerminWarteliste-Entities zurückgegeben wird (unter Verwendung von Any()), aber nie eine Entity mit gefilterter Navigations-Eigenschaft:

    whereQuery = whereQuery.Where(p => p.TerminWarteliste_PatFall.FallTexte.Any(x => x.TextArt == diagnoseTextArt)).AsQueryable();

    Diese Abfrage würde also alle TerminWarteliste-Instanzen zurückgeben die mindestens ein FallText enthalten, auf den diagnoseTextArt zutrifft.

    Hier ein Northwind-basiertes Beispiel, das die Grenzen von EF etwas forciert, indem zunächst anonym projiziert, dann das Ergebnis materialisiert und als IQueryable<Orders> zurückgegeben wird:

    var query = (from o in context.Orders
    select new
    {
    Order = o,
    Order_Details = o.Order_Details.Where(d => d.Quantity > 22)
    });
    
    var materialisiert = query.AsEnumerable();
    var ergebnis = materialisiert.Select(anon => anon.Order).AsQueryable();
    

    Beachte bitte, dass die Ergebnisauflistung Orders für Artikel mit Quantität > 22 enthält sowie Artikel mit leerer Order_Details-Auflistung dort, wo keines der Artikel eine Bestellmenge > 22 aufwies.

    [Willst Du das noch mehr eingrenzen, so helfen nur Projektionen und DTOs weiter. Damit wären wir aber beim manuellen change tracking und die Wirkung von Include() ist gleich null sobald Projektionen verwendet werden.]

    Du könntest aber auch erwägen, ob Du nicht lieber IQueryable<FallTexte> statt IQueryable<TerminWarteliste> zurückgeben möchtest. Schließlich kannst Du, bei einem gut designten Modell über die Properties in alle Richtungen navigieren. Nur die Form (shape) der Daten wäre eine andere.

    In Zusammenhang mit Silverlight-Anwendungen sind weitere Optionen möglich.

    s.a. Update relationships when saving changes of EF4 POCO objects:
    http://stackoverflow.com/questions/3635071/update-relationships-when-saving-changes-of-ef4-poco-objects

    Using self tracking entities with Silverlight 4 and Entity Framework 4:
    http://blogs.infosupport.com/blogs/alexb/archive/2010/08/24/using-self-tracking-entities-with-silverlight-4-and-entity-framework-4.aspx

    Gruss
    Marcel

    Donnerstag, 24. Februar 2011 13:39
    Moderator
  • Hallo Marcel

    vielen Dank für Deine Rückmeldung!!

    Noch eine Frage nachträglich - wäre es nicht möglich via JOIN() in dem LINQ-Beispiel von mir zu arbeiten ?? d.h. einen outer-Join auf meine "FallText"-Entität, wo dann nur eine bestimmte TextArt selektiert wird.......

    Zu Deinem Vorschlag über die  IQueryable<FallTexte> zu gehen - dies hätte ich probiert, aber leider kann ich das nicht anwenden, weil FallText (mit bestimmter TextArt) optional sind; und dann würden für die Liste Daten fehlen.

    Schönen Gruß

    Michael


    Michael Erlinger
    • Bearbeitet M.Erlinger Samstag, 26. Februar 2011 20:48 Zusatz
    Samstag, 26. Februar 2011 20:31
  • Hallo Michael,

    Natürlich könnte man auch JOIN verwenden. Rein technisch gesehen ist es möglich (und für den SQL-Programmierer sicherlich sehr geläufig):

    var query = 
     from o in context.Orders.Include("Orders.Order_Details")
     join d in Order_Details
     on o.OrderID equals d.OrderID
     where d.Quantity > 10
     select o;
     
    foreach(var order in query)
    {
     order.Order_Details.Load();
     // order.Order_Details verwenden ...
    }
    
    
    

    (mit vielen Spielarten...)

    Aber schau Dir das bitte genauer an. Was siehst Du? Obwohl wir Include verwenden, müssen die Details manuell nachgeladen werden (über zusätzliche Roundtrips zur Datenbank, und in Deinem Fall wären es einige mehr...).

    Man könnte alternativ eine nested query verwenden, evtl. mit Projektion in einen anonymen Typ und evtl. mit impliziter Rückkonversion zu Orders. Aber löst dies das grundsätzliche Problem? Nein, das grundsätzliche Problem liegt in der Art und Weise wie das Model strukturiert wurde. Wenn "FallText (mit bestimmter TextArt) optional sind", dann sollten sie auch eine eigene Entität darstellen (in Unkenntnis der Domäne kann ich natürlich nicht mehr dazu schreiben).

    Wie sagte Zlatko Michailov in seinem Blog so passend?

    "A well defined query against a well defined entity data model does not need JOIN."

    http://blogs.msdn.com/b/esql/archive/2007/11/01/entitysql_5f00_tip_5f00_1.aspx

    Gruss
    Marcel

    • Als Antwort markiert M.Erlinger Montag, 7. März 2011 10:02
    Sonntag, 27. Februar 2011 11:38
    Moderator
  • Vielen Dank für Deine Infos und Unterstützung!!

    Schönen Gruß

    Michael


    Michael Erlinger
    Montag, 7. März 2011 10:02