感谢您阅读本文。
这是我的目标: 我有两个我从Excel工作表中读取的数据表。数据表具有相同的模式(Datatable1中的列A,B,C,...与Datatable2中的列A,B,C,...具有相同类型的数据)。
我需要通过任意列比较表中的数据(即仅用于比较A列和C列,但我需要将数据保存在A,B,C,......,N列中)。
由于我从Excel工作表中读取这些内容,因此无法预期架构。例如,如果我加载一组不同的工作表,则比较列可能会有所不同。出于这个原因,我不能使用LINQ,这就像硬编码的SQL语句。
我需要执行相当于FULL OUTER JOIN的操作。我试图显示所有数据,包括从任何数据表中丢失的数据,这些数据不会出现在另一个数据表中。
我已经阅读了一些关于DataRelations的内容,但我不确定如何使用它们。
请提供示例代码。
提前致谢!
答案 0 :(得分:1)
给定一对具有任意列数的DataTable
,并且给定一个可以从两个DataTable
中的每一个创建合理类型的分组值的函数,您可以使用Linq为你做大部分的工作。
让我们从一个函数开始,从DataTable
中提取连接键。退回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());
}
当然,我们可以将一些操作简化为一个Linq查询,为我们完成99%的工作。如果听起来很有趣,我会把它留给你玩。
这里是完整的方法,作为具有连接键生成器的通用函数的扩展,使其合理通用:
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以上内容经过测试和验证,表明它正在做的事情。将它放在编译器中,看看你得到了什么。