Laden...

LinQ Abfrage mit Left Join richtig aufbauen

Erstellt von oehrle vor 11 Jahren Letzter Beitrag vor 11 Jahren 5.325 Views
O
oehrle Themenstarter:in
461 Beiträge seit 2009
vor 11 Jahren
LinQ Abfrage mit Left Join richtig aufbauen

verwendetes Datenbanksystem: <Sql2008R2>
Hallo, ich habe eine Problem mit einer Abfrage in LinQ. Habe in FOren gesucht und auch etwas gefunden. Ich habe drei Tabellen, die alle in einem Dataset eingelesen sind. Jetzt will Tabell1 / Tabelle2 / Tabelle3 miteinander verknüpfen. Tabelle1 ist der Master er hat zu jeder der beiden anderen Tabellen auf jeden Fall einen Datensatz. Tabelle2 oder Tabelle3 habe nicht immer zu jedem Datensatz in Tabelle1 einen Datensatz. Ich möchte also somit eine löchrige Gesamttabelle erstellen (aus den drei Tabellen). Der Bezug ist die Auftragsnummer. Habe da zwei Konstrukte aufgebaut, haber aber das Problem, das beim zweiten Konstrukt beim abrufen der Daten der Compiler auf die Barrikaden geht, sobald in "Mahnliste" oder "Planliste" ein Datensatz nicht vorhanden ist. Da sollen dann einfach die Felder leer bleiben. (Jetzt fällt mir was ein, in den beiden Tabellen "Mahnliste" und "Planliste" sind Spalten, die nicht NULL sein dürfen, kann das evtl. das Problem sein?) Wie gebe ich für solche leere Felder Standardwerte vor?

Hier mal die beiden Konstrukte:



            var test = from move in DsAftVerwalt.Tables["Movedaten"].AsEnumerable()
                        join mahn in DsAftVerwalt.Tables["Mahnliste"].AsEnumerable() on move["Auftragsnr"].ToString().Trim() equals mahn["Auftragsnummer"].ToString().Trim()  into moveMahn
                        join plan in DsAftVerwalt.Tables["Planliste"].AsEnumerable() on move["Auftragsnr"].ToString().Trim() equals plan["Auftragsnummer"].ToString().Trim()  into movePlan

                        from mahn in moveMahn.DefaultIfEmpty()
                        from plan in movePlan.DefaultIfEmpty()


                        select new
                        {
                            Auftragsnummer = move["Auftragsnr"],
                            Maschinengruppe = move["Maschinengruppe"],
                            Mahndatum = mahn["Mahndatum"],
                            Maschine = plan["Maschine"]

                        };


Hier das zweite Konstrukt:


            var test1 = from move in DsAftVerwalt.Tables["movedaten"].AsEnumerable()
                        from mahn in DsAftVerwalt.Tables["Mahnliste"].AsEnumerable().Where(x => x["Auftragsnummer"].ToString().Trim() == move["Auftragsnr"].ToString().Trim())
                        from plan in DsAftVerwalt.Tables["Planliste"].AsEnumerable().Where(y => y["Auftragsnummer"].ToString().Trim() == move["Auftragsnr"].ToString().Trim()).DefaultIfEmpty()

                        select new
                       {
                           Auftragsnummer = move["Auftragsnr"],
                           Maschinengruppe = move["Maschinengruppe"],
                           Mahndatum = mahn["Mahndatum"],
                           Maschine = plan["Maschine"]

                       };

Kann mir da jemand Unterstützung geben?

D
500 Beiträge seit 2007
vor 11 Jahren

Hi!

Dir fehlt im zweiten Konstrukt das kleine, aber wichtige Schluesselwort "into". Das "into" macht bei Linq einen Outer Join aus. Laesst du das "into" weg, ist es ein Inner Join.

Gruss,
DaMoe

O
oehrle Themenstarter:in
461 Beiträge seit 2009
vor 11 Jahren
Linq mit outer join

Hallo, ich muss mich da langsam hineintatsten. Arbeite als schon mit LinQ, aber nicht so verzwickte Sachen. Ich versuche erst mal so anzufangen. Ich habe die beiden Tabellen mvx und mhn.
In der mvx sind alle Datenzeilen relevant.
In der mhn sind nur ein paar Datensätze vorhanden, Schlüssel zueinander ist die Auftragsnummer.

