我正在尝试使用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);
我如何构建group-by表达式?
foreach (var grp in collection.GroupBy(
item => new {
// selectors??
}))
{
// grp.Key. ??
}
修改
我上面更新了希望澄清为什么我需要这组选择器。
编辑#2
将实体类型设为 doLinq generic。
答案 0 :(得分:1)
你应该看一下Dynamic Linq:http://blogs.msdn.com/b/mitsu/archive/2008/02/07/linq-groupbymany-dynamically.aspx
答案 1 :(得分:1)
好吧,我假设你使用linq-to-sql或类似的东西,所以你需要表达式树。如果没有,可能还有其他可能性。
我可以看到可能的解决方案:
见Vladimir Perevalovs回答。
见 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");
}
}
答案 2 :(得分:1)
我对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数据库有一个表“Test”,有三行。输出是这样的:
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。
答案 3 :(得分:0)
解决方案对我有用。它涉及两个部分:
第一部分
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提供者,以及这适用于哪种表达类型是另一个问题......