c# – 使用表达式树构造LINQ GroupBy查询

前端之家收集整理的这篇文章主要介绍了c# – 使用表达式树构造LINQ GroupBy查询前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
我已经坚持这个问题一个星期,没有找到解决方案.

我有一个像下面的POCO:

  1. public class Journal {
  2. public int Id { get; set; }
  3. public string AuthorName { get; set; }
  4. public string Category { get; set; }
  5. public DateTime CreatedAt { get; set; }
  6. }

我想知道在特定日期范围内(按月或年分组)期刊的数量由AuthorName或Category计算.

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

  1. data: {
  2. '201301': {
  3. 'Alex': 10,'James': 20
  4. },'201302': {
  5. 'Alex': 1,'Jessica': 9
  6. }
  7. }

要么

  1. data: {
  2. '2012': {
  3. 'C#': 230
  4. 'VB.NET': 120,'LINQ': 97
  5. },'2013': {
  6. 'C#': 115
  7. 'VB.NET': 29,'LINQ': 36
  8. }
  9. }

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

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

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

任何解决方案或建议都表示赞赏.

解决方法

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

首先,让我们设置我们的虚假来源,因为我没有你的数据库方便:

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

好的,现在我们已经有了一组可以使用的数据,让我们看看我们想要什么…我们想要一些像“形状”的东西:

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

它具有与(伪代码)大致相同的功能

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

让我们一次解剖一件.首先,我们如何动态地做到这一点?

  1. x => new { ... }

编译器通常会为我们带来魔力 – 它的作用是定义一个新的Type,我们也可以这样做:

  1. var sourceType = typeof(Journal);
  2.  
  3. // define a dynamic type (read: anonymous type) for our needs
  4. var dynAsm = AppDomain
  5. .CurrentDomain
  6. .DefineDynamicAssembly(
  7. new AssemblyName(Guid.NewGuid().ToString()),AssemblyBuilderAccess.Run);
  8. var dynMod = dynAsm
  9. .DefineDynamicModule(Guid.NewGuid().ToString());
  10. var typeBuilder = dynMod
  11. .DefineType(Guid.NewGuid().ToString());
  12. var properties = groupByNames
  13. .Select(name => sourceType.GetProperty(name))
  14. .Cast<MemberInfo>();
  15. var fields = groupByNames
  16. .Select(name => sourceType.GetField(name))
  17. .Cast<MemberInfo>();
  18. var propFields = properties
  19. .Concat(fields)
  20. .Where(pf => pf != null);
  21. foreach (var propField in propFields)
  22. {
  23. typeBuilder.DefineField(
  24. propField.Name,propField.MemberType == MemberTypes.Field
  25. ? (propField as FieldInfo).FieldType
  26. : (propField as PropertyInfo).PropertyType,FieldAttributes.Public);
  27. }
  28. var dynamicType = typeBuilder.CreateType();

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

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

首先,让我们为我们将返回的func设置一个“输入”:

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

我们知道我们需要“新建”我们的新动态类型之一……

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

我们需要使用该参数中的值对其进行初始化…

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

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

  1. var bindings = dynamicType
  2. .GetFields()
  3. .Select(p =>
  4. Expression.Bind(
  5. p,Expression.PropertyOrField(
  6. sourceItem,p.Name)))
  7. .OfType<MemberBinding>()
  8. .ToArray();

Oof ……看起来很讨厌,但我们还没有完成 – 所以我们需要为我们通过Expression树创建的Func声明一个返回类型……如果有疑问,请使用对象!

  1. Expression.Convert( expr,typeof(object))

最后,我们将通过Lambda将它绑定到我们的“输入参数”,从而构成整个堆栈:

  1. // Create and return an expression that maps T => dynamic type
  2. var sourceItem = Expression.Parameter(sourceType,"item");
  3. var bindings = dynamicType
  4. .GetFields()
  5. .Select(p => Expression.Bind(p,Expression.PropertyOrField(sourceItem,p.Name)))
  6. .OfType<MemberBinding>()
  7. .ToArray();
  8.  
  9. var fetcher = Expression.Lambda<Func<T,object>>(
  10. Expression.Convert(
  11. Expression.MemberInit(
  12. Expression.New(dynamicType.GetConstructor(Type.EmptyTypes)),typeof(object)),sourceItem);

为了便于使用,让我们将整个混乱包装为扩展方法,所以现在我们已经:

  1. public static class Ext
  2. {
  3. // Science Fact: the "Grouper" (as in the Fish) is classified as:
  4. // Perciformes Serranidae Epinephelinae
  5. public static Expression<Func<T,object>> Epinephelinae<T>(
  6. this IEnumerable<T> source,string [] groupByNames)
  7. {
  8. var sourceType = typeof(T);
  9. // define a dynamic type (read: anonymous type) for our needs
  10. var dynAsm = AppDomain
  11. .CurrentDomain
  12. .DefineDynamicAssembly(
  13. new AssemblyName(Guid.NewGuid().ToString()),FieldAttributes.Public);
  14. }
  15. var dynamicType = typeBuilder.CreateType();
  16.  
  17. // Create and return an expression that maps T => dynamic type
  18. var sourceItem = Expression.Parameter(sourceType,"item");
  19. var bindings = dynamicType
  20. .GetFields()
  21. .Select(p => Expression.Bind(
  22. p,p.Name)))
  23. .OfType<MemberBinding>()
  24. .ToArray();
  25.  
  26. var fetcher = Expression.Lambda<Func<T,object>>(
  27. Expression.Convert(
  28. Expression.MemberInit(
  29. Expression.New(dynamicType.GetConstructor(Type.EmptyTypes)),sourceItem);
  30. return fetcher;
  31. }
  32. }

现在,使用它:

  1. // What you had originally (hand-tooled query)
  2. var db = entries.AsQueryable();
  3. var query = db.GroupBy(x => new
  4. {
  5. Year = x.CreatedAt.Year,Month = x.CreatedAt.Month
  6. },Count = z.Count() })
  7. });
  8.  
  9. var func = db.Epinephelinae(new[]{"CreatedAt","AuthorName"});
  10. var dquery = db.GroupBy(func,prj => prj.AuthorName);

这个解决方案缺乏“嵌套语句”的灵活性,比如“CreatedDate.Month”,但是有了一点想象力,你可以扩展这个想法来处理任何自由形式的查询.

猜你在找的C#相关文章