[英]NHibernate 3 LINQ - how to create a valid parameter for Average()
假设我有一个非常简单的实体,例如:
public class TestGuy
{
public virtual long Id {get;set;}
public virtual string City {get;set;}
public virtual int InterestingValue {get;set;}
public virtual int OtherValue {get;set;}
}
这个人为的示例对象与NHibernate映射(使用Fluent),并且运行良好。
是时候做一些报告了。 在此示例中,“ testGuys”是一个IQueryable,其中已应用了一些条件。
var byCity = testGuys
.GroupBy(c => c.City)
.Select(g => new { City = g.Key, Avg = g.Average(tg => tg.InterestingValue) });
这样很好。 在NHibernate Profiler中,我可以看到正在生成正确的SQL,并且结果符合预期。
受到成功的启发,我想使其更加灵活。 我想使其可配置,以便用户可以获取OtherValue和InterestingValue的平均值。 应该不太困难,Average()的参数似乎是一个Func(因为在这种情况下,值是整数)。 十分简单。 我能否仅创建一种基于某些条件返回Func并将其用作参数的方法?
var fieldToAverageBy = GetAverageField(SomeEnum.Other);
private Func<TestGuy,int> GetAverageField(SomeEnum someCondition)
{
switch(someCondition)
{
case SomeEnum.Interesting:
return tg => tg.InterestingValue;
case SomeEnum.Other:
return tg => tg.OtherValue;
}
throw new InvalidOperationException("Not in my example!");
}
然后,在其他地方,我可以这样做:
var byCity = testGuys
.GroupBy(c => c.City)
.Select(g => new { City = g.Key, Avg = g.Average(fieldToAverageBy) });
好吧,我以为我可以做到。 但是,当我进行枚举时,NHibernate抛出一个合适的结果:
Object of type 'System.Linq.Expressions.ConstantExpression' cannot be converted to type 'System.Linq.Expressions.LambdaExpression'.
因此,我想在幕后进行某些转换或强制转换或诸如此类的事情,第一种情况接受我的lambda,但第二种情况使NHibernate无法转换为SQL。
我的问题希望很简单-当NHibernate 3.0 LINQ支持(.Query()方法)将其转换为SQL时,我的GetAverageField函数如何返回将作为Average()参数工作的东西?
欢迎任何建议,谢谢!
编辑
根据David B在回答中的评论,我仔细研究了这一点。 我认为Func将是正确的返回类型的假设是基于我对Average()方法获得的智能感知。 它似乎基于Enumerable类型,而不是Queryable类型。 真是奇怪..需要仔细研究一些东西。
GroupBy方法具有以下返回签名:
IQueryable<IGrouping<string,TestGuy>>
这意味着它应该可以给我一个IQueryable。 但是,我接着转到下一行:
.Select(g => new { City = g.Key, Avg = g.Average(tg => tg.InterestingValue) });
如果我在新{}对象定义中检查g变量的intellisense,则实际上它被列为IGrouping-NOT IQueryable>类型。 这就是为什么调用的Average()方法是Enumerable方法的原因,以及为什么它不接受David B建议的Expression参数的原因。
因此,我的组值显然以某种方式失去了其IQueryable的地位。
有点有趣的注释:
我可以将“选择”更改为以下内容:
.Select(g => new { City = g.Key, Avg = g.AsQueryable<TestGuy>().Average(fieldToAverageBy) });
现在可以编译了! 黑魔法! 但是,这并不能解决问题,因为NHibernate现在不再爱我了,并给出以下异常:
Could not parse expression '[-1].AsQueryable()': This overload of the method 'System.Linq.Queryable.AsQueryable' is currently not supported, but you can register your own parser if needed.
使我感到困惑的是,当我将lambda表达式提供给Average()方法时,这种方法有效,但是我找不到一种简单的方法来表示相同的表达式作为参数。 我显然做错了,但是看不到...!?
我不知道该怎么做。 乔恩·斯基特(Jon Skeet),请帮助我,您是我唯一的希望! ;)
您将无法在lambda表达式中调用“本地”方法。 如果这是一个简单的非嵌套子句,它将相对简单-您只需要更改以下内容:
private Func<TestGuy,int> GetAverageField(SomeEnum someCondition)
对此:
private Expression<Func<TestGuy,int>> GetAverageField(SomeEnum someCondition)
然后将调用结果传递到相关的查询方法中,例如
var results = query.Select(GetAverageField(fieldToAverageBy));
但是,在这种情况下,您需要为Select
子句构建整个表达式树-匿名类型创建表达式,Key的提取以及平均字段部分的提取。 老实说,这不会很有趣。 特别是,在构建表达式树时,由于无法在声明中表示匿名类型,因此不会像普通查询表达式那样静态地键入静态类型。
如果您使用的是.NET 4,则动态键入可能会有所帮助,尽管您当然要付出不再拥有静态类型的代价。
一个选择(虽然可能很可怕)是尝试使用一种匿名类型投影表达式树的“模板”(例如,始终使用单个属性),然后构建该表达式树的副本,并插入正确的表达式代替。 再次,它不会很有趣。
马克·格雷夫(Marc Gravell)也许可以在这方面提供更多帮助-听起来确实像应该做的事情,但是目前我对如何优雅地进行操作感到困惑。
嗯 Queryable.Average的参数不是Func<T, U>
。 它是Expression<Func<T, U>>
这样做的方法是:
private Expression<Func<TestGuy,int>> GetAverageExpr(SomeEnum someCondition)
{
switch(someCondition)
{
case SomeEnum.Interesting:
return tg => tg.InterestingValue;
case SomeEnum.Other:
return tg => tg.OtherValue;
}
throw new InvalidOperationException("Not in my example!");
}
其次是:
Expression<Func<TestGuy, int>> averageExpr = GetAverageExpr(someCondition);
var byCity = testGuys
.GroupBy(c => c.City)
.Select(g => new { City = g.Key, Avg = g.Average(averageExpr) });
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.