Casablanca: LINQ-Anweisung

Hallo Forum,

ich brauche wieder mal eure Hilfe bei einer LINQ-Abfrage.
Ich habe da 3 Tabellen wie folgt. Ich benenne nur die relevanten Spalten:

Tabelle A:
AID
1
2
3
4

Tabelle B: (Verknüpft mit Tabelle A durch AID)
BID  | AID | BName
1    | 1   | a
2    | 1   | b
3    | 1   | c
4    | 2   | d
5    | 2   | f
6    | 3   | g
7    | 4   | h
8    | 3   | i

Tabelle C:  (Verknüpft mit Tabelle B durch BID)
CID | BID | CName
1   | 1   | aa
2   | 1   | bb
3   | 2   | cc
4   | 3   | dd
5   | 3   | ee
6   | 1   | ff
7   | 4   | ff
8   | 4   | ff
9   | 5   | ff

Ich brauche nun diese Anzeige:
A_1 hat 3XB und 6XC (BID -> 1, 2, 3 und CID -> 1, 2, 3, 4, 5, 6)
A_2 hat 2XB und 3XC (BID -> 4, 5 und CID -> 7, 8, 9)
A_3 hat 2XB und 0XC (BID -> 6, 8)
A_4 hat 1XB und 0XC (BID -> 7)

Ich habe bereits versucht über diese LINQ-Anweisung das Ergebnis zu bekommen, es gelingt mir aber nicht:

  
from b in B  
join a in A on b.AID equals a.AID  
join c in C on b.BID equals c.BID  
group b by new { a.AID, b.AID, c.BID } into grp  
select new PlantDeviceViewModel  
{  
  myAID = grp.Key.AID,  
  AnzahlB = grp.Count(),  
  ANZAHLC = grp.Select(x => x.CID).Distinct().Count()  
};  
  

Hat jemand eventuell eine Idee, wie ich das Ergebnis wie oben beschreiebn erhalten kann?

