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