首页 > 解决方案 > 将聚合选择表达式传递给 Dynamic Linq 的 GroupBy

问题描述

我已经从我的代码中简化了以下示例,并希望没有明显的编译错误。假设我有以下实体(不是我实际拥有的,请假设我没有 EF 或架构问题,这只是示例):

public class Company
{
  public string GroupProperty {get;set;}
  public virtual ICollection<PricingForm> PricingForms {get;set;}
}
public class PricingForm
{
  public decimal Cost {get;set;}
}

我想这样查询:

IQueryable DynamicGrouping<T>(IQueryable<T> query)
{
  Expression<Func<Company, decimal?>> exp = c => c.PricingForms.Sum(fr => fr.Cost);
  string selector = "new (it.Key as Key, @0(it) as Value)";
  IQueryable grouping = query.GroupBy("it.GroupProperty", "it").Select(selector, exp);
  return grouping;
}

调用 groupby/select 行时出现以下错误:

System.Linq.Dynamic.ParseException: 'Argument list incompatible with lambda expression'

分组时“它”是什么类型?我尝试使用其他表达式,假设它是一个IGrouping<string, Company>或一个IQueryable<Company>相同的错误。我试过只选择“成本”并将 Sum() 聚合移动到选择器字符串(即Sum(@0(it)) as Value)中,但似乎总是得到相同的错误。

我最终尝试了一些类似的东西:

Expression<Func<IEnumerable<Company>, decimal?>> exp = l => l.SelectMany(c => c.PricingForms).Sum(fr => fr.Cost);

然而,这一次,我走得更远,但是当尝试遍历结果时,我得到了一个不同的错误。

The LINQ expression node type 'Invoke' is not supported in LINQ to Entities.

那么,通过这种动态分组并注入我自己的选择表达式,我应该假设“它”的数据类型是什么?这甚至会起作用吗?

标签: entity-frameworkdynamic-linq

解决方案


的类型itIGrouping<TKey, TElement>,其中TKey根据keySelector结果类型是动态的,TElement是 input 的元素类型IQueryable。幸运的是IGrouping<TKey, TElement>继承了 (is a) IEnumerable<TElement>,因此一旦您知道输入元素类型,您就可以安全地将选择器基于IEnumerable<TElement>.

换句话说,基于的最后一次尝试Expression<Func<IEnumerable<Company>, decimal?>>是正确的。

您收到的新错误是因为@0(it)生成Expression.Invoke了 EF 不支持的调用。解决这个问题的最简单方法是使用LINQKit Expand方法:

Expression<Func<Company, decimal?>> exp = c => c.PricingForms.Sum(fr => fr.Cost);
string selector = "new (it.Key as Key, @0(it) as Value)";
IQueryable grouping = query.GroupBy("it.GroupProperty", "it").Select(selector, exp);

// This would fix the EF invocation expression error
grouping = grouping.Provider.CreateQuery(grouping.Expression.Expand());

return grouping;

推荐阅读