Danke im Voraus

  1. Tach!

    ich brauche wieder mal eure Hilfe bei einer LINQ-Abfrage.
    Ich habe da 3 Tabellen wie folgt.

    Tabellen haben aber nicht direkt was mit LINQ zu tun. Die Tabellen werden doch irgendwie mit einem ORM in ein Klassen-Layout umgesetzt. Das heißt, dass zum Beispiel die Klasse, die die A-Elemente repräsentiert, eine Eigenschaft vom Typ ICollection<B> besitzt. Über diese kann man dann alle Elemente aus B ansprechen, die zur ID des A-Elements passen. Darauf kann man dann auch Count() und ähnliches ansetzen.

    Ich brauche nun diese Anzeige:
    A_1 hat 3XB und 6XC (BID -> 1, 2, 3 und CID -> 1, 2, 3, 4, 5, 6)

    Und hier musste ich raten, was die Abkürzungen sein sollen. A_1 ist das A-Element mit der ID 1. Was 3XB ist, wäre mit 3 x B besser zur Geltung gekommen. Am besten wäre eine Beschreibung in natürlicher Sprache gewesen.

    Ich habe bereits versucht über diese LINQ-Anweisung das Ergebnis zu bekommen, es gelingt mir aber nicht:

    Das Ergebnis ist die eine Sache. Du weißt doch aber (hoffentlich) auch, was da nach welchen Regeln zusammengezählt werden soll. Wenn du das in Worte hättest gefasst, wäre mir als Helfer auch mehr geholfen. Es kann nämlich sein, dass dein Algorithmus oder Ergebnis schon Fehler enthält oder irgendeine Nebenbedingung nicht zu sehen ist, weil sie in dem Fall grad nicht vorkommt.

    Hat jemand eventuell eine Idee, wie ich das Ergebnis wie oben beschreiebn erhalten kann?

    Unter der eingangs erwähnten Bedingung der Navigation Properties (die ICollection-Dinger) komme ich auf diese einfache Lösung:

    var x = As.Select(a => new {
        a,
        bCount = a.Bs.Count(),
        cCount = a.Bs.Sum(b => b.Cs.Count())
      });

    x ist eine Enumeration eines anonymen Typs, welcher in den Eigenschaften bCount und cCount die gewünschten Ergebnisse enthält zum jeweilgen A-Element (in a enthalten). As ist ein IEnumerable<A> oder ähnliches (z.B. DbSet<A>) und a.Bs und b.Cs sind die Navigation Properties.

    dedlfix.

    1. Hi,

      vielen Dank für deine Antwort und deine Bemühung.
      Eigentlich hat dies Sache mit LINQ zu tun. Da musste nämlich eine LINQ-Anweisung (keine normale Select-Anweisung) geschrieben werden, die drei Tabellen mit join miteineder verknüpft. Das Problem, die ich hatte lag mit der Gruppierung zusammen. Bei der Gruppierung kammen nicht die richtigen Werte raus und ich habe nicht ganz geschafft, eine left join darein zu bekommen.

      Gruß

      1. Tach!

        Eigentlich hat dies Sache mit LINQ zu tun. Da musste nämlich eine LINQ-Anweisung (keine normale Select-Anweisung) geschrieben werden,

        LINQ setzt nicht auf Datenbank-Tabellen auf, sondern auf .NET-Klassen. Ob das dnan am Ende ein LINQ-to-SQL ist oder LINQ-to-XML oder LINQ-to-Sonstwas, ist erstmal egal. Wenn ein Problem mit LINQ zu lösen sein soll, muss man in erster Linie die Klassen betrachten, mit denen man in .NET arbeitet. (Erst später muss man einige Besonderheiten der verschiedenen LINQ-to-... berücksichtigen.)

        die drei Tabellen mit join miteineder verknüpft.

        Und das kann man für LINQ nicht ohne Kenntnis der Klassen lösen. In meiner Lösung braucht es zum Beispiel keine Gruppierung. Was das LINQ-to-... am Ende für ein SQL-Statement oder anderes draus baut, muss dich nicht interessieren. Und du musst auch nicht SQL und dessen Möglichkeiten im Hinterkopf behalten, um eine LINQ-Lösung zu erstellen, sondern lediglich die LINQ-Fähigkeiten kennen. (Dazu noch die Klassenstruktur des Datenmodells.)

        Das Problem, die ich hatte lag mit der Gruppierung zusammen. Bei der Gruppierung kammen nicht die richtigen Werte raus und ich habe nicht ganz geschafft, eine left join darein zu bekommen.

        Gruß

        dedlfix.

        1. Hi,

          vielen Dank.
          Wenn es so ist, dann bitte erkläre deine Lösung etwas näher, allerdings wenn du irgendwann mal Zeit und lust dazu hast.

          Ich würde mich sehr interessieren, wie ein ausführbares Beispiel aussehen würde.

          MfG

          1. Tach!

            Wenn es so ist, dann bitte erkläre deine Lösung etwas näher, allerdings wenn du irgendwann mal Zeit und lust dazu hast.

            Die erste zu klärende Frage ist, welchen ORM du verwendest, um von Datenbank-Tabellen (solch meintest du doch?) zu Klassen zu kommen. Ist es das Entity Framework oder das alte LINQ to SQL oder OpenAccess von Telerik oder ...? All diese Tools, wenn sie die Verknüpfung zwischen den Tabellen erkennen (konfigurierte Relationen), setzen solche Verbindungen als extra Eigenschaft von einem von IEnumeration abgeleiteten Typ in die erzeugten Klassen ein. Über diese Navigation Properties genannte Eigenschaften kann man auf die zugehörigen Datensätze der anderen Tabellen zugreifen, und sie so auch einfach zählen. Ein Gruppieren ist nicht notwendig (in dem Fall jedenfalls), das erledigt bei Bedarf der ORM.

            Wenn man Daten abbildet, so macht man das in den RDBMS-Tabellen über einen Schlüsselwert, der auf einen anderen Datensatz (einer anderen Tabelle) verweist. In OOP hingegen erweitert man die Klassen neben den eigentlichen Datenfeldern um eine Eigenschaft, die eine Referenz auf das Objekt einer anderen Klasse enthält. Oder wenn es mehrere sind, dann über eine Collection oder ähnliches. Die Beziehungen sind in OOP also direkt vorhanden. Man kann sich an ihnen auch entlanghangeln, wenn man auf die Eigenschaften der referenzierten Objekte zugreifen möchte. Im RDBMS muss man stattdessen joinen. - Nun ist es aber nicht so, dass sämtliche Tabelleninhalte in den Speicher gelesen werden und ein unter Umständen riesiges Beziehungsgeflecht über Referenzen aufgebaut wird. Der ORM sorgt dafür, dass nur genau zu den gewünschten Daten die Objekte erstellt werden. Auch werden die in Beziehung stehenden Objekte erst beim eigentlichen Zugriff angelegt (Lazy Load), wenn man nicht vorher ein explizites Mitladen verlangt. All das macht dir das Leben einfacher, weil du in objektorientiert denken und arbeiten kannst.

            Mit dem Wissen sollte es eigentlich einfach sein, meine Lösung zu verstehen. Ich gehe über die Navigation Properties und zähle einfach, wieviele Objekte in der Collection drin sind. Das Zählen der C-Datensätze/-Objekte ist ein wenig aufwendiger, weil dazu eine Summe über alle gezählten C-Objekte gebildet werden muss, die in Beziehung zu den B-Objekten des jeweiligen A-Objekts stehen.

            dedlfix.

            1. Hi,

              vielen Dank für die gute Erklärung. Soweit ich verstanden habe, sollen aber die Tabellen auf jeden Fall miteinander via join verknüpft sein, ansonsten, wie kann man die Zeilen von z.B. B-Tabelle zur ID=5 von der A-Tabelle zählen, wenn die nicht miteinander verknüpft sind? Meine Lösung sah wie folgt aus.

                
              from b in B  
              join a in A on b.AID equals a.AID  
              join c in C on b.BID equals c.BID  
              group new { a, b, c } by { b.AID, a.AName, ... } into grp  
              select new {  
                 ...  
                 wert = grp.Select (x => x.c.BID).Distinct().Count)  
              }  
                
              
              

              Wie würdest du das umschreiben, um das zu erreichen, was du die vorstellst?

              Gruß

              1. Tach!

                Wie würdest du das umschreiben, um das zu erreichen, was du die vorstellst?

                Noch einmal: Es fehlt der Zwischenschritt von den Datenbanktabellen zu OOP-Klassen - zumindest die Information darüber, welchen ORM du verwendest. Ohne dass du mir sagst, was du da verwendest, kann ich dir nur theoretisch erklären, wie es funktioniert, und nicht konkret, wo du nachschauen musst, um diese Theorie in der Praxis zu finden. ORM (Object-relational mapping/mapper) ist der Überbegriff für dieses Bindeglied. Konkrete Namen zu solchen ORMs habe ich in der vorigen Antwort genannt.

                In deinem obigen Code sind A, B und C Sammlungen von Objekten, wovon jedes Objekt einen Datensatz der Tabelle darstellt. Diese Klassen hat doch irgendjemand erstellt und irgendwie kommen auch die Inhalte in diese Klassen. Ich gehe davon aus, dass diese Klassen Navigation Properties in irgendeiner Form haben, über die man einfach die verbundenen Datensätze zählen kann.

                Soweit ich verstanden habe, sollen aber die Tabellen auf jeden Fall miteinander via join verknüpft sein, ansonsten, wie kann man die Zeilen von z.B. B-Tabelle zur ID=5 von der A-Tabelle zählen, wenn die nicht miteinander verknüpft sind?

                Wir schauen hier aus der Sicht eines C#-Programmierers und nicht aus der eines Datenbank-Anwenders. Was da wie gejoint werden muss, ist uns normalerweise egal, das macht im besten Fall alles der ORM für uns. Lediglich bei komplexeren Abfragen brauchen wir überhaupt Joins und Gruppierungen selbst zu erstellen - also über LINQ nachzubilden.

                Da https://gist.github.com/dedlfix/7301882 hab ich mal ein Beispiel erstellt. Das ist aber nicht wirklich praxisnah, weil ich da alles zu Fuß und ohne ORM erstellt habe (siehe die dortigen Kommentare). In der Praxis ist das alles durch den ORM erledigt und lediglich die Abfrage, die ich auch schon in der ersten Antwort gab, ist für die Problemlösung erforderlich - und natürlich das Wissen, was der ORM da für uns erzeugt hat.

                dedlfix.

                1. Hallo,

                  vielen Dank für die Bemühung und den Aufwand.
                  Enschuldige, ich habe es vergessen zu erwähnen, dass ich momentan mit LINQ to SQL arbeite. Das Beispiel habe ich gut nachvollziehen können. Wenn du sagst "Navigation Properties füllen, ebenfalls normalerweise vom ORM erledigt", heißt, dass der ORM bereits anhand der Verknüpfungen in beider Tabellen, die Beziehungen automatisch herstellet und die Daten bereitsstellt und man braucht nur diese durch Count zu zählen?

                  Sind AS und BS bei einer LINQ to SQL jeweils die Objekte einer Klasse/Tabllenklasse/Entity?

                  Gruß

                  1. Tach!

                    Enschuldige, ich habe es vergessen zu erwähnen, dass ich momentan mit LINQ to SQL arbeite.

                    Also, wenn du keinen ganz besonders guten Grund hast, bei LINQ to SQL (L2S) zu bleiben, würde ich dir empfehlen, auf das Entity Framework umzusteigen. Das wird wenigstens noch aktiv weiterentwickelt. Die Klassen, die L2S anlegt, sind vollgestopft mit Zeug, die mehr oder weniger für das Datenbank-Handling benötigt werden. Das EF erzeugt Klassen, die nur aus deinen Datenfeldern plus den Navigation Properties bestehen. Das ist nicht nur im Code sondern auch in der Anwendung übersichtlicher.

                    Das Beispiel habe ich gut nachvollziehen können. Wenn du sagst "Navigation Properties füllen, ebenfalls normalerweise vom ORM erledigt", heißt, dass der ORM bereits anhand der Verknüpfungen in beider Tabellen, die Beziehungen automatisch herstellet und die Daten bereitsstellt und man braucht nur diese durch Count zu zählen?

                    Genau das. L2S hat Äquivalente zu den Navigation Properties. Deine Data Class für A hat eine Eigenschaft Bs vom Typ EntitySet<B>. Das ist das, was in meinem Beispiel das A.Bs ist (nicht verwechseln mit Program.Bs). Das L2S-Diagramm zeigt diese Navigation Properties nicht in der Auflistung der Feldnamen an, aber sie sind da. Beim Klick auf die Beziehungspfeile kannst du in den Properties die Child und Parent Property sehen, und da stehen dann auch die Namen der Eigenschaften.

                    Wie/wann bei L2S konkret dieses EntitySet gefüllt wird, weiß ich nicht (mehr). Es kann sein, dass man da erst noch ein Load() aufrufen muss, bvor man darauf zugreifen kann, also erst A.Bs.Load(); dann erst A.Bs.Count(); Beim EF kann man sich zwischen Lazy Load und einem expliziten Include() entscheiden.

                    Sind AS und BS bei einer LINQ to SQL jeweils die Objekte einer Klasse/Tabllenklasse/Entity?

                    Program.As/Bs/Cs sind die Tabellen-Klassen, also die Klassen vom Typ Table<A> etc. A.Bs und B.Cs sind die EntitySet<B> und <C>, Verweise auf die Teilmengen in B und C zum jeweilgen Datensatz/Objekt in A beziehungsweise B.

                    Was ich in meinem Beispiel weggelassen habe, sind die Rück-Links. Eine Navigation Property erweitert die Klassen an beiden Enden um jeweils eine Eigenschaft. Je nach Typ der Beziehung (1:n, 1:1, m:n) ist das eine ICollection<> (EntitySet<> bei L2S) oder eine Referenz auf eine einfache Klasse. Also, in B gibt es noch eine Eigenschaft A vom Typ A und in C ein B.

                    In L2S kannst du dir das alles anschauen. Wenn du die .dbml aufklappst, sollte darin eine .designer.cs zu finden sein. Die Enthält Klassen für den DataContext und die Klassen für die Tabellen. Der Code ist wie gesagt sehr umfangreich, aber du solltest deine Datenfeld-Eigenschaften und die Verlinkungseigenschaften (Nav.Props) darin finden. (Visual Studio -> Edit -> Outlining -> Collapse to Definitions - macht den Code etwas übersichtlicher)

                    dedlfix.

                    1. Guten Tag,

                      vielen herzlichen Dank für die umfangreiche Erklärung. Jetzt weiß ich genau, was ich in meiner Freizeit zu tun habe.

                      MfG