我想你快到了,只需将你的 LINQ 查询输出转换为一个对象数组,然后将它作为一个单独的东西放入一个新的数据表中;请记住,LINQ 主要用于查询和返回结果集合,而不是修改现有内容:
使用LINQ左连接,手动输出列表,手动消费到数据表中
var query =
from ce in c.AsEnumerable()
join le in lookup.AsEnumerable() on c.Field<Guid>("Campaign ID") equals le.Field<Guid>("Campaign ID") into cele
from lenull in cele.DefaultIfEmpty()
select new object[]
{
ce.Field<Guid>("Campaign ID"),
ce.Field<string>("Description"),
ce.Field<int>("Number"), //don't know how your table has null here, maybe <int?>
lenull?.Field<string>("Name")
};
DataTable c = new DataTable(); //to hold results
c.Columns.Add("Campaign ID", typeof(Guid));
c.Columns.Add("Description");
c.Columns.Add("Number", typeof(int));
c.Columns.Add("Name");
foreach (var at in query)
c.Rows.Add(at);
因为 lenull 可能为 null,所以我使用 null 传播器来避免尝试获取 null 行的字段的 null 引用异常。我们也可以动态地执行此操作,无需反射,但速度要慢得多。对于以下示例,我使用了自己的一对简单数据表,设置如下:
//setup part
DataTable a = new DataTable();
a.Columns.Add("ID", typeof(int));
a.Columns.Add("Name", typeof(string));
a.Columns.Add("Age", typeof(int));
DataTable b = new DataTable();
var pk = b.Columns.Add("ID", typeof(int));
b.Columns.Add("Address", typeof(string));
b.Columns.Add("YearsAt", typeof(int));
b.PrimaryKey = new[] { pk };
a.Rows.Add(1, "John", 22);
a.Rows.Add(2, "Mary", 33);
a.Rows.Add(3, "Bill", 44);
b.Rows.Add(1, "JohnAddr", 3);
b.Rows.Add(2, "MaryAddr", 4);
Left Join with LINQ,手动输出列表,动态消费
var query =
from ae in a.AsEnumerable()
join be in b.AsEnumerable() on ae.Field<int>("ID") equals be.Field<int>("ID_") into aebe
from be2 in aebe.DefaultIfEmpty()
select new Dictionary<string, object>
{
{"ID", ae.Field<int>("ID")},
{"Name", ae.Field<string>("Name") },
{"Age", ae.Field<int>("Age") },
{"Address", be2?.Field<string>("Address") },
{"YearsAt", be2?.Field<int>("YearsAt") }
};
//setup datatable
DataTable c = new DataTable();
int keyCount = query.First().Keys.Count; //track columns needed to be added
foreach (var dict in query)
{
var ro = c.NewRow();
foreach (string key in dict.Keys)
{
if (keyCount > 0 && dict[key] != null && !c.Columns.Contains(key))
{ //if the column is not in the table, and the value isnt null (so we can deduce the type)
c.Columns.Add(key, dict[key].GetType());
keyCount--; //mark it as added. Eventually this will hit 0 and we won't evaluate the other two clauses
}
if (dict[key] != null) //don't store nulls
ro[key] = dict[key];
}
c.Rows.Add(ro);
}
当然,您可能会抱怨您仍然必须在 LINQ 查询选择中指定您想要的所有列。我们也可以让它变得动态:
Left Join with LINQ,动态输出列表,动态消费
var query =
from ae in a.AsEnumerable()
join be in b.AsEnumerable() on ae.Field<int>("ID") equals be.Field<int>("ID_") into aebe
from be2 in aebe.DefaultIfEmpty()
select MapToDict(ae, be2);
//setup datatable
DataTable c = new DataTable();
int keyCount = query.First().Keys.Count;
foreach (var dict in query)
{
//have we got all our columns addded yet?
var ro = c.NewRow();
foreach (string key in dict.Keys)
{
if (keyCount > 0 && dict[key] != null && !c.Columns.Contains(key))
{ //if the column is not in the table, and the value isnt null (so we can deduce the type)
c.Columns.Add(key, dict[key].GetType());
keyCount--; //mark it as added. Eventually this will hit 0 and we won't evaluate the other two clauses
}
if (dict[key] != null) //don't store nulls
ro[key] = dict[key];
}
c.Rows.Add(ro);
}
我从不喜欢 LINQ 中的 DataTables 连接,我一直更喜欢:
- 在b上建立主键
- 向与 b 的列名和类型重复的 a 添加新列(如果存在名称冲突,重命名 b 列添加一个 int)
- 遍历 a,调用 b.Find(a 中的某些列)
- 如果 find 没有返回 null,对于 b 中的每一列,将 a 行中的同名列设置为 find 给你的 b 行中的值
这是执行上述操作的代码:
使用循环左连接
//ensure unique named columns in b, and grow a's columns
foreach (DataColumn bcol in b.Columns) {
while (a.Columns.Contains(bcol.ColumnName))
bcol.ColumnName += "_";
a.Columns.Add(bcol.ColumnName, bcol.DataType);
}
//perform left join
foreach (DataRow aro in a.Rows) {
var f = b.Rows.Find(aro["ID"]);
if (f != null)
foreach (DataColumn bcol in b.Columns)
aro[bcol.ColumnName] = f[bcol];
}
将其转换为扩展方法可能相当简单,这样任何表都可以像 a.LeftJoin(b, aID: "ID", bID: "ID") 那样加入另一个表。如果你想要一个比简单等于更复杂的逻辑,那么就需要进行一些代码更改。
出于好奇,我背靠背尝试了所有 4 种方法,并为它们计时。在我的上下文中,循环比具有固定结构和硬编码列名的 LINQ 快约 2.5 倍,比使用字典使事物动态化快 4 倍:
for (int lc = 0; lc < 10; lc++) {
//setup 100K rows
DataTable a = new DataTable();
a.Columns.Add("ID", typeof(int));
a.Columns.Add("Name", typeof(string));
a.Columns.Add("Age", typeof(int));
DataTable b = new DataTable();
var pk = b.Columns.Add("ID", typeof(int));
b.Columns.Add("Address", typeof(string));
b.Columns.Add("YearsAt", typeof(int));
b.PrimaryKey = new[] { pk };
Random r = new Random();
for (int i = 0; i < 100000; i++)
{
a.Rows.Add(i, Guid.NewGuid().ToString(), r.Next(20, 99));
if (r.Next(0, 9) < 1)
b.Rows.Add(i, Guid.NewGuid().ToString(), r.Next(1, 10));
}
Stopwatch sw = Stopwatch.StartNew();
### INSERT CHOSEN METHOD HERE ###
sw.Stop();
Console.WriteLine($"Time: {sw.ElapsedMilliseconds}ms");
}
循环处理 100K 行的结果通常为 80 毫秒,LINQ 硬代码(手动选择、手动表)为 200 毫秒,LINQ 字典(动态内容)方法为 400 毫秒。