Jetzt möchte ich eine Abfrage erstellen, mit aalen Datensätzen von Tabelle mvx und den zugeordneten Datensätzen von Tabelle mhn. Ist kein Datensatz von mhn vorhanden, wird ein Default-Datensatz zugewiesen.

Hier mal ein Beispiel, funktioniert aber nicht. Sobald in der mhn ein Datensatz nicht vorhanden ist, streikt die Abfrage. Ich habe jetzt mal nur von jeder Tabelle eine Spalte verwendet.


 var mvx = DsAftVerwalt.Tables["Movedaten"].AsEnumerable();
            var mhn = DsAftVerwalt.Tables["Mahnliste"].AsEnumerable();

            var xx =    from m in mvx orderby m["Auftragsnr"]
                        join p in mhn on m["Auftragsnr"] equals p["Auftragsnummer"] into outer
                        from o in outer.DefaultIfEmpty()
                        select new
                                    {
                                        Auftragsnummer = c["Auftragsnr"], Mahndatum = ((o["Mahndatum"] == DBNull.Value) ? "" : o["Mahndatum"])
                                    };

2.891 Beiträge seit 2004
vor 11 Jahren

Soweit ich dich verstanden habe, willst du ein LeftOuterJoin machen(?)

Unter Join Operators/Left Outer Join bekommst du ein Beispiel. Dein Code entspricht dem ja relativ genau - allerdings vermute ich stark, dass der Fehler bei dir beim Vergleich o["Mahndatum"] == DBNull.Value liegt. Hier triffst du auf einen Unterschied zwischen SQL und LINQ - denn wenn es keinen Joinpartner gibt, ist schon das o-Objekt null (statt wie in SQL der Wert der Spalte).

Deine Zuweisung musst du also nur auf Mahndatum = (o==null) ? "" : o["Mahndatum"] ändern. Dann sollte es eigentlich gehen.

O
oehrle Themenstarter:in
461 Beiträge seit 2009
vor 11 Jahren
LinQ mit 2 Tabelle geht, jetzt noch mit drei Tabellen

Hi, endlich hats geklappt. Das war der Fehler mit (o==null).

Jetzt kommt noch eine dritte Tabelle dazu, die auch nur ein paar Datensätze hat, aber auch zu jedem Datensatz in Tabelle1 (mvx) eine leere oder wenn Daten vorhanden die Felder gefüllt hergeben muss.

Ich habe das erst mal in eine weitere Query ausgelagert, siehe Code:


var yy =    from m in xx orderby m.m["Auftragsnr"]
                        join p in pln on m.m["Auftragsnr"] equals p["Auftragsnummer"] into outer
                        from o in outer.DefaultIfEmpty()
                        select new
                                    {
                                        //Auftragsnummer = m["Auftragsnr"], Mahndatum = ((o == null) ? "" : o["Mahndatum"])
                                        m,  o
                                    }; 

Wie kann ich das aber in eine Abfrage packen, damit ich dann 3 Rows habe??

2.891 Beiträge seit 2004
vor 11 Jahren

Wie kann ich das aber in eine Abfrage packen, damit ich dann 3 Rows habe??

Naja, das erlernte Wissen anwenden und einfach analog zu dem, was du hast, weitermachen.
Hier mal ein abgespecktes Beispiel:


var oneSet =   new[] { new { ID = "1",OneValue = 1 },new { ID = "2",OneValue = 1 },new { ID = "3",OneValue = 1 }, };
var twoSet =   new[] { new { ID = "1",TwoValue = 2 },new { ID = "2",TwoValue = 2 }, };
var threeSet = new[] { new { ID = "1",ThreeValue = 3 }, };

// das hast du ja schon (LeftOuterJoin mit zwei Sequenzen):
var query1 = from one in oneSet
			 join two in twoSet on one.ID equals two.ID into outerTwoSet
			 from outerTwo in outerTwoSet.DefaultIfEmpty()
			 select new { One = one,Two = outerTwo };

