我的目标是:linq-join两个c#DataTables并从第一个表中选择所有列(不枚举列),只从第二个列中选择1列,并获得结果作为DataTable:
var result = from t in tt.AsEnumerable() join a in aa.AsEnumerable() on t.Field<DateTime>("time") equals a.Field<DateTime>("time")
select new { t, aa_col1 = a.Field<int>("col1") }
;
是的,该连接的结果无法转换为DataTable。
该问题的基本思想是:在编写linq-join时不要手动枚举tt的所有列(tt列的数量可能很大且是动态的)
我试图解决应用result.CopyToDataTable()方法的问题,但是编译器产生了错误:类型<anonymous type System.Data.DataRow t,int aa_col1>不能在通用方法“DataTableExtensions”中用作类型T的参数。 CopyToDataTable(IEnumerable的)”
下面显示了测试表tt和aa的代码:
DataTable tt = new DataTable();
tt.Columns.Add("time", typeof(DateTime));
tt.Columns.Add("col1", typeof(int));
tt.Columns.Add("col2", typeof(int));
tt.Columns.Add("col3", typeof(int));
DataRow dr = tt.NewRow();
dr["time"] = new DateTime(2018,10,5); dr["col1"] = 24; dr["col2"] = 14; dr["col3"] = 15;
tt.Rows.Add(dr);
dr = tt.NewRow();
dr["time"] = new DateTime(2018, 10, 6); dr["col1"] = 4; dr["col2"] = 43; dr["col3"] = 58;
tt.Rows.Add(dr);
dr = tt.NewRow();
dr["time"] = new DateTime(2018, 10, 6); dr["col1"] = 6; dr["col2"] = 3; dr["col3"] = 78;
tt.Rows.Add(dr);
dr = tt.NewRow();
dr["time"] = new DateTime(2018, 10, 7); dr["col1"] = 1; dr["col2"] = 4; dr["col3"] = 5;
tt.Rows.Add(dr);
// -----
DataTable aa = new DataTable();
aa.Columns.Add("time", typeof(DateTime));
aa.Columns.Add("col1", typeof(int));
aa.Columns.Add("col2", typeof(int));
aa.Columns.Add("col3", typeof(int));
DataRow rr = aa.NewRow();
rr["time"] = new DateTime(2018, 10, 4);
rr["col1"] = 6; rr["col2"] = 34; rr["col3"] = 66;
aa.Rows.Add(rr);
rr = aa.NewRow();
rr["time"] = new DateTime(2018, 10, 5); rr["col1"] = 7; rr["col2"] = 43; rr["col3"] = 98;
aa.Rows.Add(rr);
rr = aa.NewRow();
rr["time"] = new DateTime(2018, 10, 6); rr["col1"] = 6; rr["col2"] = 3; rr["col3"] = 3;
aa.Rows.Add(rr);
rr = aa.NewRow();
rr["time"] = new DateTime(2018, 10, 7); rr["col1"] = 16; rr["col2"] = 65; rr["col3"] = 12;
aa.Rows.Add(rr);
linq-join生成类型:{System.Linq.Enumerable.d__38f__AnonymousType0>}
请参阅显示“var result”的图:
https://yadi.sk/i/X8K9HIq5hnavCg
和https://yadi.sk/i/MaViq23zqpRDXw
和https://yadi.sk/i/Re0DFJdVl02RjA
join的目标结果应该是一个表,其中包含来自tt和aa的第一列的所有列 - aa.col1:
time col1 col2 col3 aa_col1
05.10.2018 0:00 24 14 15 7
06.10.2018 0:00 4 43 58 6
06.10.2018 0:00 6 3 78 6
07.10.2018 0:00 1 4 5 16
所以我有一个与DataTable
s连接的扩展方法,它使用了一个由DataRow
s组成的匿名对象并创建了一个新的DataTable
来保存连接数据,但是能够投影一些连接表是很好的,所以我补充说。它使用反射,所以性能不理想,但除非你这么做,否则应该没问题。
该代码使用了许多扩展方法,使反射更容易,并使用DataTable
s和IEnumerable
s。
public static class DataTableJoinExt {
// ***
// *** Type Extensions
// ***
public static List<MemberInfo> GetPropertiesOrFields(this Type t, BindingFlags bf = BindingFlags.Public | BindingFlags.Instance) =>
t.GetMembers(bf).Where(mi => mi.MemberType == MemberTypes.Field || mi.MemberType == MemberTypes.Property).ToList();
// ***
// *** MemberInfo Extensions
// ***
public static Type GetMemberType(this MemberInfo member) {
switch (member) {
case FieldInfo mfi:
return mfi.FieldType;
case PropertyInfo mpi:
return mpi.PropertyType;
case EventInfo mei:
return mei.EventHandlerType;
default:
throw new ArgumentException("MemberInfo must be if type FieldInfo, PropertyInfo or EventInfo", nameof(member));
}
}
public static object GetValue(this MemberInfo member, object srcObject) {
switch (member) {
case FieldInfo mfi:
return mfi.GetValue(srcObject);
case PropertyInfo mpi:
return mpi.GetValue(srcObject);
default:
throw new ArgumentException("MemberInfo must be of type FieldInfo or PropertyInfo", nameof(member));
}
}
public static T GetValue<T>(this MemberInfo member, object srcObject) => (T)member.GetValue(srcObject);
// ***
// *** IEnumerable Extensions
// ***
public static IEnumerable<T> AsSingleton<T>(this T first) {
yield return first;
}
// ***
// *** DataTable Extensions
// ***
public static IEnumerable<DataColumn> DataColumns(this DataTable aTable) => aTable.Columns.Cast<DataColumn>();
public static IEnumerable<string> ColumnNames(this DataTable aTable) => aTable.DataColumns().Select(dc => dc.ColumnName);
// Create new DataTable from LINQ join results on DataTable
// Expect T to be anonymous object of form new { [DataRow or other] d1, [DataRow or other] d2, ... }
public static DataTable FlattenToDataTable<T>(this IEnumerable<T> src) {
var res = new DataTable();
if (src.Any()) {
var firstRow = src.First();
var memberInfos = typeof(T).GetPropertiesOrFields();
var allDC = memberInfos.SelectMany(mi => (mi.GetMemberType() == typeof(DataRow)) ? mi.GetValue<DataRow>(firstRow).Table.DataColumns() : new DataColumn(mi.Name, mi.GetMemberType()).AsSingleton());
foreach (var dc in allDC) {
var newColumnName = dc.ColumnName;
if (res.ColumnNames().Contains(newColumnName)) {
var suffixNumber = 1;
while (res.ColumnNames().Contains($"{newColumnName}.{suffixNumber}"))
++suffixNumber;
newColumnName = $"{newColumnName}.{suffixNumber}";
}
res.Columns.Add(new DataColumn(newColumnName, dc.DataType));
}
foreach (var objRows in src)
res.Rows.Add(memberInfos.SelectMany(mi => (mi.GetMemberType() == typeof(DataRow)) ? mi.GetValue<DataRow>(objRows).ItemArray : mi.GetValue(objRows).AsSingleton()).ToArray());
}
return res;
}
}
现在定义了此扩展,您可以执行以下操作:
var result = (from t in tt.AsEnumerable()
join a in aa.AsEnumerable() on t.Field<DateTime>("time") equals a.Field<DateTime>("time")
select new { t, aa_col1 = a.Field<int>("col1") }).FlattenToDataTable();