【问题标题】:C# Full Outer Join Two DataTables Without LINQ没有 LINQ 的 C# 完全外连接两个数据表
【发布时间】:2014-09-14 04:40:06
【问题描述】:

感谢您阅读本文。

这是我的目标: 我有两个从 Excel 工作表中读取的数据表。数据表具有相同的架构(Datatable1 中的 A、B、C 列...与 Datatable2 中的 A、B、C 列...具有相同类型的数据)。

我需要按任意列比较表中的数据(即仅用于比较 A 列和 C 列,但我需要将数据保留在 A、B、C、...、N 列中)。

由于我是从 Excel 工作表中读取这些内容,因此无法预期架构。例如,如果我加载一组不同的工作表,比较列可能会有所不同。由于这个原因,我不能使用 LINQ,它就像一个硬编码的 SQL 语句。

我需要执行相当于 FULL OUTER JOIN 的操作。我正在尝试显示所有数据,包括任一数据表中未出现在另一个数据表中的缺失数据。

我已经阅读了一些关于 DataRelations 的内容,但我不确定如何使用它们。

请提供示例代码。

提前致谢!

【问题讨论】:

  • 这真的是全连接还是合并?
  • 这就像一个硬编码的 SQL 语句。
  • @Corey:我需要一个完整的外部联接(显示两个工作表中缺失数据的空值)。
  • @MatíasFidemraizer:LINQ 语句由 C# 代码组成,不能以编程方式修改。可以构建字符串 SQL 语句以匹配遇到的任何工作表架构。
  • @JessStuart 没有 cmets

标签: c# sql excel datatable datarelation


【解决方案1】:

给定一对具有任意列数的DataTables,并给定一个可以从两个DataTables 中的每一个创建一个合理类型的分组值的函数,您可以使用Linq 来完成大部分工作为你工作。

让我们从一个从DataTables 中提取连接键的函数开始。最好只返回一个object[],但它们的比较不好。但是,我们可以使用Tuple<object, object> 来完成此操作 - 这些功能非常适合此目的。如果您需要更多列,您可以添加更多列:P

// Produces a JoinKey as Tuple containing columns 'A' and 'C' (0 and 2)
public Tuple<object, object> GetJoinKey(DataRow row)
{
    return Tuple.Create(row[0], row[2]);
}

现在加入。我们不能直接进行完全外连接,但我们可以双向进行外连接,Union 结果:

// given DataTables table1 & table2:
var outerjoin = 
    (
        from row1 in table1.AsEnumerable()
        join row2 in table2.AsEnumerable() 
            on GetJoinKey(row1) equals GetJoinKey(row2)
            into matches
        from row2 in matches.DefaultIfEmpty()
        select new { key = GetJoinKey(row1), row1, row2 }
    ).Union(
        from row2 in table2.AsEnumerable()
        join row1 in table1.AsEnumerable()
            on GetJoinKey(row2) equals GetJoinKey(row1)
            into matches
        from row1 in matches.DefaultIfEmpty()
        select new { key = GetJoinKey(row2), row1, row2 }
    );

接下来,您必须创建一个合适的输出格式 - 一个 DataTable,它包含来自两个来源的所有行,以及一个包含有关键的一些信息的字段:

DataTable result = new DataTable();
// add column for string value of key:
result.Columns.Add("__key", typeof(string));
// add columns from table1:
foreach (var col in table1.Columns.OfType<DataColumn>())
    result.Columns.Add("T1_" + col.ColumnName, col.DataType);
// add columns from table2:
foreach (var col in table2.Columns.OfType<DataColumn>())
    result.Columns.Add("T2_" + col.ColumnName, col.DataType);

最后,从查询中填写表格:

var row1def = new object[table1.Columns.Count];
var row2def = new object[table2.Columns.Count];
foreach (var src in outerjoin)
{
    // extract values from each row where present
    var data1 = (src.row1 == null ? row1def : src.row1.ItemArray);
    var data2 = (src.row2 == null ? row2def : src.row2.ItemArray);

    // create row with key string and row values
    result.Rows.Add(new object[] { src.key.ToString() }.Concat(data1).Concat(data2).ToArray());
}

当然,我们可以缩短其中的几个操作来获得一个可以为我们完成 99% 工作的 Linq 查询。如果听起来很有趣,我会留给你玩。

这是完整的方法,作为连接键生成器的通用函数的扩展完成,使其相当通用:

public static DataTable FullOuterJoin<T>(this DataTable table1, DataTable table2, Func<DataRow, T> keygen)
{
    // perform inital outer join operation
    var outerjoin = 
        (
            from row1 in table1.AsEnumerable()
            join row2 in table2.AsEnumerable() 
                on keygen(row1) equals keygen(row2)
                into matches
            from row2 in matches.DefaultIfEmpty()
            select new { key = keygen(row1), row1, row2 }
        ).Union(
            from row2 in table2.AsEnumerable()
            join row1 in table1.AsEnumerable()
                on keygen(row2) equals keygen(row1)
                into matches
            from row1 in matches.DefaultIfEmpty()
            select new { key = keygen(row2), row1, row2 }
        );

    // Create result table
    DataTable result = new DataTable();
    result.Columns.Add("__key", typeof(string));
    foreach (var col in table1.Columns.OfType<DataColumn>())
        result.Columns.Add("T1_" + col.ColumnName, col.DataType);
    foreach (var col in table2.Columns.OfType<DataColumn>())
        result.Columns.Add("T2_" + col.ColumnName, col.DataType);

    // fill table from join query
    var row1def = new object[table1.Columns.Count];
    var row2def = new object[table2.Columns.Count];
    foreach (var src in outerjoin)
    {
        // extract values from each row where present
        var data1 = (src.row1 == null ? row1def : src.row1.ItemArray);
        var data2 = (src.row2 == null ? row2def : src.row2.ItemArray);

        // create row with key string and row values
        result.Rows.Add(new object[] { src.key.ToString() }.Concat(data1).Concat(data2).ToArray());
    }

    return result;
}

现在,如果表具有相同的架构(上面不关心),您可以做几乎完全相同的事情 - 修改结果表生成以克隆其中一个表,然后添加一些合并逻辑在加载循环中。

Here's a Gist 上面的测试和验证它正在做它所说的。把它放到你的编译器中,看看你会得到什么。

【讨论】:

  • 这太棒了!我希望我有足够的声望来投票!
  • 这必须是静态类吗?在这种情况下使用静态类有什么好处?
  • 我把它变成了一个静态方法,因为它不使用实例数据,所以(当你把它放在静态类中时)它可以作为DataTable的扩展方法...所以你可以使用table1.FullOuterJoin(table2, keygen); 代替FullOuterJoin(table1, table2, keygen);
  • 这非常适合在完全输出连接中复制数据,但它不会保留原始内容顺序。为了获得订单,我必须循环遍历每个表,比较键值,然后根据比较将每个表中的行复制到结果中。我非常感谢@corey 你的例子,因为我以前从未使用过元组。
  • 元组是很方便的东西,所以很高兴能向您介绍它们。
猜你喜欢
  • 1970-01-01
  • 2018-05-24
  • 2015-06-23
  • 2020-06-10
  • 2014-03-08
  • 1970-01-01
  • 2014-08-07
  • 1970-01-01
  • 2018-03-04
相关资源
最近更新 更多