[英]LINQ join to return dynamic column list
我正在寻找一种从两个数据表的 LINQ 连接返回动态列列表的方法。
首先,这不是重复的。 我已经研究和丢弃了:
如何执行行为与物理数据库内部连接完全相同的 LINQ 连接?
(和许多其他人)
这是我的出发点:
public static DataTable JoinDataTables(DataTable dt1, DataTable dt2, string table1KeyField, string table2KeyField, string[] columns) {
DataTable result = ( from dataRows1 in dt1.AsEnumerable()
join dataRows2 in dt2.AsEnumerable()
on dataRows1.Field<string>(table1KeyField) equals dataRows2.Field<string>(table2KeyField)
[...I NEED HELP HERE with the SELECT....]).CopyToDataTable();
return result;
}
一些注意事项和要求:
DataTable
的大型 CSV 文件(500K+ 条记录)。DataTable
中的那些列。 如果传入的两个数据表碰巧有一个名称相同的列,并且如果该列在我的列名数组中,只需传回任一列,因为在这种情况下两列之间的数据将相同。JoinDataTables()
在我的系统中从许多不同的地方被调用,以便加入各种各样的 CSVs-turned-datatables,并且每个 CSV 文件都有非常不同的列。DataTable
表中返回所有列——只是我在columns
数组中指定的columns
。 所以假设,在调用JoinDataTables()
之前,我有以下 2 个数据表:
Table: T1
T1A T1B T1C T1D
==================
10 AA H1 Foo1
11 AB H1 Foo2
12 AA H2 Foo1
13 AB H2 Foo2
Table: T2
T2A T2X T2Y T2Z
==================
12 N1 O1 Yeah1
17 N2 O2 Yeah2
18 N3 O1 Yeah1
19 N4 O2 Yeah2
现在假设我们像这样连接这两个表: ON T1.T1A = T2.T2A
select * from [join]
这产生了这个结果集:
T1A T1B T1C T1D T2A T2X T2Y T2Z
====================================
12 AA H2 Foo1 12 N1 O1 Yeah1
请注意,连接仅产生 1 行。
现在是我问题的关键。 假设对于给定的用例,我只想从此连接返回 4 列:T1A、T1D、T2A 和 T2Y。 所以我的结果集将如下所示:
T1A T1D T2A T2Y
==================
12 Foo1 12 O1
我希望能够像这样调用我的JoinDataTables
函数:
DataTable dt = JoinDataTables(dt1, dt2, "T1A", "T2A", new string[] {"T1A", "T1D", "T2A", "T2Y"});
牢记性能以及我不想遍历记录的事实(因为它对于大量数据集很慢),如何实现? (连接已经运行良好,现在我只需要一个正确的select
段(无论是通过new{..}
还是您认为的任何方式))。
我不能接受在函数内部带有硬编码列列表的解决方案。 我在SO
找到了这种方法的例子。
有任何想法吗?
编辑:我每次都可以恢复所有列,但是我为包含所有列所做的每一次尝试都导致了某种完全外部连接或交叉连接,返回的记录数量比它应该的多。 所以,只要我没有得到交叉连接,我就愿意恢复所有列。
我不确定 500k 记录的性能,但这是一个尝试的解决方案。
由于您正在组合来自不同表的DataRow
的两个子集,因此没有简单的操作可以创建子集或从子集创建新的DataTable
(尽管我有一个扩展方法来展平IEnumerable<anon>
where anon = new { DataRow1, DataRow2, ... }
来自连接,对您来说可能会很慢)。
相反,我使用请求的列预先创建了一个答案DataTable
,然后使用 LINQ 构建要添加为行的值数组。
public static DataTable JoinDataTables(DataTable dt1, DataTable dt2, string table1KeyField, string table2KeyField, string[] columns) {
var rtnCols1 = dt1.Columns.Cast<DataColumn>().Where(dc => columns.Contains(dc.ColumnName)).ToList();
var rc1 = rtnCols1.Select(dc => dc.ColumnName).ToList();
var rtnCols2 = dt2.Columns.Cast<DataColumn>().Where(dc => columns.Contains(dc.ColumnName) && !rc1.Contains(dc.ColumnName)).ToList();
var rc2 = rtnCols2.Select(dc => dc.ColumnName).ToList();
var work = from dataRows1 in dt1.AsEnumerable()
join dataRows2 in dt2.AsEnumerable()
on dataRows1.Field<string>(table1KeyField) equals dataRows2.Field<string>(table2KeyField)
select (from c1 in rc1 select dataRows1[c1]).Concat(from c2 in rc2 select dataRows2[c2]).ToArray();
var result = new DataTable();
foreach (var rc in rtnCols1)
result.Columns.Add(rc.ColumnName, rc.DataType);
foreach (var rc in rtnCols2)
result.Columns.Add(rc.ColumnName, rc.DataType);
foreach (var rowVals in work)
result.Rows.Add(rowVals);
return result;
}
由于您使用的是查询语法,我也这样做了,但通常我可能会像这样进行select
:
select rc1.Select(c1 => dataRows1[c1]).Concat(rc2.Select(c2 => dataRows2[c2])).ToArray();
更新:通过替换rc1
和rc2
的定义,使用列序数而不是名称来索引每个DataRow
可能是值得的:
var rc1 = rtnCols1.Select(dc => dc.Ordinal).ToList();
var rc1Names = rtnCols1.Select(dc => dc.ColumnName).ToHashSet();
var rtnCols2 = dt2.Columns.Cast<DataColumn>().Where(dc => columns.Contains(dc.ColumnName) && !rc1Names.Contains(dc.ColumnName)).ToList();
var rc2 = rtnCols2.Select(dc => dc.Ordinal).ToList();
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.