gpt4 book ai didi

c# - 使用表达式树构建 LINQ GroupBy 查询

转载 作者:太空狗 更新时间:2023-10-29 18:04:41 25 4
gpt4 key购买 nike

我在这个问题上纠结了一个星期,没有找到解决方案。

我有一个如下所示的 POCO:

public class Journal {
public int Id { get; set; }
public string AuthorName { get; set; }
public string Category { get; set; }
public DateTime CreatedAt { get; set; }
}

我想知道在特定日期范围内(按月或年分组)按作者姓名或类别计算的期刊数量。

在我将查询到的对象发送到 JSON 序列化器之后生成如下所示的 JSON 数据(只是使用 JSON 来演示我想要获取的数据,如何将对象序列化为 JSON 不是我的问题)

data: {
'201301': {
'Alex': 10,
'James': 20
},
'201302': {
'Alex': 1,
'Jessica': 9
}
}

data: {
'2012': {
'C#': 230
'VB.NET': 120,
'LINQ': 97
},
'2013': {
'C#': 115
'VB.NET': 29,
'LINQ': 36
}
}

我所知道的是以“方法方式”编写 LINQ 查询,例如:

IQueryable<Journal> query = db.GroupBy(x=> new 
{
Year = key.CreatedAt.Year,
Month = key.CreatedAt.Month
}, prj => prj.AuthorName)
.Select(data => new {
Key = data.Key.Year * 100 + data.Key.Month, // very ugly code, I know
Details = data.GroupBy(y => y).Select(z => new { z.Key, Count = z.Count() })
});

按月或年、AuthorName或Category分组的条件将通过两个字符串类型的方法参数传递。我不知道的是如何在 GroupBy() 方法中使用“魔术字符串”参数。经过一番谷歌搜索后,似乎我无法通过传递像“AuthorName”这样的神奇字符串来对数据进行分组。我应该做的是构建一个表达式树并将其传递给 GroupBy() 方法。

如有任何解决方案或建议,我们将不胜感激。

最佳答案

哦,这看起来是个有趣的问题:)

所以首先,让我们设置我们的伪源,因为我手头没有你的数据库:

// SETUP: fake up a data source
var folks = new[]{"Alex", "James", "Jessica"};
var cats = new[]{"C#", "VB.NET", "LINQ"};
var r = new Random();
var entryCount = 100;
var entries =
from i in Enumerable.Range(0, entryCount)
let id = r.Next(0, 999999)
let person = folks[r.Next(0, folks.Length)]
let category = cats[r.Next(0, cats.Length)]
let date = DateTime.Now.AddDays(r.Next(0, 100) - 50)
select new Journal() {
Id = id,
AuthorName = person,
Category = category,
CreatedAt = date };

好的,现在我们有了一组要处理的数据,让我们看看我们想要什么……我们想要一些“形状”如下的东西:

public Expression<Func<Journal, ????>> GetThingToGroupByWith(
string[] someMagicStringNames,
????)

这与(在伪代码中)具有大致相同的功能:

GroupBy(x => new { x.magicStringNames })

让我们一次一个地剖析它。首先,我们到底如何动态地做到这一点?

x => new { ... }

编译器通常为我们施展魔法 - 它所做的是定义一个新的 Type,我们也可以这样做:

    var sourceType = typeof(Journal);

// define a dynamic type (read: anonymous type) for our needs
var dynAsm = AppDomain
.CurrentDomain
.DefineDynamicAssembly(
new AssemblyName(Guid.NewGuid().ToString()),
AssemblyBuilderAccess.Run);
var dynMod = dynAsm
.DefineDynamicModule(Guid.NewGuid().ToString());
var typeBuilder = dynMod
.DefineType(Guid.NewGuid().ToString());
var properties = groupByNames
.Select(name => sourceType.GetProperty(name))
.Cast<MemberInfo>();
var fields = groupByNames
.Select(name => sourceType.GetField(name))
.Cast<MemberInfo>();
var propFields = properties
.Concat(fields)
.Where(pf => pf != null);
foreach (var propField in propFields)
{
typeBuilder.DefineField(
propField.Name,
propField.MemberType == MemberTypes.Field
? (propField as FieldInfo).FieldType
: (propField as PropertyInfo).PropertyType,
FieldAttributes.Public);
}
var dynamicType = typeBuilder.CreateType();

所以我们在这里所做的是定义一个自定义的一次性类型,它为我们传入的每个名称都有一个字段,它与源类型上的(Property 或 Field)类型相同。不错!

现在我们如何为 LINQ 提供它想要的东西?

首先,让我们为要返回的函数设置一个“输入”:

