简体   繁体   English

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

[英]NHibernate 3 LINQ - how to create a valid parameter for Average()

Say I have a very simple entity like this: 假设我有一个非常简单的实体,例如:

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;}
}

This contrived example object is mapped with NHibernate (using Fluent) and works fine. 这个人为的示例对象与NHibernate映射(使用Fluent),并且运行良好。

Time to do some reporting. 是时候做一些报告了。 In this example, "testGuys" is an IQueryable with some criteria already applied. 在此示例中,“ testGuys”是一个IQueryable,其中已应用了一些条件。

var byCity = testGuys
    .GroupBy(c => c.City)
    .Select(g => new { City = g.Key, Avg = g.Average(tg => tg.InterestingValue) });

This works just fine. 这样很好。 In NHibernate Profiler I can see the correct SQL being generated, and the results are as expected. 在NHibernate Profiler中,我可以看到正在生成正确的SQL,并且结果符合预期。

Inspired by my success, I want to make it more flexible. 受到成功的启发,我想使其更加灵活。 I want to make it configurable so that the user can get the average of OtherValue as well as InterestingValue. 我想使其可配置,以便用户可以获取OtherValue和InterestingValue的平均值。 Shouldn't be too hard, the argument to Average() seems to be a Func (since the values are ints in this case). 应该不太困难,Average()的参数似乎是一个Func(因为在这种情况下,值是整数)。 Easy peasy. 十分简单。 Can't I just create a method that returns a Func based on some condition and use that as an argument? 我能否仅创建一种基于某些条件返回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!");
}

And then, elsewhere, I could just do this: 然后,在其他地方,我可以这样做:

var byCity = testGuys
    .GroupBy(c => c.City)
    .Select(g => new { City = g.Key, Avg = g.Average(fieldToAverageBy) });

Well, I thought I could do that. 好吧,我以为我可以做到。 However, when I do enumerate this, NHibernate throws a fit: 但是,当我进行枚举时,NHibernate抛出一个合适的结果:

Object of type 'System.Linq.Expressions.ConstantExpression' cannot be converted to type 'System.Linq.Expressions.LambdaExpression'.

So I am guessing that behind the scenes, some conversion or casting or some such thing is going on that in the first case accepts my lambda, but in the second case makes into something NHibernate can't convert to SQL. 因此,我想在幕后进行某些转换或强制转换或诸如此类的事情,第一种情况接受我的lambda,但第二种情况使NHibernate无法转换为SQL。

My question is hopefully simple - how can my GetAverageField function return something that will work as a parameter to Average() when NHibernate 3.0 LINQ support (the .Query() method) translates this to SQL? 我的问题希望很简单-当NHibernate 3.0 LINQ支持(.Query()方法)将其转换为SQL时,我的GetAverageField函数如何返回将作为Average()参数工作的东西?

Any suggestions welcome, thanks! 欢迎任何建议,谢谢!

EDIT 编辑

Based on the comments from David B in his answer, I took a closer look at this. 根据David B在回答中的评论,我仔细研究了这一点。 My assumption that Func would be the right return type was based on the intellisense I got for the Average() method. 我认为Func将是正确的返回类型的假设是基于我对Average()方法获得的智能感知。 It seems to be based on the Enumerable type, not the Queryable one. 它似乎基于Enumerable类型,而不是Queryable类型。 That's strange.. Need to look a bit closer at stuff. 真是奇怪..需要仔细研究一些东西。

The GroupBy method has the following return signature: GroupBy方法具有以下返回签名:

IQueryable<IGrouping<string,TestGuy>>

That means it should give me an IQueryable, all right. 这意味着它应该可以给我一个IQueryable。 However, I then move on to the next line: 但是,我接着转到下一行:

.Select(g => new { City = g.Key, Avg = g.Average(tg => tg.InterestingValue) });

