Casablanca: Linq und Include

Hallo Forum,

ich komme einfach nicht dahinter, wie Include bei Linq funktioniert. Ich z.B. drei Tabellen wie folgt:

Tablelle A mit Feldern:
ID und Name

Tablelle B mit Feldern:
ID, AID und Name

Tablelle C mit Feldern:
ID, BID und Name

Mit join kann man die Abfrage so gestallten:

from a in A
join b in B on b.AID equals a.ID
join c in C on c.BID equals b.ID
select a.Name;

oder

from aa in A
from bb in B.Where(b => b.AID == aa.ID)
from cc in C.Where(c => c.BID == bb.ID)
select aa.Name;

Wie kann man dieses Beispiel mit Include umsetzen?

Danke im Voraus.

  1. Tach!

    ich komme einfach nicht dahinter, wie Include bei Linq funktioniert.

    Include ist kein LINQ-Feature und hat keine Entsprechung in der LINQ-Syntax (from f in foo where... select f). Die Funktion gibt es aber zum Beispiel im Entity Framework. Dort kannst du es über funktionsbasierte Abfrageweise (context.foo.Include(...).Where(...)) aufrufen.

    Du hast eine Beziehung zwischen zwei Tabellen und willst hauptsächlich die Daten einer Tabelle und über die Beziehung auch auf die Daten der anderen Tabelle zugreifen. Das ist das hauptsächliche Einsatzszenario. Include() sorgt lediglich dafür, dass im Hintergrund die Daten der anderen Tabelle gleich mitgeladen werden. Ob dabei ein Join ausgeführt wird, interessiert an der Stelle aus der Sicht des .NET-Programmierers nicht. Eine zweite Option wäre LazyLoad, dann werden die Daten erst beim Zugriff ermittelt, und dann aber auch in Einzelabfrage. Das lohnt nur bei wenigen Zugriffen. Wenn bei allen Datensätzen die Beziehungsdaten benötigt werden, nimmst du das Include() und gibst einfach nur den Tabellennamen an. Was dann intern wie gejoint werden muss, weiß das EF von selbst anhand der vorher festgelegten (oder aus den Fremdschlüsseln der Datenbank ermittelten) Beziehungen. In neueren Versionen muss man für den Tabellennamen allerdings keinen String mehr nehmen, sondern kann einen Lambda-Ausdruck verwenden. Das hat gegenüber dem String den Vorteil, dass der Compiler gleich mitkontrolliert, dass es den Tabellennamen auch gibt.

    dedlfix.

    1. Hi,

      vielen Dank für deine Antwort.
      Ich habe versucht, die gejointe Anweisung mit Include umzuschreiben. Das Problem dabei ist, dass z.B. Tabelle A keine Ahnung von der Tabelle C hat und kennt nur Tabelle B. B kennt aber A und C. Wenn ich nun eine Anweisung schreiben möchte, in der ich die Daten von B und C über A mitgeliefert bekommen will, gelingt mir nicht. Muss man bei einer includierten Anweisung nicht alle Eigenschften der 3 Tabellen im Eingenschaftenfensterchen (intellisense) zu gesicht bekommen? Wie kann ich sonst z.B. sagen "Alle Daten aus A wenn ID bei B gleich BID in C ist und C.Name = "meinName" ist, wenn ich nicht direkt an C daran komme? Ich habe folgendes versucht:

        
      var lingQuery = from aa in A.Include(a => a.B).Include(b => b.B.Select(c => c.C))  
      
      

      ich erwarte an dieser Stelle, dass ich so etwas schreiben kann:

        
      where aa.ID (in B) == aa.BID (in C)  
      
      

      Ich bekomme aber immer noch nur die Eigenschften der Tabelle A. Es ist klar, dass ich an dieser Stelle einen Denkfehler habe. Wie kann dann sonst via Include eine Bedingung formulieren? Oder ist an dieser Stelle join besser?

      Gruß

      1. Tach!

        Ich habe versucht, die gejointe Anweisung mit Include umzuschreiben. Das Problem dabei ist, dass z.B. Tabelle A keine Ahnung von der Tabelle C hat und kennt nur Tabelle B. B kennt aber A und C.

        Soweit kein Problem, man kann/muss sich dann von A über B nach C durchhangeln, über die Navigationseigenschaften.

        Wenn ich nun eine Anweisung schreiben möchte, in der ich die Daten von B und C über A mitgeliefert bekommen will, gelingt mir nicht. Muss man bei einer includierten Anweisung nicht alle Eigenschften der 3 Tabellen im Eingenschaftenfensterchen (intellisense) zu gesicht bekommen?

        Es werden dabei keine Klassen erweitert. Du greifst weiterhin über die Navigationseigenschaften auf die verbundenen Objekte zu.

        Wie kann ich sonst z.B. sagen "Alle Daten aus A wenn ID bei B gleich BID in C ist und C.Name = "meinName" ist, wenn ich nicht direkt an C daran komme? Ich habe folgendes versucht:

        var lingQuery = from aa in A.Include(a => a.B).Include(b => b.B.Select(c => c.C))

          
        Daraus entnehme ich, dass zu einem A mehrere B existieren können, und jedes B mindestens ein C haben kann, oder auch mehrere, das lässt sich nicht genau aus dem Beispiel erkennen. "Falsch" ist nur das kleine b, denn der Parameter ist vom Typ A, und auch c, denn das ist B. Halt mal die Maus drauf. Du kannst ja deine Variablen benennen wie du möchtest, aber etwas irreführend ist es im Moment (selbst wenn es nur Beispielnamen sind).  
          
        
        > ich erwarte an dieser Stelle, dass ich so etwas schreiben kann:  
        >   
        > ~~~
          
        
        > where aa.ID (in B) == aa.BID (in C)  
        > 
        
        

        Dann hast du falsche Erwartungen. In deinem Fall ist, gemäß deines obigen Selects im Include, in aa.B das B eine Navigationseigenschaft vom ICollection<B>. Du kannst nun nicht direkt auf C zugreifen, sondern musst erst ein bestimmtes Item in der ICollection wählen und dann kannst du auf dessen C zugreifen.

        dedlfix.

        1. Hallo,

          vielen Dank. Deine Annahmen sind alle korrekt.
          Wie meinst du mit "ein bestimmtes Item in der ICollection"? Wie komme ich denn daran? Im Where-Klausel komme ich demzufolge nur an "aa" daran. über "aa." bekomme ich, außer der Eingenschaften "EntityKey" und "EntityState", nur die Eigenschften von aa. When ich wieder alle Tabellennamen angeben muss, um an deren Eigeanschften daran kommen zu können, frage ich mich, warum dann Include? Was bewirkt das?

          Gruß

          1. Tach!

            Vorab, ich weiß nicht, wie dein Datenmodell in Bezug auf die Art der Beziehung zwischen den Tabellen A, B und C aussieht, also 1:1, 1:n oder m:n. Ich habe lediglich aus deinem Code geschlussfolgert. Aber wenn dein Code nicht richtig ist, ist auch meine Schlussfolgerung hinfällig. Das Include mit dem Select muss man nur bei 1:n nehmen, bei 1:1 gehts ja direkt (a.B.C, oder b.B.C in deinem Fall mit den "falschen" Variablennamen).

            Wie meinst du mit "ein bestimmtes Item in der ICollection"? Wie komme ich denn daran?

            Wenn du zwischen B und C eine 1:n-Beziehung hast, ist über die Navigationseigenschaft logischerweise eine Ansammlung aller n C-Datensätze zu erreichen. Und von einer solchen ICollection spricht man einzelne Daten über ein foreach oder einen Indexzugriff oder andere Funktionen an (First(), Last(), FirstOrDefault(), ...)

            Im Where-Klausel komme ich demzufolge nur an "aa" daran. über "aa." bekomme ich, außer der Eingenschaften "EntityKey" und "EntityState", nur die Eigenschften von aa.

            An die Eigenschaften von B solltest du über die Navigationseigenschaft von A, die auf B verweist, zugreifen können. Die müssen im Datenmodel (*.edmx) angezeigt werden, dazu die Beziehungslinien. Wenn du die nicht hast, fehlt dir die Möglichkeit des Durchgreifens, da hilft dann auch kein Include() sondern nur ein Zu-Fuß-Join.

            Die Datenmenge von C einzuschränken, wenn es eine 1:n-Beziehung ist, geht vermutlich nur, wenn du an vor das .Select() in deinem Include noch ein .Where() hinzufügst. Dann, so nehme ich an, kannst du aber keine Werte aus A als Bedingung angeben. Wenn dein Ziel ist, eine eingeschränkte Datenmenge von C mit A zu verbinden, dann ist Include nicht der richtige Weg, dann musst du Joinen. Das geht auch über LINQ-Syntax.

            When ich wieder alle Tabellennamen angeben muss, um an deren Eigeanschften daran kommen zu können, frage ich mich, warum dann Include? Was bewirkt das?

            Include bewirkt beim Ausführen der Query ein Mitladen der Daten, auf die über die Navigationseigenschaften zur jeweiligen Tabelle zugegriffen werden soll. Das Gegenteil ist Lazy Load, dass erst beim Zugriff die Daten geladen werden. Include ist kein Ersatz für Join, wenn dir die Navigationseigenschaften fehlen.

            dedlfix.

  2. Moin,

    ich komme einfach nicht dahinter, wie Include bei Linq funktioniert.

    dedlfix hat da ja schon einiges zu geschrieben.

    Die Include-Methode bewirkt hinter den Kulissen ein GroupJoin (LEFT JOIN + GROUP BY). Was du jetzt mit der jeweiligen Gruppe machst bleibt dir überlassen. Also ein Sum, Any, Min, Max oder was auch immer darauf anwenden. Du kannst natürlich auch ein LEFT OUTER JOIN durchführen.

    1. Tach!

      Die Include-Methode bewirkt hinter den Kulissen ein GroupJoin (LEFT JOIN + GROUP BY).

      Naja, das was das Include im Hintergrund macht ist ja nur ein Mittel zum Zweck. Da kann auch was ganz anderes ablaufen, solange das Ergebnis so bleibt wie es ist. Was da abläuft, muss den Verwender nicht unbedingt interessieren. Er muss Include auch allein anhand der Funktionsbeschreibung anwenden können.

      Was du jetzt mit der jeweiligen Gruppe machst bleibt dir überlassen. Also ein Sum, Any, Min, Max oder was auch immer darauf anwenden. Du kannst natürlich auch ein LEFT OUTER JOIN durchführen.

      Was kann man denn da anderes machen als ohne? Ein Include hat doch - zumindest beim EF - keine direkte Auswirkung auf das Ergebnis. Die Ergebnismenge ist doch dieselbe wie ohne Include(), nur der Zugriff auf Navigationseigenschaften ist unter Umständen performanter als LazyLoad.

      dedlfix.

      1. Moin,

        Ein Include hat doch - zumindest beim EF - keine direkte Auswirkung auf das Ergebnis.

        es geht darum, wo und wann es verarbeitet wird. Wenn man es in einem Rutsch auf dem Server (DB) verarbeiten möchte, dann muss man es erstmal in seine Sprache übersetzen. Dadurch wird einfach der Overhead verringert und die Performance gesteigert. Wenn man das immer nachträglich auf dem Client auswerten möchte, dann sind halt auch seperate Anfragen zum Abholen der Daten nötig. Das Queryable-Interface dient ja dazu, die Abfrage nach SQL umzuformulieren. Verläßt man die Schnittstelle (z.B. mit ToList), dann führt das imho auch zu einem Bruch. Ein weiterer Vorteil von Include ist, das denke ich mir jetzt jedenfalls so, dass es die bereits im EF vorhanden Beziehungen nutzt, also weiss, wie die Daten zusammenhängen.

        1. Tach!

          Ein Include hat doch - zumindest beim EF - keine direkte Auswirkung auf das Ergebnis.
          es geht darum, wo und wann es verarbeitet wird. Wenn man es in einem Rutsch auf dem Server (DB) verarbeiten möchte, dann muss man es erstmal in seine Sprache übersetzen. Dadurch wird einfach der Overhead verringert und die Performance gesteigert.

          Das und das weitere ist ja alles richtig, nur ist aus meiner Sicht das Include lediglich ein Performance-Feature. Funktional braucht man es nicht und muss theoretisch seine interne Arbeitsweise nicht kennen. Ich denke, das eigentliche Problem von Casablanca war nicht Include, sondern das Verständnis der Arbeitsweise der Navigationseigenschaften.

          dedlfix.