// Create and return an expression that maps T => dynamic type
var sourceItem = Expression.Parameter(sourceType, "item");

我们知道我们需要“更新”我们的一种新动态类型...

Expression.New(dynamicType.GetConstructor(Type.EmptyTypes))

我们需要使用来自该参数的值对其进行初始化...

Expression.MemberInit(
Expression.New(dynamicType.GetConstructor(Type.EmptyTypes)),
bindings),

但是我们到底要为 bindings 使用什么?嗯...好吧,我们想要一些绑定(bind)到源类型中相应属性/字段的东西,但是将它们重新映射到我们的 dynamicType 字段...

    var bindings = dynamicType
.GetFields()
.Select(p =>
Expression.Bind(
p,
Expression.PropertyOrField(
sourceItem,
p.Name)))
.OfType<MemberBinding>()
.ToArray();

Oof...看起来很讨厌,但我们还没有完成 - 所以我们需要为我们通过表达式树创建的 Func 声明一个返回类型...当有疑问时,使用对象!

Expression.Convert( expr, typeof(object))

最后,我们将通过 Lambda 将其绑定(bind)到我们的“输入参数”,使整个堆栈:

    // Create and return an expression that maps T => dynamic type
var sourceItem = Expression.Parameter(sourceType, "item");
var bindings = dynamicType
.GetFields()
.Select(p => Expression.Bind(p, Expression.PropertyOrField(sourceItem, p.Name)))
.OfType<MemberBinding>()
.ToArray();

var fetcher = Expression.Lambda<Func<T, object>>(
Expression.Convert(
Expression.MemberInit(
Expression.New(dynamicType.GetConstructor(Type.EmptyTypes)),
bindings),
typeof(object)),
sourceItem);

为了便于使用,让我们把整个困惑包装成一个扩展方法,所以现在我们有:

public static class Ext
{
// Science Fact: the "Grouper" (as in the Fish) is classified as:
// Perciformes Serranidae Epinephelinae
public static Expression<Func<T, object>> Epinephelinae<T>(
this IEnumerable<T> source,
string [] groupByNames)
{
var sourceType = typeof(T);
// define a dynamic type (read: anonymous type) for our needs
var dynAsm = AppDomain
.CurrentDomain
.DefineDynamicAssembly(
new AssemblyName(Guid.NewGuid().ToString()),
AssemblyBuilderAccess.Run);
var dynMod = dynAsm
.DefineDynamicModule(Guid.NewGuid().ToString());
var typeBuilder = dynMod
.DefineType(Guid.NewGuid().ToString());
var properties = groupByNames
.Select(name => sourceType.GetProperty(name))
.Cast<MemberInfo>();
var fields = groupByNames
.Select(name => sourceType.GetField(name))
.Cast<MemberInfo>();
var propFields = properties
.Concat(fields)
.Where(pf => pf != null);
foreach (var propField in propFields)
{
typeBuilder.DefineField(
propField.Name,
propField.MemberType == MemberTypes.Field
? (propField as FieldInfo).FieldType
: (propField as PropertyInfo).PropertyType,
FieldAttributes.Public);
}
var dynamicType = typeBuilder.CreateType();

// Create and return an expression that maps T => dynamic type
var sourceItem = Expression.Parameter(sourceType, "item");
var bindings = dynamicType
.GetFields()
.Select(p => Expression.Bind(
p,
Expression.PropertyOrField(sourceItem, p.Name)))
.OfType<MemberBinding>()
.ToArray();

var fetcher = Expression.Lambda<Func<T, object>>(
Expression.Convert(
Expression.MemberInit(
Expression.New(dynamicType.GetConstructor(Type.EmptyTypes)),
bindings),
typeof(object)),
sourceItem);
return fetcher;
}
}

现在,使用它:

// What you had originally (hand-tooled query)
var db = entries.AsQueryable();
var query = db.GroupBy(x => new
{
Year = x.CreatedAt.Year,
Month = x.CreatedAt.Month
}, prj => prj.AuthorName)
.Select(data => new {
Key = data.Key.Year * 100 + data.Key.Month, // very ugly code, I know
Details = data.GroupBy(y => y).Select(z => new { z.Key, Count = z.Count() })
});

var func = db.Epinephelinae(new[]{"CreatedAt", "AuthorName"});
var dquery = db.GroupBy(func, prj => prj.AuthorName);

此解决方案缺乏“嵌套语句”(如“CreatedDate.Month”)的灵 active ,但只要发挥一点想象力,您就可以扩展此想法以处理任何自由形式的查询。

关于c# - 使用表达式树构建 LINQ GroupBy 查询,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15297884/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com