【发布时间】:2018-05-25 01:25:20
【问题描述】:
我有一个包含 4 列的 DataTable,如下所示:
private static DataSet dataSet;
private const string tableName = "MyTable";
private const string columnName1 = "Supplier"; //Column names
private const string columnName2 = "Invoice";
private const string columnName3 = "Item";
private const string columnName4 = "Amount";
我按供应商、发票列对表格进行分组,并使用以下 linq 查询计算了金额的总和:
private static DataTable GroupQueryA(DataTable dataTable)
{
DataTable groupedTable = dataTable.AsEnumerable()
.GroupBy(r => new { Key1 = r.Field<string>(columnName1), Key2 = r.Field<string>(columnName2) })
.Select(g => new GroupSum
{
Key1 = g.Key.Key1,
Key2 = g.Key.Key2,
Sum = g.Sum(x => x.Field<double>(columnName4))
}).PropertiesToDataTable<GroupSum>();
return groupedTable;
}
我声明为的 GroupSum 类型:
private class GroupSum
{
public string Key1 { get; set; }
public string Key2 { get; set; }
public Double Sum { get; set; }
}
我复制的 PropertiesToDataTable() 方法:
Convert Datatable GroupBy Multiple Columns with Sum using Linq
它工作得很好,所以对于像这样的表格行:
AddRow(dataTable, "SA", "INVA", "ITA", 10);
AddRow(dataTable, "SA", "INVA", "ITB", 20);
AddRow(dataTable, "SB", "INVB", "ITC", 50);
我在结果中收到 2 行:
"SA", "INVA", 30
"SB", "INVB", 50
但是我决定修改我的查询,以便我编写代码而不是使用匿名类型:
public class GroupKeys
{
public string Key1 { get; set; }
public string Key2 { get; set; }
}
和查询。
private static DataTable GroupQueryB(DataTable dataTable)
{
DataTable groupedTable = dataTable.AsEnumerable()
.GroupBy(r => new GroupKeys { Key1 = r.Field<string>(columnName1), Key2 = r.Field<string>(columnName2) })
.Select(g => new GroupSum
{
Key1 = g.Key.Key1,
Key2 = g.Key.Key2,
Sum = g.Sum(x => x.Field<double>(columnName4))
}).PropertiesToDataTable<GroupSum>();
return groupedTable;
}
现在对于相同的源数据,我收到不同的结果:
"SA", "INVA", 10
"SA", "INVA", 20
"SB", "INVB", 50
尽管查询中唯一的区别是一行,但似乎根本没有对源数据进行分组:
//QueryA
.GroupBy(r => new { Key1 = r.Field<string>(columnName1), Key2 = r.Field<string>(columnName2) })
//QueryB
.GroupBy(r => new GroupKeys { Key1 = r.Field<string>(columnName1), Key2 = r.Field<string>(columnName2) })
有人能解释一下吗? 对于那些想在 Visual Studio 中运行测试的人,请在下面找到完整的源代码。
internal static class TestForStackOverflow
{
private static DataSet dataSet;
private const string tableName = "MyTable";
private const string columnName1 = "Supplier"; //Column names
private const string columnName2 = "Invoice";
private const string columnName3 = "Item";
private const string columnName4 = "Amount";
private class GroupKeys
{
public string Key1 { get; set; }
public string Key2 { get; set; }
}
private class GroupSum
{
public string Key1 { get; set; }
public string Key2 { get; set; }
public Double Sum { get; set; }
}
public static void Test()
{
DataTable dataTable = InitializeDataTable();
//DataTable groupedTable = GroupQueryA(dataTable); //Please uncomment to run test A
DataTable groupedTable = GroupQueryB(dataTable); //Please uncomment to run test B
DisplayData(groupedTable);
}
private static DataTable GroupQueryA(DataTable dataTable)
{
DataTable groupedTable = dataTable.AsEnumerable()
.GroupBy(r => new { Key1 = r.Field<string>(columnName1), Key2 = r.Field<string>(columnName2) })
.Select(g => new GroupSum
{
Key1 = g.Key.Key1,
Key2 = g.Key.Key2,
Sum = g.Sum(x => x.Field<double>(columnName4))
}).PropertiesToDataTable<GroupSum>();
return groupedTable;
}
private static DataTable GroupQueryB(DataTable dataTable)
{
DataTable groupedTable = dataTable.AsEnumerable()
.GroupBy(r => new GroupKeys { Key1 = r.Field<string>(columnName1), Key2 = r.Field<string>(columnName2) })
.Select(g => new GroupSum
{
Key1 = g.Key.Key1,
Key2 = g.Key.Key2,
Sum = g.Sum(x => x.Field<double>(columnName4))
}).PropertiesToDataTable<GroupSum>();
return groupedTable;
}
private static System.Data.DataTable PropertiesToDataTable<T>(this System.Collections.Generic.IEnumerable<T> source)
{
System.Data.DataTable dt = new System.Data.DataTable();
//Weź listę właściwości typu <T> i dla każdej właściwości dodaj do tabeli kolumnę tego samego typu co właściwość
var props = System.ComponentModel.TypeDescriptor.GetProperties(typeof(T));
foreach (System.ComponentModel.PropertyDescriptor prop in props)
{
System.Data.DataColumn dc = dt.Columns.Add(prop.Name, prop.PropertyType);
dc.Caption = prop.DisplayName;
dc.ReadOnly = prop.IsReadOnly;
}
//Kopiuj rekordy z kwerendy do DataTable
foreach (T item in source)
{
System.Data.DataRow dr = dt.NewRow();
foreach (System.ComponentModel.PropertyDescriptor prop in props)
{
dr[prop.Name] = prop.GetValue(item);
}
dt.Rows.Add(dr);
}
return dt;
}
private static DataTable InitializeDataTable()
{
dataSet = new DataSet();
DataTable dataTable = dataSet.Tables.Add(tableName);
dataTable.Columns.Add( columnName1, typeof(string));
dataTable.Columns.Add( columnName2, typeof(string));
dataTable.Columns.Add( columnName3, typeof(string));
dataTable.Columns.Add( columnName4, typeof(double));
AddRow(dataTable, "SA", "INVA", "ITA", 10);
AddRow(dataTable, "SA", "INVA", "ITB", 20);
AddRow(dataTable, "SB", "INVB", "ITC", 50);
return dataTable;
}
private static void AddRow( DataTable dataTable, string supplier, string invoice, string item, double amount)
{
DataRow row = dataTable.NewRow();
row[columnName1] = supplier;
row[columnName2] = invoice;
row[columnName3] = item;
row[columnName4] = amount;
dataTable.Rows.Add(row);
}
private static void DisplayData(System.Data.DataTable table)
{
foreach (System.Data.DataRow row in table.Rows)
{
foreach (System.Data.DataColumn col in table.Columns)
{
Console.WriteLine("{0} = {1}", col.ColumnName, row[col]);
}
Console.WriteLine("============================");
}
}
}
【问题讨论】:
-
因为 C# 编译器为匿名类型正确实现了
GetHashCode/Equals。而对于您自己的类型,您应该自己实现它们。你没有,因此GroupBy的区别。 -
第二种方法是由一个没有 ICompare 方法的类 {new GroupKeys} 分组的。您实际上是在比较三个字符串的第一种方法。
-
考虑覆盖 GroupSum 类的 Equals 和 GetHashCode 方法。看stackoverflow.com/questions/2363143/…
-
^ 不知道这是因为 GetHashCode/Equals。一直认为它有效,因为它通过a,b,c作为一个组传递给sql