65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.10/5 (5投票s)

2008年4月5日

CPOL

2分钟阅读

viewsIcon

133102

展示了在内存对象上对多个列调用 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();
    }
}

© . All rights reserved.