2016-08-04 56 views
2

動的な数の列に2つのDataTableを結合しようとしています。私は以下のコードまで行ってきました。問題は、結合のON文です。リスト "joinColumnNames"に含まれる列名の数に基づいて、この動的化を行う方法を教えてください。動的な列数で2つのDataTableを結合する

私は何らかの種類の式ツリーを構築する必要があると考えていましたが、複数の結合列と各列のプロパティを持たないDataRowオブジェクトでこれを行う方法の例は見つかりません。

プルX:

Country  Company  DateId Sales 
United States Test1 Ltd 20160722 $25 

Canada   Test3 Ltd 20160723 $30 

Italy   Test4 Ltd 20160724 $40 

India   Test2 Ltd 20160725 $35 

プルY:

Country  Company  DateId Downloads 
United States Test1 Ltd 20160722 500 

Mexico   Test2 Ltd 20160723 300 

Italy   Test4 Ltd 20160724 900 
3を用いて入力/出力を表示するために具体的な例を追加

private DataTable Join(List<string> joinColumnNames, DataTable pullX, DataTable pullY) 
{ 
    DataTable joinedTable = new DataTable(); 

    // Add all the columns from pullX 
    foreach (string colName in joinColumnNames) 
    { 
     joinedTable.Columns.Add(pullX.Columns[colName]); 
    } 
    // Add unique columns from PullY 
    foreach (DataColumn col in pullY.Columns) 
    { 
     if (!joinedTable.Columns.Contains((col.ColumnName))) 
     { 
      joinedTable.Columns.Add(col); 
     } 
    } 

    var Join = (from PX in pullX.AsEnumerable() 
       join PY in pullY.AsEnumerable() on 
       // This must be dynamic and join on every column mentioned in joinColumnNames 
       new { A = PX[joinColumnNames[0]], B = PX[joinColumnNames[1]] } equals new { A = PY[joinColumnNames[0]], B = PY[joinColumnNames[1]] } 
       into Outer 
       from PY in Outer.DefaultIfEmpty<DataRow>(pullY.NewRow()) 
       select new { PX, PY }); 

    foreach (var item in Join) 
    { 
     DataRow newRow = joinedTable.NewRow(); 
     foreach (DataColumn col in joinedTable.Columns) 
     { 
      var pullXValue = item.PX.Table.Columns.Contains(col.ColumnName) ? item.PX[col.ColumnName] : string.Empty; 
      var pullYValue = item.PY.Table.Columns.Contains(col.ColumnName) ? item.PY[col.ColumnName] : string.Empty; 
      newRow[col.ColumnName] = (pullXValue == null || string.IsNullOrEmpty(pullXValue.ToString())) ? pullYValue : pullXValue; 
     } 
     joinedTable.Rows.Add(newRow); 
    } 

    return joinedTable; 
} 

列(国、会社、およびDateId)に参加します

結果:

 
Country  Company  DateId Sales Downloads 
United States Test1 Ltd 20160722 $25  500 

Canada   Test3 Ltd 20160723 $30 

Mexico   Test2 Ltd 20160723    300 

Italy   Test4 Ltd 20160724 $40  900 

India   Test2 Ltd 20160725 $35  
+1

サンプルの入力と出力はできますか? "joinColumnNames"にない "pullX"の列はどうでしょうか?これらの列を出力に入れたくないのですか? –

+0

はい、出力にはxとyのすべての列が含まれている必要があります。具体的な例を追加しました。 – typheon

答えて

0
var Join = 
    from PX in pullX.AsEnumerable() 
    join PY in pullY.AsEnumerable() 
    on  string.Join("\0", joinColumnNames.Select(c => PX[c])) 
    equals string.Join("\0", joinColumnNames.Select(c => PY[c])) 
    into Outer 
    from PY in Outer.DefaultIfEmpty<DataRow>(pullY.NewRow()) 
    select new { PX, PY }; 

もう一つの方法は、DataSetの両方DataTableを持っているとあなたがオブジェクトにLINQを使用しているのでDataRelation
How To: Use DataRelation to perform a join on two DataTables in a DataSet?

+0

はい!ありがとう、それは美しく働いた。そして、それは私が思っていたよりもはるかに簡単でした:) – typheon

+0

私は参加したことがないので、それがうまくいって、それをテストしませんでした:] – Slai

1

を使用することで、式ツリーを使用する必要はありません。カスタム等価比較ツールで問題を解決できます。

特定の列の値に基づいて、2つのDataRowオブジェクト間の等価性を比較できる等値比較器を作成します。次に例を示します。

