webentwicklung-frage-antwort-db.com.de

Erstellen Sie eine kombinierte DataTable aus zwei mit LINQ verbundenen DataTables. C #

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.

22
Robin Day

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.

20
Aaronaught

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.

5
suryakiran

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)

1
shahkalpesh
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 
};
0
Roger Lipscombe