Benutzer mit den meisten Antworten
LINQ: Mehrere LEFT OUTER JOINs mit der gleichen Tabelle (DataTable)

Frage
-
Hallo,
ich habe 4 DataTables, die ich verbinden will.
Und zwar mit mehreren Outer Joins von der gleichen Tabelle aus.
Angenommen in Tab1 ist die äußere Tabelle, dessen Keys wollen sich nun jeweils mit den weiteren Tabellen Tab2, Tab3, Tab4 verbinden. Es soll aber auch eine Zeile erscheinen, wenn kein entsprechender Key in Tab2, Tab3, Tab4 gefunden wird. Für Tab1 soll also im Ergebnis immer was "drinstehen", und jeweils optional in Tab2/Tab3/Tab4
in SQL ausgedrückt:
select * from tab1
left outer join tab2 on <verknüpfung über Key>
left outer join tab3 on <verknüpfung über Key>
left outer join tab4 on <verknüpfung über Key>
Wie muß das LINQ denn dann lauten?Vielen Dank
Wolfgang
Antworten
-
Hi Wolfgang,
warum reihst Du die Join nicht aneinander und speicherst das Ergebnis in einer Ergebnisklasse, z.B. so:class Demo { internal void Execute() { DataTable dt1 = new DataTable(); dt1.Columns.Add("ID", typeof(int)); dt1.Columns.Add("Wert1", typeof(string)); for (int i = 1; i < 11; i++) dt1.Rows.Add(i, $"Wert 1 {i}"); DataTable dt2 = new DataTable(); dt2.Columns.Add("ID", typeof(int)); dt2.Columns.Add("Wert2", typeof(string)); for (int i = 1; i < 11; i += 2) dt2.Rows.Add(i, $"Wert 2 {i}"); DataTable dt3 = new DataTable(); dt3.Columns.Add("ID", typeof(int)); dt3.Columns.Add("Wert3", typeof(string)); for (int i = 1; i < 11; i += 3) dt3.Rows.Add(i, $"Wert 3 {i}"); var res = from r1 in dt1.AsEnumerable() join r2 in dt2.AsEnumerable() on r1.Field<int>("ID") equals (int)r2["ID"] into zws1 from r1a in zws1.DefaultIfEmpty() join r3 in dt3.AsEnumerable() on r1.Field<int>("ID") equals (int)r3["ID"] into zws2 from r2a in zws2.DefaultIfEmpty() select new C123 { ID = r1.Field<int>("ID"), Wert1 = r1.Field<string>("Wert1"), Wert2 = (r1a == null) ? "<leer>" : r1a.Field<string>("Wert2"), Wert3 = (r2a == null) ? "<leer>" : r2a.Field<string>("Wert3") }; Console.WriteLine("--- Tabelle 1"); foreach (DataRow r1 in dt1.Rows) Console.WriteLine($"{r1["ID"]} - {r1["Wert1"]}"); Console.WriteLine("--- Tabelle 2"); foreach (DataRow r2 in dt2.Rows) Console.WriteLine($"{r2["ID"]} - {r2["Wert2"]}"); Console.WriteLine("--- Tabelle 3"); foreach (DataRow r3 in dt3.Rows) Console.WriteLine($"{r3["ID"]} - {r3["Wert3"]}"); Console.WriteLine("--- Ergebnis"); foreach (var item in res) Console.WriteLine($"{item.ID} - {item.Wert1} - {item.Wert2} - {item.Wert3}"); } public class C123 { public int ID { get; set; } public string Wert1 { get; set; } public string Wert2 { get; set; } public string Wert3 { get; set; } } }
Du kannst anstelle den in einer DataTable versteckten Eigenschaften einer DataRow eigene Typen (=Klassen) nutzen, wie das auch im Entity Framework gemacht wird. In diesem Fall kannst Du in der Ergebnisklasse die Werte ablegen. Hier mal eine Demo als Anregung:
class Demo { internal void Execute() { DataTable dt1 = new DataTable(); dt1.Columns.Add("ID", typeof(int)); dt1.Columns.Add("Wert1", typeof(string)); for (int i = 1; i < 11; i++) dt1.Rows.Add(i, $"Wert 1 {i}"); List<C1> l1 = new List<C1>(); foreach (var item in dt1.AsEnumerable()) l1.Add(new C1() { ID = item.Field<int>("ID"), Wert1 = item.Field<string>("Wert1") }); DataTable dt2 = new DataTable(); dt2.Columns.Add("ID", typeof(int)); dt2.Columns.Add("Wert2", typeof(string)); for (int i = 1; i < 11; i += 2) dt2.Rows.Add(i, $"Wert 2 {i}"); List<C2> l2 = new List<C2>(); foreach (var item in dt2.AsEnumerable()) l2.Add(new C2() { ID = item.Field<int>("ID"), Wert2 = item.Field<string>("Wert2") }); DataTable dt3 = new DataTable(); dt3.Columns.Add("ID", typeof(int)); dt3.Columns.Add("Wert3", typeof(string)); for (int i = 1; i < 11; i += 3) dt3.Rows.Add(i, $"Wert 3 {i}"); List<C3> l3 = new List<C3>(); foreach (var item in dt3.AsEnumerable()) l3.Add(new C3() { ID = item.Field<int>("ID"), Wert3 = item.Field<string>("Wert3") }); var res = from r1 in l1 join r2 in l2 on r1.ID equals r2.ID into zws1 from r1a in zws1.DefaultIfEmpty() join r3 in l3 on r1.ID equals r3.ID into zws2 from r2a in zws2.DefaultIfEmpty() select new C123 { ID = r1.ID, Wert1 = r1.Wert1, Wert2 = (r1a == null) ? "<leer>" : r1a.Wert2, Wert3 = (r2a == null) ? "<leer>" : r2a.Wert3 }; Console.WriteLine("--- Tabelle 1"); foreach (DataRow r1 in dt1.Rows) Console.WriteLine($"{r1["ID"]} - {r1["Wert1"]}"); Console.WriteLine("--- Tabelle 2"); foreach (DataRow r2 in dt2.Rows) Console.WriteLine($"{r2["ID"]} - {r2["Wert2"]}"); Console.WriteLine("--- Tabelle 3"); foreach (DataRow r3 in dt3.Rows) Console.WriteLine($"{r3["ID"]} - {r3["Wert3"]}"); Console.WriteLine("--- Ergebnis"); foreach (var item in res) Console.WriteLine($"{item.ID} - {item.Wert1} - {item.Wert2} - {item.Wert3}"); } public class C1 { public int ID { get; set; } public string Wert1 { get; set; } } public class C2 { public int ID { get; set; } public string Wert2 { get; set; } } public class C3 { public int ID { get; set; } public string Wert3 { get; set; } } public class C123 { public int ID { get; set; } public string Wert1 { get; set; } public string Wert2 { get; set; } public string Wert3 { get; set; } } }
Für 4 Tabellen mit DataTable könnte die Lösung so aussehen.
class Demo { internal void Execute() { DataTable dt1 = new DataTable(); dt1.Columns.Add("ID", typeof(int)); dt1.Columns.Add("Wert1", typeof(string)); for (int i = 1; i < 21; i++) dt1.Rows.Add(i, $"Wert 1 {i}"); DataTable dt2 = new DataTable(); dt2.Columns.Add("ID", typeof(int)); dt2.Columns.Add("Wert2", typeof(string)); for (int i = 1; i < 21; i += 2) dt2.Rows.Add(i, $"Wert 2 {i}"); DataTable dt3 = new DataTable(); dt3.Columns.Add("ID", typeof(int)); dt3.Columns.Add("Wert3", typeof(string)); for (int i = 1; i < 21; i += 3) dt3.Rows.Add(i, $"Wert 3 {i}"); DataTable dt4 = new DataTable(); dt4.Columns.Add("ID", typeof(int)); dt4.Columns.Add("Wert4", typeof(string)); for (int i = 1; i < 21; i += 5) dt4.Rows.Add(i, $"Wert 4 {i}"); var res = from r1 in dt1.AsEnumerable() join r2 in dt2.AsEnumerable() on r1.Field<int>("ID") equals (int)r2["ID"] into zws1 from r1a in zws1.DefaultIfEmpty() join r3 in dt3.AsEnumerable() on r1.Field<int>("ID") equals (int)r3["ID"] into zws2 from r2a in zws2.DefaultIfEmpty() join r4 in dt4.AsEnumerable() on r1.Field<int>("ID") equals (int)r4["ID"] into zws3 from r3a in zws3.DefaultIfEmpty() select new C1234 { ID = r1.Field<int>("ID"), Wert1 = r1.Field<string>("Wert1"), Wert2 = (r1a == null) ? "<leer>" : r1a.Field<string>("Wert2"), Wert3 = (r2a == null) ? "<leer>" : r2a.Field<string>("Wert3"), Wert4 = (r3a == null) ? "<leer>" : r3a.Field<string>("Wert4") }; Console.WriteLine("--- Tabelle 1"); foreach (DataRow r1 in dt1.Rows) Console.WriteLine($"{r1["ID"]} - {r1["Wert1"]}"); Console.WriteLine("--- Tabelle 2"); foreach (DataRow r2 in dt2.Rows) Console.WriteLine($"{r2["ID"]} - {r2["Wert2"]}"); Console.WriteLine("--- Tabelle 3"); foreach (DataRow r3 in dt3.Rows) Console.WriteLine($"{r3["ID"]} - {r3["Wert3"]}"); Console.WriteLine("--- Ergebnis"); foreach (var item in res) Console.WriteLine($"{item.ID} - {item.Wert1} - {item.Wert2} - {item.Wert3} - {item.Wert4}"); } public class C1234 { public int ID { get; set; } public string Wert1 { get; set; } public string Wert2 { get; set; } public string Wert3 { get; set; } public string Wert4 { get; set; } } }
--
Viele Grüsse
Peter Fleischer (ehem. MVP für Developer Technologies)
Meine Homepage mit Tipps und Tricks
- Bearbeitet Peter Fleischer Mittwoch, 21. November 2018 08:50
- Als Antwort vorgeschlagen Ivan DragovMicrosoft contingent staff, Moderator Montag, 26. November 2018 10:11
- Als Antwort markiert Ivan DragovMicrosoft contingent staff, Moderator Montag, 3. Dezember 2018 10:28
Alle Antworten
-
Hallo Wolfgang,
schau mal bitte in diesen SO Thread, da wird beschrieben, wie Du das machen kannst.
Gruß, Stefan
Microsoft MVP - Visual Developer ASP/ASP.NET (2001-2018)
https://www.asp-solutions.de/ - IT Beratung, Softwareentwicklung, Remotesupport -
Hallo,
ja Danke, ich sehe aber immer nur zuhauf Beispiele mit *zwei* Tabellen. Ich will aber mehr als zwei Tabellen verbinden, und zwar so wie oben beschrieben. Ich versuche es schon geraume Zeit und bekomme nur Unsinn heraus, und ich weiss nicht woran es liegt.
Wie kann ich wie oben beschrieben mehrere outer joins mit der gleichen Tabelle bilden?
Dank
Wolfgang
-
Hi Wolfgang,
warum reihst Du die Join nicht aneinander und speicherst das Ergebnis in einer Ergebnisklasse, z.B. so:class Demo { internal void Execute() { DataTable dt1 = new DataTable(); dt1.Columns.Add("ID", typeof(int)); dt1.Columns.Add("Wert1", typeof(string)); for (int i = 1; i < 11; i++) dt1.Rows.Add(i, $"Wert 1 {i}"); DataTable dt2 = new DataTable(); dt2.Columns.Add("ID", typeof(int)); dt2.Columns.Add("Wert2", typeof(string)); for (int i = 1; i < 11; i += 2) dt2.Rows.Add(i, $"Wert 2 {i}"); DataTable dt3 = new DataTable(); dt3.Columns.Add("ID", typeof(int)); dt3.Columns.Add("Wert3", typeof(string)); for (int i = 1; i < 11; i += 3) dt3.Rows.Add(i, $"Wert 3 {i}"); var res = from r1 in dt1.AsEnumerable() join r2 in dt2.AsEnumerable() on r1.Field<int>("ID") equals (int)r2["ID"] into zws1 from r1a in zws1.DefaultIfEmpty() join r3 in dt3.AsEnumerable() on r1.Field<int>("ID") equals (int)r3["ID"] into zws2 from r2a in zws2.DefaultIfEmpty() select new C123 { ID = r1.Field<int>("ID"), Wert1 = r1.Field<string>("Wert1"), Wert2 = (r1a == null) ? "<leer>" : r1a.Field<string>("Wert2"), Wert3 = (r2a == null) ? "<leer>" : r2a.Field<string>("Wert3") }; Console.WriteLine("--- Tabelle 1"); foreach (DataRow r1 in dt1.Rows) Console.WriteLine($"{r1["ID"]} - {r1["Wert1"]}"); Console.WriteLine("--- Tabelle 2"); foreach (DataRow r2 in dt2.Rows) Console.WriteLine($"{r2["ID"]} - {r2["Wert2"]}"); Console.WriteLine("--- Tabelle 3"); foreach (DataRow r3 in dt3.Rows) Console.WriteLine($"{r3["ID"]} - {r3["Wert3"]}"); Console.WriteLine("--- Ergebnis"); foreach (var item in res) Console.WriteLine($"{item.ID} - {item.Wert1} - {item.Wert2} - {item.Wert3}"); } public class C123 { public int ID { get; set; } public string Wert1 { get; set; } public string Wert2 { get; set; } public string Wert3 { get; set; } } }
Du kannst anstelle den in einer DataTable versteckten Eigenschaften einer DataRow eigene Typen (=Klassen) nutzen, wie das auch im Entity Framework gemacht wird. In diesem Fall kannst Du in der Ergebnisklasse die Werte ablegen. Hier mal eine Demo als Anregung:
class Demo { internal void Execute() { DataTable dt1 = new DataTable(); dt1.Columns.Add("ID", typeof(int)); dt1.Columns.Add("Wert1", typeof(string)); for (int i = 1; i < 11; i++) dt1.Rows.Add(i, $"Wert 1 {i}"); List<C1> l1 = new List<C1>(); foreach (var item in dt1.AsEnumerable()) l1.Add(new C1() { ID = item.Field<int>("ID"), Wert1 = item.Field<string>("Wert1") }); DataTable dt2 = new DataTable(); dt2.Columns.Add("ID", typeof(int)); dt2.Columns.Add("Wert2", typeof(string)); for (int i = 1; i < 11; i += 2) dt2.Rows.Add(i, $"Wert 2 {i}"); List<C2> l2 = new List<C2>(); foreach (var item in dt2.AsEnumerable()) l2.Add(new C2() { ID = item.Field<int>("ID"), Wert2 = item.Field<string>("Wert2") }); DataTable dt3 = new DataTable(); dt3.Columns.Add("ID", typeof(int)); dt3.Columns.Add("Wert3", typeof(string)); for (int i = 1; i < 11; i += 3) dt3.Rows.Add(i, $"Wert 3 {i}"); List<C3> l3 = new List<C3>(); foreach (var item in dt3.AsEnumerable()) l3.Add(new C3() { ID = item.Field<int>("ID"), Wert3 = item.Field<string>("Wert3") }); var res = from r1 in l1 join r2 in l2 on r1.ID equals r2.ID into zws1 from r1a in zws1.DefaultIfEmpty() join r3 in l3 on r1.ID equals r3.ID into zws2 from r2a in zws2.DefaultIfEmpty() select new C123 { ID = r1.ID, Wert1 = r1.Wert1, Wert2 = (r1a == null) ? "<leer>" : r1a.Wert2, Wert3 = (r2a == null) ? "<leer>" : r2a.Wert3 }; Console.WriteLine("--- Tabelle 1"); foreach (DataRow r1 in dt1.Rows) Console.WriteLine($"{r1["ID"]} - {r1["Wert1"]}"); Console.WriteLine("--- Tabelle 2"); foreach (DataRow r2 in dt2.Rows) Console.WriteLine($"{r2["ID"]} - {r2["Wert2"]}"); Console.WriteLine("--- Tabelle 3"); foreach (DataRow r3 in dt3.Rows) Console.WriteLine($"{r3["ID"]} - {r3["Wert3"]}"); Console.WriteLine("--- Ergebnis"); foreach (var item in res) Console.WriteLine($"{item.ID} - {item.Wert1} - {item.Wert2} - {item.Wert3}"); } public class C1 { public int ID { get; set; } public string Wert1 { get; set; } } public class C2 { public int ID { get; set; } public string Wert2 { get; set; } } public class C3 { public int ID { get; set; } public string Wert3 { get; set; } } public class C123 { public int ID { get; set; } public string Wert1 { get; set; } public string Wert2 { get; set; } public string Wert3 { get; set; } } }
Für 4 Tabellen mit DataTable könnte die Lösung so aussehen.
class Demo { internal void Execute() { DataTable dt1 = new DataTable(); dt1.Columns.Add("ID", typeof(int)); dt1.Columns.Add("Wert1", typeof(string)); for (int i = 1; i < 21; i++) dt1.Rows.Add(i, $"Wert 1 {i}"); DataTable dt2 = new DataTable(); dt2.Columns.Add("ID", typeof(int)); dt2.Columns.Add("Wert2", typeof(string)); for (int i = 1; i < 21; i += 2) dt2.Rows.Add(i, $"Wert 2 {i}"); DataTable dt3 = new DataTable(); dt3.Columns.Add("ID", typeof(int)); dt3.Columns.Add("Wert3", typeof(string)); for (int i = 1; i < 21; i += 3) dt3.Rows.Add(i, $"Wert 3 {i}"); DataTable dt4 = new DataTable(); dt4.Columns.Add("ID", typeof(int)); dt4.Columns.Add("Wert4", typeof(string)); for (int i = 1; i < 21; i += 5) dt4.Rows.Add(i, $"Wert 4 {i}"); var res = from r1 in dt1.AsEnumerable() join r2 in dt2.AsEnumerable() on r1.Field<int>("ID") equals (int)r2["ID"] into zws1 from r1a in zws1.DefaultIfEmpty() join r3 in dt3.AsEnumerable() on r1.Field<int>("ID") equals (int)r3["ID"] into zws2 from r2a in zws2.DefaultIfEmpty() join r4 in dt4.AsEnumerable() on r1.Field<int>("ID") equals (int)r4["ID"] into zws3 from r3a in zws3.DefaultIfEmpty() select new C1234 { ID = r1.Field<int>("ID"), Wert1 = r1.Field<string>("Wert1"), Wert2 = (r1a == null) ? "<leer>" : r1a.Field<string>("Wert2"), Wert3 = (r2a == null) ? "<leer>" : r2a.Field<string>("Wert3"), Wert4 = (r3a == null) ? "<leer>" : r3a.Field<string>("Wert4") }; Console.WriteLine("--- Tabelle 1"); foreach (DataRow r1 in dt1.Rows) Console.WriteLine($"{r1["ID"]} - {r1["Wert1"]}"); Console.WriteLine("--- Tabelle 2"); foreach (DataRow r2 in dt2.Rows) Console.WriteLine($"{r2["ID"]} - {r2["Wert2"]}"); Console.WriteLine("--- Tabelle 3"); foreach (DataRow r3 in dt3.Rows) Console.WriteLine($"{r3["ID"]} - {r3["Wert3"]}"); Console.WriteLine("--- Ergebnis"); foreach (var item in res) Console.WriteLine($"{item.ID} - {item.Wert1} - {item.Wert2} - {item.Wert3} - {item.Wert4}"); } public class C1234 { public int ID { get; set; } public string Wert1 { get; set; } public string Wert2 { get; set; } public string Wert3 { get; set; } public string Wert4 { get; set; } } }
--
Viele Grüsse
Peter Fleischer (ehem. MVP für Developer Technologies)
Meine Homepage mit Tipps und Tricks
- Bearbeitet Peter Fleischer Mittwoch, 21. November 2018 08:50
- Als Antwort vorgeschlagen Ivan DragovMicrosoft contingent staff, Moderator Montag, 26. November 2018 10:11
- Als Antwort markiert Ivan DragovMicrosoft contingent staff, Moderator Montag, 3. Dezember 2018 10:28
-
Hallo Wolfgang,
in Addition zu Peters Antwort könntest Du die Tables auch mit DtataTable.Merge verbinden.
Ich habe diverse Lieferlisten abzuarbeiten, lese diese jeweils einzeiln ein, muss hier und dort etwas rein- und rauswerfen.Dann wird "gemerged" und bei Fehlern wird es vorher entsprechend der Quelle geworfen. Quell-Tables werden gelöscht.
Danach kann kann man zügig weiterarbeiten.Falls es nicht LINQ sein muss, obwohl Deine Frage ja LINQ war
Gruß
Raimo
- Bearbeitet RaimoBecker Mittwoch, 21. November 2018 10:36 TYPO