C# NHibernate 3 LINQ-如何为Average()创建有效参数
假设我有一个非常简单的实体,如下所示:C# NHibernate 3 LINQ-如何为Average()创建有效参数,c#,linq,lambda,nhibernate-3,C#,Linq,Lambda,Nhibernate 3,假设我有一个非常简单的实体,如下所示: 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(使用Fl
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事件探查器中,我可以看到生成了正确的SQL,结果与预期一致
受我成功的启发,我想让它更灵活。我想让它可配置,这样用户就可以获得OtherValue和InterestingValue的平均值。不应该太难,Average()的参数似乎是Func(因为本例中的值是int)。简单的豌豆。我不能创建一个基于某个条件返回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!");
}
嗯,我想我能做到。然而,当我列举这一点时,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。它似乎基于可枚举类型,而不是可查询类型。真奇怪。。我们需要仔细看看这些东西
GroupBy方法具有以下返回签名:
IQueryable<IGrouping<string,TestGuy>>
如果我在新{}对象定义中检查intellisense中的g变量,它实际上被列为IGrouping-NOT IQueryable>类型。这就是为什么调用的Average()方法是可枚举的方法,以及为什么它不接受David B建议的表达式参数
因此,不知何故,我的团体价值观显然已经失去了它作为一个可在某处使用的工具的地位
有点有趣的注意:
我可以将选择更改为以下内容:
.Select(g => new { City = g.Key, Avg = g.AsQueryable<TestGuy>().Average(fieldToAverageBy) });
让我感到困惑的是,当我将lambda表达式赋给Average()方法时,这是可行的,但我找不到一种简单的方法来表示与参数相同的表达式。我显然做错了什么,但看不出什么
我束手无策<帮帮我,乔恩·斯基特,你是我唯一的希望强>;) 嗯?要设置的参数不是Func
。它是表达式
方法是:
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!");
}
私有表达式GetAverageExpr(SomeEnum someCondition)
{
开关(某些条件)
{
案例SomeEnum。有趣的是:
返回tg=>tg.InterestingValue;
案例名称枚举。其他:
返回tg=>tg.OtherValue;
}
抛出新的InvalidOperationException(“不在我的示例中!”);
}
其次是:
Expression<Func<TestGuy, int>> averageExpr = GetAverageExpr(someCondition);
var byCity = testGuys
.GroupBy(c => c.City)
.Select(g => new { City = g.Key, Avg = g.Average(averageExpr) });
Expression averageExpr=GetAverageExpr(someCondition);
var byCity=testGuys
.GroupBy(c=>c.City)
.Select(g=>new{City=g.Key,Avg=g.Average(averageExpr)});
您将无法在lambda表达式中调用“local”方法。如果这是一个简单的非嵌套子句,它将相对简单-您只需要更改它:
private Func<TestGuy,int> GetAverageField(SomeEnum someCondition)
但是,在这种情况下,您需要为Select
子句构建整个表达式树-匿名类型创建表达式、键的提取和平均字段部分的提取。老实说,这不会很有趣。特别是,当您构建表达式树时,由于无法在声明中表示匿名类型,它将不会以与普通查询表达式相同的方式进行静态类型化
如果您使用的是.NET4,动态键入可能会对您有所帮助,当然,您会为不再使用静态键入付出代价
一种选择(尽管可能很可怕)是尝试使用匿名类型投影表达式树的某种“模板”(例如,始终使用单个属性),然后构建该表达式树的副本,插入正确的表达式。再说一次,这不会很有趣
Marc Gravell可能会在这方面提供更多帮助-这听起来确实是应该可能的,但我现在不知道如何优雅地做这件事。谢谢,这是有道理的,但我尝试这样做时会犯一些错误;无法在注释中设置太多格式,但错误1:错误1实例参数:无法从“System.Linq.IGrouping”转换为“System.Linq.IQueryable”“error 2”“System.Linq.IGrouping”“不包含“Average”的定义和“System.Linq.Queryable.Average”的最佳扩展方法重载(System.Linq.IQueryable,System.Linq.Expressions.Expression)“有一些无效的论据我可能忽略了一些非常明显的东西,但我想不出来…”。。。!?
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) });
private Func<TestGuy,int> GetAverageField(SomeEnum someCondition)
private Expression<Func<TestGuy,int>> GetAverageField(SomeEnum someCondition)
var results = query.Select(GetAverageField(fieldToAverageBy));