从两个通过 LINQ 连接的 DataTable 创建组合的 DataTable。 C#

Posted

技术标签:

【中文标题】从两个通过 LINQ 连接的 DataTable 创建组合的 DataTable。 C#【英文标题】:Create combined DataTable from two DataTables joined with LINQ. C# 【发布时间】:2010-03-04 13:56:04 【问题描述】:

我有以下代码用两个简单的 SQL 查询填充 dataTable1dataTable2dataTableSqlJoined 是从同一个表中填充但连接在一起的。

我正在尝试编写一个可以创建dataTableLinqJoined 的 LINQ 查询,就好像它是使用 SQL 创建的一样。在下面的示例中,它仅返回 dataTable1 中的值。

我的问题是在 linq 查询的 SELECT 中放入什么。如何创建一个包含两个 DataRows 的所有列的新 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();

对于更多背景知识,组合查询非常占用数据库,并导致性能问题。第一个查询返回的数据是相当静态的,可以大量缓存。第二个查询返回的数据不断变化,但运行速度很快,因此不需要缓存。还有很多代码依赖于组合 DataTable 的传递,因此以不同格式传递数据的可行选项并不多。

【问题讨论】:

我有点好奇,一方面,您如何绝对知道这些查询的性能,但另一方面,您直到运行时才知道表结构。 查询是动态构建的。 【参考方案1】:

你看过这个页面了吗?

HOW TO: Implement a DataSet JOIN helper class in Visual C# .NET

如果这种方法对您来说不够 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,没有它就无法生存。 没有支持的方式来创建“断开连接”DataRowCopyToDataTable() 扩展方法适用于已经存在于一个 DataTable 中的行,只需从源中复制模式(请记住,每个 DataRow 都有对其父 Table 的引用),然后再复制行本身(很可能使用ImportRow,虽然我实际上并没有打开Reflector来检查)。

在这种情况下,您需要创建一个新架构。在创建任何(新)行之前,您需要先创建表来保存它们首先,这意味着在上述方法的顶部至少编写 3 行代码。

然后您最终可以创建行 - 但一次只能创建一个,因为 DataTable 及其关联的 DataRowCollection 不公开任何方法来一次添加多行。当然,您可以为 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 to DataSet 扩展很好,但它们只是扩展,不能改变上述限制。

【讨论】:

我看过那个页面,但不喜欢它不是 LINQy 的事实。您构建 targetTable 的示例很棒。谢谢。 对您的代码进行了小幅编辑,itemarray 需要是 Concat 而不是 Union。否则,它会删除任何具有相同值(空值)的列。不过除此之外。它工作得很好!谢谢! targetTable.Columns.AddRange(dt2Columns.ToArray());再次添加具有相同名称的列时出错。【参考方案2】:

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

希望这对像我这样的人有所帮助。

【讨论】:

将 dtcmets 更改为 dt2Columns。一个问题:如果我有多个表要加入,我如何加入表? var rowData =from row1 in dataTable1.AsEnumerable() join row2 in dataTable2.AsEnumerable() on row1.Field&lt;int&gt;("ID") equals row2.Field&lt;int&gt;("ID") join row3 in &lt;datatable&gt;.AsEnumerable() on &lt;condition&gt; 注意:列名被截断为 64 个字符。如果您的列名称在 64th 字符之前 之前是相同的,那么您的 targetTable 中将不会有该列。 ItemArray.Contains 的问题是它只是删除了实际项目,不处理重复列问题。在此示例中,它会删除 ID 相同的 ID,但它也会删除碰巧与另一个表中的值匹配的其他列的值。【参考方案3】:

如果我听起来像个白痴,请原谅我。

我认为,您应该准备好最终表(包含表 A 和表 B 的所有字段)。 并且,不要使用 LINQ,而是进行连接,然后对结果执行 ForEach 并将值插入到最终数据表中。

伪代码

dt1.Join(dt2).Where(...).ForEach(row => 代码读取匿名对象的内容并将其添加到 finalTable.Rows)

【讨论】:

我确实考虑过这样的事情。例如运行组合查询,而不是执行连接,只是将空值放入第二个查询的列中。然后只需运行第二个查询来填充它们。为答案+1,但我仍然希望有更“优雅”的东西。【参考方案4】:
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 
;

【讨论】:

直到运行时我才知道列名/模式。也没有“简单”的方法可以将此类型转换为 DataTable。 在问题中没有发现这一点。对不起。要将未知类型转换为 DataTable,您可以使用反射(或使用 Expression 做疯狂的事情:infoq.com/articles/expression-compiler)

以上是关于从两个通过 LINQ 连接的 DataTable 创建组合的 DataTable。 C#的主要内容,如果未能解决你的问题,请参考以下文章

LINQ to DataSet

从 Linq 查询返回类型化的 DataTable

如何使用 LINQ 从 DataTable 中以 IEnumerable<MODEL> 形式获取数据?

使用 DataContext 从 LINQ 查询中填充 DataTable 的最快方法

从 DbCommand.ExecuteReader 中的 Linq 选择中没有记录添加到 DataTable

如何从DataTable和LINQ获取分组的字段数组