web-dev-qa-db-ja.com

LINQで結合された2つのDataTableから結合されたDataTableを作成します。 C#

dataTable1dataTable2に2つの単純なSQLクエリを入力する次のコードがあります。dataTableSqlJoinedは同じテーブルから入力されますが、結合されます。

SQLを使用して作成されたかのようにdataTableLinqJoinedを作成できるLINQクエリを記述しようとしています。以下の私の例では、dataTable1からの値のみを返します。

私が抱えている問題は、linqクエリのSELECTに何を入れるかです。両方のDataRowからすべての列を含む新しいDataRowを作成するにはどうすればよいですか。実行時まで、クエリの正確な列名/スキーマはわかりません。

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();

もう少し背景として、結合されたクエリは非常にDB集約的であり、パフォーマンスの問題を引き起こしています。最初のクエリによって返されるデータはかなり静的であり、大量にキャッシュされる可能性があります。 2番目のクエリによって返されるデータは常に変化しますが、実行が高速であるため、キャッシュする必要はありません。また、結合されたDataTableの受け渡しに依存するコードがたくさんあるため、異なる形式でデータを渡すために利用できる実行可能なオプションは多くありません。

22
Robin Day

このページはもう見ましたか?

方法:Visual C#.NETでDataSet JOINヘルパークラスを実装する

そのアプローチではLINQyが十分でない場合は、行データをオブジェクト配列に分割できます。

_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);
_

それはあなたがそれを作ることができるようになるだろうと同じくらい簡潔だと思います、そして私は理由を説明します:それはスキーマです。

DataRowは独立したオブジェクトではありません。それはそれを所有するDataTableに依存し、それなしでは生きられません。 「切断された」DataRowを作成するためのサポートされている方法はありませんCopyToDataTable()拡張メソッドは、1つのDataTableに既に存在する行に対して機能し、ソースからスキーマをコピーするだけです(すべてのDataRowは、行自体をコピーする前に、親Tableへの参照を持っていることに注意してください)(おそらくImportRowを使用します) 、実際にはReflectorを開いて確認していません)。

この場合、作成する必要のある新しいスキーマがあります。 (新しい)行を作成する前に、それらを保持するテーブルを作成する必要があります first 、つまり、上記のメソッドの上部に少なくとも3行のコードを記述します。

その後、最終的に行を作成できます。ただし、DataTableとそれに関連付けられているDataRowCollectionは、一度に複数の行を追加するメソッドを公開していないため、一度に1つしか作成できません。もちろん、DataRowCollectionに独自の拡張メソッドを追加して、この「見栄え」を良くすることができます。

_public static void AddRange(this DataRowCollection rc,
    IEnumerable<object[]> tuples)
{
    foreach (object[] data in tuples)
        rc.Add(tuples);
}
_

次に、最初のメソッドでforeachを削除して、次のものに置き換えることができます。

_targetTable.Rows.AddRange(rowData);
_

それは実際には冗長性を移動するだけであり、それを排除するものではありません。

結論としては、レガシーのDataSetクラス階層を使用している限り、常に少しばかり問題が発生します。 LinqからDataSetへの拡張は素晴らしいですが、これらは拡張にすぎず、上記の制限を変更することはできません。

20
Aaronaught

それは素晴らしかったです。しかし、LINQyコードにいくつかの拡張機能を追加したいと思います。 dataTable2からターゲットテーブルに列を追加する際、(結合先の)ターゲットテーブルに既にいくつかの列が存在している可能性があります。さあ、行きます。

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);

これが私のような人たちに役立つことを願っています。

5
suryakiran

バカみたいに聞こえたらごめんなさい。

私は、ファイナルテーブルを準備する必要があると思います(テーブルAとテーブルBのすべてのフィールドを含む)。
そして、LINQを使用する代わりに、結合を実行してから、結果に対してForEachを実行し、最終的なデータテーブルに値を挿入します。

疑似コード

dt1.Join(dt2).Where(...)。ForEach(row =>匿名オブジェクトのコンテンツを読み取り、finalTable.Rowsに追加するコード)

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