Linq “GroupBy” 在内存数据表上基于多个键/列






3.10/5 (5投票s)
展示了在内存对象上对多个列调用 linq group by 的众多方法之一。
引言
本文中的代码片段旨在解释
- 如何在 DataRowCollection 上调用 Linq 查询
- 如何在内存中基于多个动态列/键对 DataRowCollection 使用 group by 子句
使用代码
1 - 如何在 DataRowCollection 上调用 LINQ 查询
.net 框架默认情况下不包含支持在 DataRowCollection 上调用 LINQ 查询的扩展方法,因此 datatable/dataset 从设计上来说默认不支持开箱即用的 LINQ 查询(至少在 .net 3.5 中)。
由于 LINQ 查询只是一种扩展,我们可以通过编写自己的包装器来克服这个限制,该包装器可以枚举 DataRowCollection,从而允许我们调用 LINQ 查询
EnumerableDataRows 类(见下面的代码)允许我们这样做,构造函数接受 IEnumerable DataRowCollection,枚举器基本上会按需一次返回一个数据行
class EnumerableDataRows<T> : IEnumerable<T>, Enumerable
{
IEnumerable dataRows;
EnumerableDataRowList(IEnumerable items)
{
dataRows = items;
}
IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
foreach(T dataRow in dataRows)
{
yield return dataRow;
}
}
IEnumerator IEnumerable.GetEnumerator()
{
IEnumerable<T> iEnumerable = this;
return iEnumerable.GetEnumerator();
}
}
以下是一个包含四个列的示例数据表,分别是 Name(姓名)、Age(年龄)、Height(身高)和 Diet(饮食)。
static DataTable GetTable { get { DataTable dataTable = new DataTable("MyTable"); dataTable.Columns.Add("Name"); dataTable.Columns.Add("Age"); dataTable.Columns.Add("Height"); dataTable.Columns.Add("Diet"); dataTable.Rows.Add("Bob", "28", "170", "Vegan"); dataTable.Rows.Add("Michael", "28", "210", "Vegan"); dataTable.Rows.Add("James", "28", "190", "NonVegan"); dataTable.Rows.Add("George", "28", "200", "NonVegan"); return dataTable; } }
使用 EnumerableDataRows 类,我们可以使表的数据行集合可枚举,从而启用 LINQ。
EnumerableDataRowList<DataRow> enumerableRowCollection = new EnumerableDataRowList<DataRow>(Table.Rows);
这完成了我们的第一个目标,现在我们可以对 enumerableRowCollection 调用 LINQ 查询了。
var groupedRows = from row in enumerableRowCollection group row by row["Age"];
2 - 如何在内存中基于多个动态列/键对 DataRowCollection 使用 group by 子句
LINQ 默认情况下不支持对内存对象(在本例中为 datatable)基于多个列进行分组,就像我们在 SQL 中可以做的那样。我们可以通过多种方式实现这一点,例如为各种键递归地编写多个 group by 子句,或者通过创建内存中的桶来以编程方式进行分组。 在本主题中,我将解释我认为在实现或可理解性方面更好的一种方法(如果您认为这是一种糟糕的方法,我很乐意听取您的意见)。
我使用 LINQ 实现多列分组的方法是字符串连接,即基于连接结果设计键。
逻辑:如果我们要按年龄和饮食列分组,则将年龄和饮食值都连接成一个字符串,并基于新字符串进行分组。 以下函数执行完全相同的操作,它基本上遍历动态列列表,检索列值并返回连接的字符串
public static String GroupData(DataRow dataRow) { // This could be user defined dynamic columns String[] columnNames = new[] { "Age", "Diet" }; stringBuilder.Remove(0, stringBuilder.Length); foreach (String column in columnNames) { stringBuilder.Append(dataRow[column].ToString()); } return stringBuilder.ToString(); }
实现了第一个目标后,现在我们可以使用上述逻辑按多个列进行分组了。
Func<DataRow, String> groupingFunction = GroupData; var groupedDataRow = enumerableRowCollection.GroupBy(groupingFunction);
执行时,数据表将分组如下
Grouped by -> 28Vegan Items -> Bob Items -> Michael ---- Grouped by -> 28NonVegan Items -> James Items -> George ----
太棒了……我们完成了上述两个目标。
以下是完整的代码片段供您参考……尽情享受和学习吧!!!
using System; using System.Text; using System.Linq; using System.Data; using System.Collections.Generic; using System.Collections; using System.Linq.Expressions; using System.Xml.Linq; class EnumerableDataRowList<T> : IEnumerable<T>, IEnumerable { IEnumerable dataRows; internal EnumerableDataRowList(IEnumerable items) { dataRows = items; } IEnumerator<T> IEnumerable<T>.GetEnumerator() { foreach (T dataRow in dataRows) yield return dataRow; } IEnumerator IEnumerable.GetEnumerator() { IEnumerable<T> iEnumerable = this; return iEnumerable.GetEnumerator(); } } public class mainclass { static StringBuilder stringBuilder = new StringBuilder(); public static String GroupData(DataRow dataRow) { String[] columnNames = new[] { "Age", "Diet" }; stringBuilder.Remove(0, stringBuilder.Length); foreach (String column in columnNames) { stringBuilder.Append(dataRow[column].ToString()); } return stringBuilder.ToString(); } static DataTable Table { get { DataTable dataTable = new DataTable("MyTable"); dataTable.Columns.Add("Name"); dataTable.Columns.Add("Age"); dataTable.Columns.Add("Height"); dataTable.Columns.Add("Diet"); dataTable.Rows.Add("Bob", "28", "170", "Vegan"); dataTable.Rows.Add("Michael", "28", "210", "Vegan"); dataTable.Rows.Add("James", "28", "190", "NonVegan"); dataTable.Rows.Add("George", "28", "200", "NonVegan"); return dataTable; } } public static void Main() { EnumerableDataRowList<DataRow> enumerableRowCollection = new EnumerableDataRowList<DataRow>(Table.Rows); Func<DataRow, String> groupingFunction = GroupData; var groupedDataRow = enumerableRowCollection.GroupBy(groupingFunction); foreach (var keys in groupedDataRow) { Console.WriteLine(String.Format("Grouped by -> {0}",keys.Key)); foreach (var item in keys) { Console.WriteLine(String.Format(" Item -> {0}", item["Name"])); } Console.WriteLine("----"); } Console.ReadKey(); } }