If I check the intellisense for the g variable inside the new { } object definition, it is actually listed as being of type IGrouping - NOT IQueryable>. 如果我在新{}对象定义中检查g变量的intellisense,则实际上它被列为IGrouping-NOT IQueryable>类型。 This is why the Average() method called is the Enumerable one, and why it won't accept the Expression parameter suggested by David B. 这就是为什么调用的Average()方法是Enumerable方法的原因,以及为什么它不接受David B建议的Expression参数的原因。

So somehow my group value has apparently lost it's status as an IQueryable somewhere. 因此,我的组值显然以某种方式失去了其IQueryable的地位。

Slightly interesting note: 有点有趣的注释:

I can change the Select to the following: 我可以将“选择”更改为以下内容:

.Select(g => new { City = g.Key, Avg = g.AsQueryable<TestGuy>().Average(fieldToAverageBy) });

And now it compiles! 现在可以编译了! Black magic! 黑魔法! However, that doesn't solve the issue, as NHibernate now doesn't love me anymore and gives the following exception: 但是,这并不能解决问题,因为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.

What baffles me is that this works when I give the lambda expression to the Average() method, but that I can't find a simple way to represent the same expression as an argument. 使我感到困惑的是,当我将lambda表达式提供给Average()方法时,这种方法有效,但是我找不到一种简单的方法来表示相同的表达式作为参数。 I am obviously doing something wrong, but can't see what...!? 我显然做错了,但是看不到...!?

I am at my wits end. 我不知道该怎么做。 Help me, Jon Skeet, you're my only hope! 乔恩·斯基特(Jon Skeet),请帮助我,您是我唯一的希望! ;) ;)

You won't be able to call a "local" method within your lambda expression. 您将无法在lambda表达式中调用“本地”方法。 If this were a simple non-nested clause, it would be relatively simple - you'd just need to change this: 如果这是一个简单的非嵌套子句,它将相对简单-您只需要更改以下内容:

private Func<TestGuy,int> GetAverageField(SomeEnum someCondition)

to this: 对此:

private Expression<Func<TestGuy,int>> GetAverageField(SomeEnum someCondition)

and then pass the result of the call into the relevant query method, eg 然后将调用结果传递到相关的查询方法中,例如

var results = query.Select(GetAverageField(fieldToAverageBy));

In this case, however, you'll need to build the whole expression tree up for the Select clause - the anonymous type creation expression, the extraction of the Key, and the extraction of the average field part. 但是,在这种情况下,您需要为Select子句构建整个表达式树-匿名类型创建表达式,Key的提取以及平均字段部分的提取。 It's not going to be fun, to be honest. 老实说,这不会很有趣。 In particular, by the time you've built up your expression tree, that's not going to be statically typed in the same way as a normal query expression would be, due to the inability to express the anonymous type in a declaration. 特别是,在构建表达式树时,由于无法在声明中表示匿名类型,因此不会像普通查询表达式那样静态地键入静态类型。

If you're using .NET 4, dynamic typing may help you, although you'd pay the price of not having static typing any more, of course. 如果您使用的是.NET 4,则动态键入可能会有所帮助,尽管您当然要付出不再拥有静态类型的代价。

One option (horrible though it may be) would be try to use a sort of "template" of the anonymous type projection expression tree (eg always using a single property), and then build a copy of that expression tree, inserting the right expression instead. 一个选择(虽然可能很可怕)是尝试使用一种匿名类型投影表达式树的“模板”(例如,始终使用单个属性),然后构建该表达式树的副本,并插入正确的表达式代替。 Again, it's not going to be fun. 再次,它不会很有趣。

Marc Gravell may be able to help more on this - it does sound like the kind of thing which should be possible, but I'm at a loss as to how to do it elegantly at the moment. 马克·格雷夫(Marc Gravell)也许可以在这方面提供更多帮助-听起来确实像应该做的事情,但是目前我对如何优雅地进行操作感到困惑。

Eh? the parameter to Queryable.Average is not Func<T, U> . Queryable.Average的参数不是Func<T, U> It's Expression<Func<T, U>> 它是Expression<Func<T, U>>

The way to do this is: 这样做的方法是:

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!");
} 

Followed by: 其次是:

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