簡體   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