Ich habe den folgenden Code, der dataTable1
und dataTable2
mit zwei einfachen SQL-Abfragen füllt. dataTableSqlJoined
wird aus denselben Tabellen gefüllt, aber zusammengefügt.
Ich versuche, eine LINQ-Abfrage zu schreiben, die die dataTableLinqJoined
so erstellen kann, als wäre sie mit SQL erstellt worden. In meinem folgenden Beispiel werden nur die Werte von dataTable1 zurückgegeben.
Das Problem, das ich habe, ist, was in die Variable SELECT
der linq-Abfrage eingefügt werden soll. Wie kann ich eine neue DataRow erstellen, die alle Spalten aus beiden DataRows enthält. Ich werde die genauen Spaltennamen/Schemata der Abfragen bis zur Laufzeit nicht kennen.
sqlCommand = new SqlCommand("SELECT ID, A, B FROM Table1", sqlConnection, sqlTransaction);
sqlAdapter = new SqlDataAdapter(sqlCommand);
DataTable dataTable1 = new DataTable();
sqlAdapter.Fill(dataTable1);
sqlCommand = new SqlCommand("SELECT ID, C, D FROM Table2", sqlConnection, sqlTransaction);
sqlAdapter = new SqlDataAdapter(sqlCommand);
DataTable dataTable2 = new DataTable();
sqlAdapter.Fill(dataTable2);
sqlCommand = new SqlCommand("SELECT Table1.ID, A, B, Table2.ID, C, D FROM Table1 INNER JOIN Table2 ON Table1.ID = Table2.ID", sqlConnection, sqlTransaction);
sqlAdapter = new SqlDataAdapter(sqlCommand);
DataTable dataTableSqlJoined = new DataTable();
sqlAdapter.Fill(dataTableSqlJoined);
var dataRows =
from
dataRows1 in dataTable1.AsEnumerable()
join
dataRows2 in dataTable2.AsEnumerable()
on
dataRows1.Field<int>("ID") equals dataRows2.Field<int>("ID")
select
dataRows1; // + dataRows2;
DataTable dataTableLinqJoined = dataRows.CopyToDataTable();
Für etwas mehr Hintergrund ist die kombinierte Abfrage sehr DB-intensiv und verursacht Leistungsprobleme. Die von der ersten Abfrage zurückgegebenen Daten sind ziemlich statisch und können stark zwischengespeichert werden. Die von der zweiten Abfrage zurückgegebenen Daten ändern sich ständig, sind jedoch schnell ausgeführt und müssen daher nicht zwischengespeichert werden. Es gibt auch eine Menge Code, der von der Übergabe der kombinierten DataTable abhängig ist. Daher gibt es nicht viele realisierbare Optionen, um die Daten in einem anderen Format zu übergeben.
Haben Sie sich diese Seite schon angesehen?
Gewusst wie: Implementieren einer DataSet JOIN-Helper-Klasse in Visual C # .NET
Wenn dieser Ansatz für LINQy nicht ausreichend ist, können Sie die Zeilendaten in Objektarrays aufteilen:
DataTable targetTable = dataTable1.Clone();
var dt2Columns = dataTable2.Columns.OfType<DataColumn>().Select(dc =>
new DataColumn(dc.ColumnName, dc.DataType, dc.Expression, dc.ColumnMapping));
targetTable.Columns.AddRange(dt2Columns.ToArray());
var rowData =
from row1 in dataTable1.AsEnumerable()
join row2 in dataTable2.AsEnumerable()
on row1.Field<int>("ID") equals row2.Field<int>("ID")
select row1.ItemArray.Concat(row2.ItemArray).ToArray();
foreach (object[] values in rowData)
targetTable.Rows.Add(values);
Ich denke, das ist so knapp, wie man es schaffen kann, und ich erkläre warum: es ist das Schema.
Ein DataRow
ist kein unabhängiges Objekt. es hängt von seinem Besitz DataTable
ab und kann nicht ohne es leben. Es gibt keine unterstützte Möglichkeit, eine "getrennte Verbindung" zu erstellen. DataRow
; Die Erweiterungsmethode CopyToDataTable()
arbeitet mit Zeilen, die bereits in einem DataTable
vorhanden sind, und kopiert einfach das Schema aus der Quelle (denken Sie daran, dass jedes DataRow
einen Verweis auf das übergeordnete Table
hat), bevor Sie die Zeilen selbst kopieren (höchstwahrscheinlich nicht mit ImportRow
) tatsächlich Reflektor geöffnet zu prüfen).
In diesem Fall haben Sie ein neues Schema, das Sie erstellen müssen. Bevor Sie eine (neue) Zeile erstellen können, müssen Sie die Tabelle erstellen, die first enthält. Das bedeutet, dass Sie mindestens die 3 Codezeilen oben in der oben beschriebenen Methode schreiben.
Dann können Sie schließlich die Zeilen erstellen - aber nur eine nach dem anderen, da DataTable
und das zugehörige DataRowCollection
keine Methoden zum Hinzufügen mehrerer Zeilen gleichzeitig bereitstellen. Sie können natürlich Ihre eigene Erweiterungsmethode für DataRowCollection
hinzufügen, um dieses "Aussehen" schöner zu gestalten:
public static void AddRange(this DataRowCollection rc,
IEnumerable<object[]> tuples)
{
foreach (object[] data in tuples)
rc.Add(tuples);
}
Dann könnten Sie foreach
in der ersten Methode loswerden und durch Folgendes ersetzen:
targetTable.Rows.AddRange(rowData);
Das ist wirklich nur das Verschieben der Ausführlichkeit, nicht das Beseitigen.
Fazit: Solange Sie mit der älteren Klassenhierarchie DataSet
arbeiten, wird es immer ein bisschen scheißt. Die Linq-zu-DataSet-Erweiterungen sind Nice, aber sie sind nur Erweiterungen und können die obigen Einschränkungen nicht ändern.
Das war großartig. Ich möchte jedoch einige Verbesserungen an Ihrem LINQy-Code hinzufügen. Beim Hinzufügen von Spalten aus der Datentabelle2 zur Zieltabelle besteht die Möglichkeit, dass nur wenige Spalten in der Zieltabelle vorhanden sind (an der wir teilnehmen). Auf geht's.
DataTable targetTable = dataTable1.Clone();
var dt2Columns = dataTable2.Columns.OfType<DataColumn>().Select(dc =>
new DataColumn(dc.ColumnName, dc.DataType, dc.Expression, dc.ColumnMapping));
var dt2FinalColumns=from dc in dt2Columns.AsEnumerable()
where targetTable.Columns.Contains(dc.ColumnName) == false
select dc;
targetTable.Columns.AddRange(dt2FinalColumns.ToArray());
var rowData =from row1 in dataTable1.AsEnumerable()
join row2 in dataTable2.AsEnumerable()
on row1.Field<int>("ID") equals row2.Field<int>("ID")
select row1.ItemArray.Concat(row2.ItemArray.Where(r2=> row1.ItemArray.Contains(r2)==false)).ToArray();
foreach (object[] values in rowData)
targetTable.Rows.Add(values);
Ich hoffe, das wäre hilfreich für die Jungs wie mich.
Verzeihen Sie, wenn ich mich wie ein Idiot anhöre.
Ich denke, Sie sollten die endgültige Tabelle bereithalten (mit allen Feldern von Tabelle A und Tabelle B).
Statt LINQ zu verwenden, führen Sie einen Join aus, und führen Sie dann eine ForEach
für das Ergebnis aus, und fügen Sie den Wert in die endgültige Datentabelle ein.
Pseudocode :
dt1.Join (dt2) .Where (...). ForEach (Zeile => Code, um den Inhalt eines anonymen Objekts zu lesen und zu finalTable.Rows hinzuzufügen)
select new {
ID = dataRows1.ID, // no need to select dataRows2.ID, because of JOIN.
A = dataRows1.A,
B = dataRows1.B,
C = dataRows2.C,
D = dataRows2.D
};