繁体   English   中英

NHibernate 3 LINQ-如何为Average()创建有效参数

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM