Smart: Linq und Count

Hallo Forum, ich habe da ein Problem, das ich nicht so einfach lösen kann, weil ich nicht so viel Erfahrung in dem Linq-Bereich habe. Das Problem ist folgendes:

Ich habe zwei Tabellen wie folgt, die über eine id miteinander verbunden sind:

Tabelle Lager:

  
LagerID, LagerName  
1      | aa  
2      | bb  
3      | cc  
4      | dd  

und Tabelle Geräte:

  
GräteID, LagerID, StationName, GeräteName  
1       |1      | saa         |g1  
2       |2      | sbb         |g2  
3       |1      | saa         |g3  
4       |1      | sdd         |g4  
5       |1      | sdd         |g4  
6       |2      | sbg         |g5  
7       |2      | sbz         |g6  
7       |2      | sbb         |g7  

Ich brauch nun eine Ling-Anweisung, die mir folgendes liefert:

Anzahl Stationen in einem Lager groupiert nach Stationsname in verbindung mit Lager ID, also:
Stationen im Lager 1 = 3
Stationen im Lager 2 = 2

und anzahl Geräte (insgesamt) in dem jeweiligen Lager, also:
Lager 1 hat 2 Stationen mit insgesamt 4 Geräten
Lager 2 hat 3 Stationen mit insgesamt 4 Geräten

