c# – NHibernate 3 LINQ – 如何为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()方法得到的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可能会对此有所帮助 – 听起来确实应该是可能的事情,但我现在不知道如何优雅地做到这一点.

点赞