// das willst du haben (LeftOuterJoin mit drei Sequenzen):
var query2 = from one in oneSet
			 join two in twoSet on one.ID equals two.ID into outerTwoSet
			 from outerTwo in outerTwoSet.DefaultIfEmpty()
			 join three in threeSet on one.ID equals three.ID into outerThreeSet
			 from outerThree in outerThreeSet.DefaultIfEmpty()
			 select new { One = one,Two = outerTwo,Three = outerThree };

Das ergibt dann folgende Ergebnisse (oben query1, unten query2):

O
oehrle Themenstarter:in
461 Beiträge seit 2009
vor 11 Jahren
LinQ mit LeftOuterJoin funktioniert

Danke dir, habe das gerade vorher getestet und es funzt. Bin erst al guter Laune darüber. Kann ich das in LinQ (also die QUery) auch noch gleich in DataRow[] casten oder sofort in eine DataTable stecken? Wenn ich das nur schon ineinem DataRow[] hätte, wäre das schonalles, dann die Dinge in eien Tabelle schaufeln, oder da gibt es noch etwas mit .Tolist().

O
oehrle Themenstarter:in
461 Beiträge seit 2009
vor 11 Jahren

Danke dir, habe das gerade vorher getestet und es funzt. Bin erst al guter Laune darüber. Kann ich das in LinQ (also die QUery) auch noch gleich in DataRow[] casten oder sofort in eine DataTable stecken? Wenn ich das nur schon ineinem DataRow[] hätte, wäre das schonalles, dann die Dinge in eien Tabelle schaufeln, oder da gibt es noch etwas mit .Tolist().

Ich habe heute morgen nochmal getestet. Ich komme da noch nicht zum Ziel. Ich möchte diese Zusmmenfassung der drei Tabellen in eine Tabelle packen, und dann in einem DataGrid darstellen. Dort soll man dann in den Feldern der Tab3 (also Plantabelle) Werte eintragen, welche ich dann durch den RowState "modified" oder "added" erfassen kann und dann diese Änderungen in die Datenbank schreibe.

Ich dachte est mal an so etwas, alle Zeilen in eine DataRow[]-Array zu schreiben, aber da gibt es auch´Probleme mit Spaltentypen:


 IEnumerable<DataRow[]>  query = (IEnumerable<DataRow[]>) (from m in mvx
                                                       orderby m["Auftragsnr"]
                                                       join p in mhn on m["Auftragsnr"] equals p["Auftragsnummer"] into outerMvxMhn
                                                       from omm in outerMvxMhn.DefaultIfEmpty()
                                                       join z in pln on m["Auftragsnr"] equals z["Auftragsnummer"] into outerMvxMhnPln
                                                       from outerPln in outerMvxMhnPln.DefaultIfEmpty()
                                                       select new
                                                                  {
                                                                      //mvx,
                                                                      //outerMvxMhn,
                                                                      outerMvxMhnPln 
                                                                  });

Kann mir da jemand Unterstützung geben? Am besten wäre es wennich die query mit allen drei verjointen Tabellen in einer Tabelle hätte.

2.891 Beiträge seit 2004
vor 11 Jahren

Kann ich das in LinQ (also die QUery) auch noch gleich in DataRow[] casten oder sofort in eine DataTable stecken?

Nein, du kannst es nicht casten - denn es ist ja eben kein DataRow-Array (sondern eine Auflistung von Objekten eines anonymen Typs). Allerdings kannst du eine Objekt-Auflistung direkt als Datenquelle eines DataGrids benutzen.
Wenn es dir nützt, kannst du dir auch eine DataTable erstellen und jedes Auflistungselement als DataRow hinzufügen.

Ich möchte diese Zusmmenfassung der drei Tabellen in eine Tabelle packen und [...] dann durch den RowState "modified" oder "added" [...] diese Änderungen in die Datenbank schreibe.

Möchtest du den RowState direkt in den drei Original-DataTables präsent haben? Also so, dass du in der einen Join-DataTable schreibst und Änderung direkt in die ursprünglichen Tabellen zu sehen sind?
Ich denke, von dem Gedanken kannst du dich verabschieden. Jedenfalls fällt mir keine (hinreichend einfache) Möglichkeit ein, das zu tun.