public class MyEqualityComparer : IEqualityComparer<DataRow> 
{ 
    private readonly string[] columnNames; 

    public MyEqualityComparer(string[] columnNames) 
    { 
     this.columnNames = columnNames; 
    } 

    public bool Equals(DataRow x, DataRow y) 
    { 
     return columnNames.All(cn => x[cn].Equals(y[cn])); 
    } 

    public int GetHashCode(DataRow obj) 
    { 
     unchecked 
     { 
      int hash = 19; 
      foreach (var value in columnNames.Select(cn => obj[cn])) 
      { 
       hash = hash * 31 + value.GetHashCode(); 
      } 
      return hash; 
     } 
    } 
} 

次に、あなたはこのように参加するためにそれを使用することができます:

public class TwoRows 
{ 
    public DataRow Row1 { get; set; } 
    public DataRow Row2 { get; set; } 
} 

private static List<TwoRows> LeftOuterJoin(
    List<string> joinColumnNames, 
    DataTable leftTable, 
    DataTable rightTable) 
{ 
    return leftTable 
     .AsEnumerable() 
     .GroupJoin(
      rightTable.AsEnumerable(), 
      l => l, 
      r => r, 
      (l, rlist) => new {LeftValue = l, RightValues = rlist}, 
      new MyEqualityComparer(joinColumnNames.ToArray())) 
     .SelectMany(
      x => x.RightValues.DefaultIfEmpty(rightTable.NewRow()), 
      (x, y) => new TwoRows {Row1 = x.LeftValue, Row2 = y}) 
     .ToList(); 
} 

私はあなたが使用できるとは思わないので、私はメソッドの構文を使用していますのでご注意くださいそうでなければカスタム等価比較器。

このメソッドは、完全外部結合ではなく、左外部結合を行います。あなたが指定した例に基づいて、完全な外部結合が必要なようです。これを行うには、2つの左外部結合を実行する必要があります(このanswerを参照)。完全な方法は次のようになります。

private static DataTable FullOuterJoin(
    List<string> joinColumnNames, 
    DataTable pullX, 
    DataTable pullY) 
{ 
    var pullYOtherColumns = 
     pullY.Columns 
      .Cast<DataColumn>() 
      .Where(x => !joinColumnNames.Contains(x.ColumnName)) 
      .ToList(); 

    var allColumns = 
     pullX.Columns 
      .Cast<DataColumn>() 
      .Concat(pullYOtherColumns) 
      .ToArray(); 

    var allColumnsClone = 
     allColumns 
      .Select(x => new DataColumn(x.ColumnName, x.DataType)) 
      .ToArray(); 

    DataTable joinedTable = new DataTable(); 

    joinedTable.Columns.AddRange(allColumnsClone); 

    var first = 
     LeftOuterJoin(joinColumnNames, pullX, pullY); 

    var resultRows = new List<DataRow>(); 

    foreach (var item in first) 
    { 
     DataRow newRow = joinedTable.NewRow(); 
     foreach (DataColumn col in joinedTable.Columns) 
     { 
      var value = pullX.Columns.Contains(col.ColumnName) 
       ? item.Row1[col.ColumnName] 
       : item.Row2[col.ColumnName]; 

      newRow[col.ColumnName] = value; 
     } 
     resultRows.Add(newRow); 
    } 

    var second = 
     LeftOuterJoin(joinColumnNames, pullY, pullX); 

    foreach (var item in second) 
    { 
     DataRow newRow = joinedTable.NewRow(); 
     foreach (DataColumn col in joinedTable.Columns) 
     { 
      var value = pullY.Columns.Contains(col.ColumnName) 
       ? item.Row1[col.ColumnName] 
       : item.Row2[col.ColumnName]; 

      newRow[col.ColumnName] = value; 
     } 
     resultRows.Add(newRow); 
    } 

    var uniqueRows = 
     resultRows 
      .Distinct(
       new MyEqualityComparer(
        joinedTable.Columns 
         .Cast<DataColumn>() 
         .Select(x => x.ColumnName) 
         .ToArray())); 

    foreach (var uniqueRow in uniqueRows) 
     joinedTable.Rows.Add(uniqueRow); 


    return joinedTable; 
} 

どのようにして列を複製するかにも注意してください。 2つのテーブルで同じ列オブジェクトを使用することはできません。

+0

非常に徹底した完全な解決策、私はそれが好きです。 列のクローンの問題を指摘していただき、ありがとうございました。私は非常に似たようなことをした。私はxとyを結合して左にyとxを結合した後、結合を使いました。 – typheon

関連する問題