Danke im Voraus.

  1. Tach!

    Hallo Forum, ich habe da ein Problem, das ich nicht so einfach lösen kann, weil ich nicht so viel Erfahrung in dem Linq-Bereich habe.

    Suchst du wirklich eine LINQ-Anweisung, wie in LINQ für .NET? Und wenn ja, welche .NET-Sprache? Oder suchst du ein SQL-Statement, das joint und gruppiert?

    dedlfix.

    1. Hi,

      viele Dank für deine Antwort.
      Ich suche eine LINQ-Anweisung unter C# - VS 2010. Ich habe auch Vergessen zu erwähnen:

      da ich auch den Lagername aus der Lager-Tabelle brauche, muss ein Join aufgebaut werden.
      Also zu jedem Lager in der Tabelle Lager muss die Anzahl der Stationen und die Gesamtanzahl der Geräte in diesen Stationen ermittelt werden.

      Die Ausgabe sollte in etwa so aussehen:

      Lager 1 -> 2 Stationen, 4 Geräte
      Lager 2 -> 3 Stationen, 4 Geräte

      Gruß

      1. Tach!

        Also zu jedem Lager in der Tabelle Lager muss die Anzahl der Stationen und die Gesamtanzahl der Geräte in diesen Stationen ermittelt werden.

        Und das ist gar nicht mal so einfach (oder ich sehe nur Bäume und keinen Wald). Es ist ja noch einfach, für jedes Lager die Anzahl der Geräte zu ermitteln. Das geht mit einfachem Gruppieren, für das man Beispiele findet. Aber von den Stationen muss erst einmal eine Distinct-Menge erzeugt werden. Das ist nach meinem Dafürhalten nicht als LINQ-Syntax verfügbar, nur als Extension-Method. Dafür muss man jedoch eine IEqualityComparer implementierende Klasse erstellen, wenn man keine IEnumerable von den Stationen selbst hat, sondern nur den Namen, wie in deinen Beispieldaten. Und dann kommt noch hinzu, beide Teilprobleme in eine Abfrage zu vereinen.

        Ich würde das etwas anders angehen und nicht joinen oder gruppieren. Du hast doch hoffentlich/sicherlich ein EntityFramework-Model von deiner Datenhaltung erstellt. Dann gäbe es eine Klasse, die ein Lager repräsentiert, und die hat eine Listen-Eigenschaft für all ihre Geräte. Wenn sie jetzt noch eine für alle Stationen hätte, wäre schon alles gut und mit zwei Count()s pro Lager hättest du dein Ergebnis. Ungefähr so:

        var result = from lager in Lagers.Include("Stationen").Include("Geräte")
          select new {
            Lager => lager.Name,
            Stationen => lager.Stationen.Distinct().Count();
            Geräte => lager.Geräte.Count();
          };

        Ohne IEnumerable für die Stationen braucht es für das Distinct() die erwähnte Klasse.

        dedlfix.

        1. Hi,

          oh danke für deine Bemühung. Ich kann leider keine Listeneigenschft in der Lager-Entityklasse finden. Ich habe gerade versuch, dieses Problem mi subselect zu lösen. Das habe ich auch leider nicht ganz hinbekommen. Mit "Lagers" ist die erwähnte Liste gemeint oder was anderes?

          Gruß

          1. Tach!

            Ich kann leider keine Listeneigenschft in der Lager-Entityklasse finden.

            Listen entstehen beim Entity Framework als Navigation Property, wenn es eine Fremdschlüsselbeziehungen gibt, jedenfalls beim Database-First-Ansatz. Wenn du mit Code First an die Sache gehst, musst du diese Eigenschaften selbst anlegen, als virtual ICollection<...>

            Ich habe gerade versuch, dieses Problem mi subselect zu lösen. Das habe ich auch leider nicht ganz hinbekommen.

            Das ist aber nicht mehr LINQ sondern SQL. Aber das sollte nicht schwer sein. Sinnbildlich müsste das so gehen:

            SELECT lager, COUNT(*) GeräteAnzahl, COUNT(DISTINCT StationName) StationAnzahl FROM Geräte GROUP BY lager

            Sogar ohne Subquery.

            Mit "Lagers" ist die erwähnte Liste gemeint oder was anderes?

            Lagers ist das DbSet<Lager>, was das EF in der Entities-Klasse für jede Tabelle anlegt. (Am besten gibst du allen Tabellen und Spalten englische Namen, dann klappt das mit der automatischen und händischen Plural-Vergabe besser.)

            dedlfix.

            1. Servus,
              unser Sportsfreund Smart bringt hier immer wieder Linq und SQL durcheinander.

              Smart, was genau hast du vorliegen? 2 Tabellen in einer SQL Datenbank (SQL Compact, SQL Server, ...) oder 2 Collections voll Entities. Oder wie dedlfix vermutet, ein EF Modell?

              Hast du zwei Tabellen in einer MS SQL Datenbank, dann kannst du das Problem mithilfe eines Select Statements lösen.

              SELECT  
                s.lagerid  
              , l.LagerName  
              , COUNT(*) AS NumStationen  
              , SUM(numgeraete) AS TotalGeraeteProLager  
                 FROM (SELECT lagerid, StationName, COUNT(*) as numgeraete FROM geraete GROUP BY lagerid, StationName) s  
                 JOIN lager l ON l.lagerid = s.lagerid  
                 GROUP BY s.lagerid, l.LagerName
              

              Und das ist nicht die einzige Möglichkeit für ein einziges Select.

              Hast du 2 Entities Lager { lagerId, lagerName } und Geraet { geraetId, lagerId, stationName, geraetName } in deinem C# Code, dann hast du selbige sicherlich in Form von mehr oder weniger typisierten Collections vorliegen.
              Diese Collections sollten IEnumerable<..>, IList<..> implementieren, damit du effektiv LinQ darauf anwenden kannst. Das Prinzip bleibt aber annähernd das gleiche.

              Poste doch mal etwas Beispiel Code.

              Ciao, Frank

              1. Vielen Dank für eure Hilfe.
                Eigentlich bringe ich LINQ und SQL nich durcheinander. Da ich mit LINQ an dieser Stelle nich weiterkam, was ein muss für meine Implementierung ist, habe ich versucht eine SQL-Abfrage zusammenzubasteln, damit ich ein besseres Verständnis dafür zu bekommen, wie die LINQ-Abfrage aussehen könnte. Aus eine LINQ wir auch zur Komplierezeit nichts anderes als eine einfache SQL-Anweisung generiert.

                Bei mir geht es um EF. Einfach gesagt, habe ich dadurch für jede Tabelle eine Klasse, auf die ich über die DataContext-Klasse zugreifen kann. Bei mir steht keine Collections oder Liste zur Verfügung, nur die Eigenschsften, die wegen der Tabelenbezihungen zustande kommen. Die Tabellen sind durch eine 1:n Bezeihung (über Lager-ID) miteieneder verbunden.

                Ich habe leider keinen Code, die hier posten kann, nur die LINQ-Versuche, die ich immer wieder verwefen muss.

                Gruß

                1. Tach!

                  Bei mir geht es um EF. Einfach gesagt, habe ich dadurch für jede Tabelle eine Klasse, auf die ich über die DataContext-Klasse zugreifen kann. Bei mir steht keine Collections oder Liste zur Verfügung, nur die Eigenschsften, die wegen der Tabelenbezihungen zustande kommen. Die Tabellen sind durch eine 1:n Bezeihung (über Lager-ID) miteieneder verbunden.

                  Die Eigenschaften, die durch die 1:n Tabellenbeziehungen zustandekommen, sind Listen auf der einen Seite (ICollection) und ein einzelnes Element einer Klasse auf der anderen.

                  Ich habe leider keinen Code, die hier posten kann, nur die LINQ-Versuche, die ich immer wieder verwefen muss.

                  Du kannst dir die erzeugten Entity-Klassen ansehen, sie sind nach dem Ausklappen der DeinModel.edmx-Datei unterhalb von DeinModel.tt zu finden.

                  dedlfix.

                  1. Hi,

                    vielen Dank. Bei mir stehen die Klassen in DataConrext-Klasse unter einer .dbml-Datei. Da sind folgendes zu sehen:

                    private EntitySet<Geraete> _Geraete; (in Lager-Tabelle)
                    private EntityRef<Lager> _Lager;     (in Geräte-Tabelle)

                    Wie du siehst, sind diese Felder als Private definiert und man kann nicht ohne weiteres darauf zugreifen. Meinst du vielleicht was anderes?

                    Gruß

                    1. Tach!

                      Bei mir stehen die Klassen in DataConrext-Klasse unter einer .dbml-Datei.

                      Dann hast du nicht das EntityFramework verwendet sondern "LINQ to SQL". Das ist oder besser gesagt war ein konkurrierender Ansatz zum EF, der aber zugunsten des EF nicht mehr weiter entwickelt wird (meines Wissens nach). Der Designer von LINQ2SQL jedenfalls zeigt die von mir genannten Navigation Properties nicht an, sondern nur Verbindungslinien zwischen den Tabellenobjekten.

                      Da sind folgendes zu sehen:
                      private EntitySet<Geraete> _Geraete; (in Lager-Tabelle)
                      private EntityRef<Lager> _Lager;     (in Geräte-Tabelle)
                      Wie du siehst, sind diese Felder als Private definiert und man kann nicht ohne weiteres darauf zugreifen. Meinst du vielleicht was anderes?

                      Wie du nicht gesehen hast, gibt es dieselben Eigenschaften auch noch als public ohne Unterstrich am Anfang (bei mir zumindest). Das sind die Verweise auf die mit dem jeweiligen Datensatz in Beziehungen stehenden Datensätze anderer Tabellen. Die Tabellen selbst (das was bei mit Lagers hieß) sind Kinder der ...DataContext-Klasse, à la public System.Data.Linq.Table<Lager> Lagers. (Das was das EF an Code erzeugt ist übersichtlicher, weil sich das auf das Wesentlichste beschränkt. Alles andere ist irgendwo in nicht öffentlich sichtbarem Bereich angesiedelt. Versuch doch, wenn es geht, auf das EF umzusteigen.)

                      dedlfix.

                      1. Deine Select-Anweisung hat mich etwas wietergebracht. Ich habe die LINQ-Anweisung dazu herausgefunden:

                          
                                    var quary = from geraet in Geraete  
                                                group geraet by LagerID into grp  
                                                select new HilfsKlasse  
                                                {  
                                                    GeraeteAnzahl = grp.Select(g => g.Beschreibung).Count(),  
                                                    StationenAnzahl = grp.Select(x => x.StationName).Distinct().Count()  
                                                };  
                        
                        

                        Jetzt kommt aber ein Join hinzu:

                          
                                    var quary = from geraet in Geraete  
                                                join lager in Lager on geraet.LagerID equals lager.PlantID  
                                                group geraet by LagerID into grp  
                                                select new HilfsKlasse  
                                                {  
                                                    GeraeteAnzahl = grp.Select(g => g.Beschreibung).Count(),  
                                                    StationenAnzahl = grp.Select(x => x.StationName).Distinct().Count()  
                                                    LagerName = ?  
                                                    LagerBeschreibung = ?  
                                                };  
                        
                        

                        Und nun habe ich das Problem, dass ich nicht weiß, wie die Felder der Lager-Tabelle in Select-Bereich hinein bekomme. Hast du eventuell eine Idee?

                        Gruß

                        1. Tach!

                          Deine Select-Anweisung hat mich etwas wietergebracht. Ich habe die LINQ-Anweisung dazu herausgefunden:

                          var quary = from geraet in Geraete
                                                  group geraet by LagerID into grp
                                                  select new HilfsKlasse
                                                  {
                                                      GeraeteAnzahl = grp.Select(g => g.Beschreibung).Count(),
                                                      StationenAnzahl = grp.Select(x => x.StationName).Distinct().Count()
                                                  };

                            
                          Diese Variante ist schon nicht schlecht. Gruppiere aber nicht nach LagerId sondern nach Lager (der Verweiseigenschaft von Gerät auf Lager). Dann hast du in grp.Key ein vollständiges Lager und nicht nur dessen Id. Von dem kannst du dann auch den Namen abgreifen. Notfalls musst du noch ein Include("lager") an das "Geraete" hängen. Außerdem brauchst du für die Geräteanzahl kein Select, ein Count() allein sollte reichen.  
                            
                            
                          dedlfix.
                          
                          1. Hallo,

                            vielen Dank nochmals für deine Hilfe.
                            Leider steht bei mir kei Include() zur Verfügung und das Aufnehmen des Lager-Objekts hat auch nicht hingehauen. Ich habe einfach die Felder von Lagertabelle in die group by aufgenommen und so konnte ich im Selectbereich darauf zugreifen.

                            Gruß

                            1. Tach!

                              Leider steht bei mir kei Include() zur Verfügung und das Aufnehmen des Lager-Objekts hat auch nicht hingehauen.

                              Was für Gründe sprechen denn dafür, bei LINQ2SQL zu bleiben und nicht auf EF umzusteigen?

                              dedlfix.

                              1. Hi,

                                der Aufwand! Ich bin schon sehr tief in der Implementierung fortgeschritten, um jetzt alles umzustellen. Bei dem nächsten Projekt werde ich bestimmt daran denken. Jetzt funktioniert ja alles.

                                Gruß