将聚合选择表达式传递给Dynamic Linq的GroupBy

时间:2018-05-22 18:07:44

标签: entity-framework dynamic-linq

我已经从我的代码中简化了以下示例,并希望没有明显的编译错误。假设我有以下实体(不是我实际拥有的,请假设我没有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>相同的错误。我试过选择“Cost”并将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.

所以,通过这个动态分组并注入我自己的select表达式,我应该假设'it'的数据类型是什么?这甚至会起作用吗?

1 个答案:

答案 0 :(得分:2)

it的类型为IGrouping<TKey, TElement>,其中TKey基于keySelector结果类型是动态的,TElement是输入的元素类型IQueryable。幸运的是IGrouping<TKey, TElement>继承了(是)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;