如何按多个泛型 linq 表达式进行分组

本文关键字:表达式 linq 何按多 泛型 | 更新日期: 2023-09-27 18:31:15

我正在尝试使用 Linq 表达式来构造查询,但尝试按多个列分组时陷入困境。 假设我有一个基本集合:

IEnumerable<Row> collection = new Row[]
{
    new Row() { Col1 = "a", Col2="x" },
    new Row() { Col1 = "a", Col2="x" },
    new Row() { Col1 = "a", Col2="y" },
};

我知道您可以使用 lambda 表达式对这些进行分组:

foreach (var grp in collection.GroupBy(item => new { item.Col1, item.Col2 }))
{
    Debug.Write("Grouping by " + grp.Key.Col1 + " and " + grp.Key.Col2 + ": ");
    Debug.WriteLine(grp.Count() + " rows");
}

如您所见,这将正确分组:

Grouping by a and x: 2 rows
Grouping by a and y: 1 rows

但是现在,假设我收到一个要针对其分组的选择器的集合,该选择器作为方法中的参数传递给我,并且实体类型是泛型的:

void doLinq<T>(params Expression<Func<T,object>>[] selectors)
{
    // linq stuff
}

无论谁调用该方法,都会像这样调用:

doLinq<Row>(entity=>entity.Col1, entity=>entity.Col2);

我将如何构造分组依据表达式?

foreach (var grp in collection.GroupBy(
      item => new { 
          // selectors??
      }))
{
    // grp.Key. ??
}

编辑

我在上面进行了更新,希望能澄清为什么我需要这组选择器。

编辑 #2

使 doLinq 中的实体类型成为泛型。

如何按多个泛型 linq 表达式进行分组

你应该看看 Dynamic Linq: http://blogs.msdn.com/b/mitsu/archive/2008/02/07/linq-groupbymany-dynamically.aspx

好吧,我假设您使用linq-to-sql或类似的东西,所以你需要表达式树。如果没有,可能还有其他可能性。

我可以看到可能的解决方案:

  • 动态林克

见弗拉基米尔·佩雷瓦洛夫的回答。

  • 手动构造整个分组依据表达式树

看http://msdn.microsoft.com/en-us/library/bb882637.aspx

  • 丑陋的解决方法

嗯,这就是我的部门:)

未经测试的代码:

 void doLinq(params string[] selectors) // checking two expressions for equality is messy, so I used strings
     foreach (var grp in collection.GroupBy(
          item => new { 
              Col1 = (selectors.Contains("Col1") ? item.Col1 : String.Empty),
              Col2 = (selectors.Contains("Col2") ? item.Col2 : String.Empty)
              // need to add a line for each column :(
          }))
     {
          string[] grouping = (new string[]{grp.Key.Col1, grp.Key.Col2 /*, ...*/ }).Where(s=>!s.IsNullOrEmpty()).ToArray();
          Debug.Write("Grouping by " + String.Join(" and ", grouping)+ ": ");
          Debug.WriteLine(grp.Count() + " rows");
     }
 }

我对 linq-to-sql 的了解非常有限,但 GroupBy 内部的内容真的很重要吗?因为如果不是,您可以推出自己的keySelector。无论如何,我用Sql Server CE和Sql Server Express都尝试过,这似乎有效:

using System;
using System.Linq;
using System.Collections.Generic;
using System.Data.Linq;
using System.Linq.Expressions;
namespace ConsoleApplication1 {
    class Props {
        public List<object> list = new List<object>();
        public override bool Equals(object obj) {
            return Enumerable.SequenceEqual(list, (obj as Props).list);
        }
        public override int GetHashCode() {
            return list.Select(o => o.GetHashCode()).Aggregate((i1, i2) => i1 ^ i2);
        }
    }
    class Program {
        static void Main(string[] args) {
            Lol db = new Lol(@"Data Source=.'SQLExpress;Initial Catalog=Lol;Integrated Security=true");
            db.Log = Console.Out;
            doLinq(db.Test, row => row.Col1, row => row.Col2);
            Console.ReadLine();
        }
        static void doLinq<T>(Table<T> table, params Func<T, object>[] selectors) where T : class {
            Func<T, Props> selector = item => {
                var props = new Props();
                foreach (var sel in selectors) props.list.Add(sel(item));
                return props;
            };
            foreach (var grp in table.GroupBy(selector)) {
                Console.Write("Grouping by " + string.Join(", ", grp.Key.list) + ": ");
                Console.WriteLine(grp.Count() + " rows");
            }
        }
    }
}

Lol 数据库有一个包含三行的表"测试"。输出是这样的:

SELECT [t0].[Col1], [t0].[Col2]
FROM [dbo].[Test] AS [t0]
-- Context: SqlProvider(Sql2008) Model: AttributedMetaModel Build: 4.0.30319.1
Grouping by a, x: 2 rows
Grouping by a, y: 1 rows

我检查了查询,似乎linq-to-sql足够聪明,当它不能为groupBy生成sql时,所以它会遍历表的所有行,然后在客户端上对它们进行分组。

编辑:为了完成而进行少量添加,连接字符串现在采用 Sql Server Express。

该解决方案对我有用。 它包括两部分:

  • 创建一个分组对象(我将其不优雅地实现为 object[]),给定行值和选择器集。 这涉及一个 lambda 表达式,该表达式编译并调用行项目上的每个选择器。
  • 为分组对象类型实现IEquality(在我的例子中是IEqualityComparer)。

第一部分

foreach (System.Linq.IGrouping<object[], T> g in collection.GroupBy(
    new Func<T, object[]>(
        item => selectors.Select(sel => sel.Compile().Invoke(item)).ToArray()
    ),
    new ColumnComparer()
)
{ ... }

第二部分

public class ColumnComparer : IEqualityComparer<object[]>
{
    public bool Equals(object[] x, object[] y)
    {
        return Enumerable.SequenceEqual(x, y);
    }
    public int GetHashCode(object[] obj)
    {
        return (string.Join("", obj.ToArray())).GetHashCode();
    }
}

这适用于基本的Linq,Linq适用于MySql连接器。 哪些其他 Linq 提供程序以及这适用于哪些表达式类型是另一个问题......