Soweit ich verstanden habe, was du machen willst, würde ich zu folgendem Vorgehen tendieren:1.Du erstellst dir via anonymen Typen eine Auflistung der Elemente deiner Jointabelle. 1.Diese Auflistung bindest du an ein DataGrid. Änderungen im DataGrid sollten (automatisch) direkt zurück in die Objekte geschrieben werden. 1.Bei der Speicheroperation musst du alle Objekte durchlaufen (hier bietet sich dann besser auch an, einen konkreten Typen [statt einen anonymen] zu verwenden) und alle Werte zurück in die drei Ursprungs-DataTables schreiben. 1.Die drei ursprünglichen DataTables sollten nun einen entsprechenden RowState haben und du kannst sie zurückschreiben.

O
oehrle Themenstarter:in
461 Beiträge seit 2009
vor 11 Jahren
LinQ LEft outer Join mit 3 Tabellen aus DataSet

Ok, werde mich mal versuchen. Das ist wirklich ein blödes PRoblem, an dem ichda schon seit mitte der Woche dran hänge. Nochmal zum Verständnis: Die Daten der drei Tabellen ergänzen sich zu einem Auftragsüberblick. Tab1 hat die Werkzeugdaten, Tab2 die Mahndaten, Tab3 die Planungsdaten für Abarbeitung. Alle drei Tabellen sind über die Spalte Auftragsnummer zueinander zu verknüpfen. Bei Tab2 und Tab3 kann es sein, das es noch keinen Datensatz zu Tab1 gibt. Diese Datensätze müssen dann aber auch angezeigt werden. Der user kann dann noch über eine Filtermaske seine genauen AUftagsbereiche filtern, und dann in die Spalten der Tab3 die Daten für die geplante Bearbeitung eintragen. Nur diese geänderte oder neu eingetragenen Daten der Tab3 werde zurückgespeichert. Das alles wäre dann am besten in einem DataGrid darzustellen. Ich tu mir da gerade echt schwer. Also versteht man das was ich vorhabe, oder muss ich das nochmal besser schildern?

O
oehrle Themenstarter:in
461 Beiträge seit 2009
vor 11 Jahren
Problem beim internen casten? Warum?

Kann mir jemand einen Tipp geben? Habe bei MS etwas gefunden, was ich anwenden wollte, aber beim abrufen der Daten bekomme ichden Fehler: "SPecifies cast is not valid"

Habe aber die Spaltentypen wie bei den DB-Tabellen angegeben.

Hier mal der Code , evtl. hat jemand eine Idee oder sieht den Fehler sofort (kann man das irgedwie debuggen??):




           var mvx = DsAftVerwalt.Tables["Movexdaten"].AsEnumerable();
            var mhn = DsAftVerwalt.Tables["Mahnliste"].AsEnumerable();
            var pln = DsAftVerwalt.Tables["Planliste"].AsEnumerable();


            //// Für Default-Row in LinQ
            DataRow dRowMahn = DsAftVerwalt.Tables["Mahnliste"].NewRow();
            dRowMahn[1] = 0;
            dRowMahn[2] = "0000";
            dRowMahn[3] = "0000";
            dRowMahn[4] = DateTime.Now;


 var xxx = from m in mvx
                      orderby m.Field<Decimal>("Auftragsnr")
                      join p in mhn on m.Field<Decimal>("Auftragsnr") equals p.Field<Decimal>("Auftragsnummer") into outer
                      from o in outer.DefaultIfEmpty(dRowMahn)
                      select new
                                 {
                                     Auftragsnr = m.Field<Decimal>("Auftragsnr"),
                                     Maschinengruppe = m.Field<String>("Maschinengruppe"),
                                     AGStatus = m.Field<String>("AGStatus"),
                                     AGBeschreibung = m.Field<String>("AGBeschreibung"),
                                    Mahndatum = o.Field<DateTime>("Mahndatum")
                     };

2.891 Beiträge seit 2004
vor 11 Jahren

Kann mir jemand einen Tipp geben?

Ich habe dir doch schon geschrieben, wie ich es machen würde.

kann man das irgedwie debuggen??

Wie du selbst Programme mit der Entwicklungsumgebung deiner Wahl debuggst, solltest du schon wissen (Haltepunkte, Überwachungsfenster, schrittweise Ausführung usw. sind eigentlich Grundlagen).
Falls wir da was für dich debuggen sollten: [Tutorial] Vertrackte Fehler durch Vergleich von echtem Projekt mit minimalem Testprojekt finden