假设我有一个非常简单的实体:
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()方法得到的intellisense.它似乎基于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更改为以下内容:
.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可能会对此有所帮助 – 听起来确实应该是可能的事情,但我现在不知道如何优雅地做到这一点.