2009-08-20 52 views
8

假設我有一個IQueryable<T>表達式,我想要封裝定義,將其存儲並重用,或稍後將其嵌入到更大的查詢中。例如:如何維護LINQ延遲執行?

IQueryable<Foo> myQuery = 
    from foo in blah.Foos 
    where foo.Bar == bar 
    select foo; 

現在我相信我可以保留myQuery對象並像我描述的那樣使用它。但有些事情我不確定:

  1. 如何最好地參數化它?最初我已經在方法中定義了這個,然後返回IQueryable<T>作爲該方法的結果。這樣我可以將blahbar定義爲方法參數,我想它只是每次創建一個新的IQueryable<T>。這是封裝IQueryable<T>邏輯的最佳方式嗎?有其他方法嗎?

  2. 如果我的查詢解析爲標量而不是IQueryable會怎麼樣?例如,如果我想要這個查詢完全如圖所示,但是追加.Any()只是讓我知道是否有匹配的結果?如果我添加(...).Any()那麼結果是bool並立即執行,對吧?有沒有辦法利用這些Queryable運營商(Any,SindleOrDefault等)沒有立即執行? LINQ到SQL如何處理這個問題?

編輯:第2部分是真的更多的想了解什麼是IQueryable<T>.Where(Expression<Func<T, bool>>)IQueryable<T>.Any(Expression<Func<T, bool>>)之間的限制不同。看起來好像後者在執行被延遲時創建更大的查詢時不那麼靈活。可以附加Where(),然後可以添加其他結構,最後執行。由於Any()返回一個標量值,因此它聽起來像在構建查詢的其餘部分之前它會立即執行。

回答

5
  1. 你必須非常小心周圍IQueryables傳球,當你使用一個DataContext,因爲一旦場景中獲得的配置,您將無法對IQueryable的執行了。如果你沒有使用上下文,那麼你可能會沒事,但要注意這一點。

  2. .Any()和.FirstOrDefault()分別爲而不是推遲。當你給他們打電話時,他們導致執行發生。但是,這可能不符合你的想法。例如,如果您在IQueryable上執行.Any(),則在LINQ to SQL中,它基本上充當IF EXISTS(SQL HERE)。

您可以鏈的IQueryable的沿着這樣的,如果你想:

var firstQuery = from f in context.Foos 
        where f.Bar == bar 
        select f; 

var secondQuery = from f in firstQuery 
        where f.Bar == anotherBar 
        orderby f.SomeDate 
        select f; 

if (secondQuery.Any()) //immediately executes IF EXISTS(second query in SQL) 
{ 
    //causes execution on second query 
    //and allows you to enumerate through the results 
    foreach (var foo in secondQuery) 
    { 
     //do something 
    } 

    //or 

    //immediately executes second query in SQL with a TOP 1 
    //or something like that 
    var foo = secondQuery.FirstOrDefault(); 
} 
+0

這聽起來像在#1,具有一個新的'IQueryable'每次基本構造的方法是*好事*,因爲這樣我就不會遇到與處置問題。在#2中,我很困惑LINQ-to-SQL如何翻譯「Any」運算符,但我不能推遲。如果我在較大的查詢中使用「Any」運算符,那麼它會立即在那裏執行,或者它是否是較大查詢執行的一部分? – mckamey 2009-08-20 19:16:05

+0

好吧,我想我快到了。如果我將'.Any()'嵌入到'where'子句中,那麼它將不會在循環中執行它,對嗎?它會編譯爲適當的SQL表達式並將其發送。所以實際上,它不是'.Any()',它可以防止延遲執行,因爲它是如何被使用的。基本上,如果* whole *查詢的結果是標量,那麼編譯器會計算出您現在需要的結果,而不是繼續構建'IQueryable '。 – mckamey 2009-08-21 15:13:41

+1

@McKAMEY正確,只要你在不延遲的上下文中使用.Any(),那麼它將執行。在.Where()的情況下,它正在尋找一個可推遲的表達式,所以你沒事。在var或foreach循環的情況下,這些會導致執行,因爲它們不是可延遲的。 – Joseph 2009-08-21 15:42:33

2

一個更好的選擇比緩存IQueryable的對象是緩存表達式樹。所有IQueryable對象都有一個名爲Expression(我相信)的屬性,它表示該查詢的當前表達式樹。

在稍後的時間點,您可以通過調用queryable.Provider.CreateQuery(表達式)或直接在提供程序的任何位置(在您的情況下是Linq2Sql數據上下文)重新創建查詢。

但是,參數化這些表達式樹會稍微困難一些,因爲它們使用ConstantExpressions構建值。爲了參數化這些查詢,每次需要不同的參數時,您都必須重新生成查詢。

+1

我想說參數化(或者更重要的是封裝一個邏輯單元)是真正的目標,而不是緩存。考慮到C#編譯器已經對它進行了轉換,我不認爲運行時等價物會有很大的使用/性能優勢(就像緩存所暗示的那樣)。 – mckamey 2009-08-21 15:06:24

+0

你能解釋爲什麼這個選項更好嗎?據我瞭解,你只是解開「表達式」來重新包裝它:爲什麼不保持包裝原樣? – PPC 2013-01-28 20:34:00

1

Any()用這種方式推遲了。

var q = dc.Customers.Where(c => c.Orders.Any()); 

Any()用這樣的方式是不是推遲,但仍轉換爲SQL(整個客戶表中沒有加載到內存中)。

bool result = dc.Customers.Any(); 

如果你想有一個延遲的任何(),這樣來做:

public static class QueryableExtensions 
{ 
    public static Func<bool> DeferredAny<T>(this IQueryable<T> source) 
    { 
    return() => source.Any(); 
    } 
} 

被稱爲像這樣:

Func<bool> f = dc.Customers.DeferredAny(); 
bool result = f(); 

缺點是,這種技術不會允許子查詢。

+0

當你說推遲,這聽起來像你的意思是我可以定義一個lambda表達式(或委託),但不能立即執行它。我認爲LINQ的「延遲執行」概念有一個微妙之處,它允許操作成爲更大的表達式樹的一部分,然而它可以由提供者解釋,但是它需要。 我的問題是更多地試圖弄清楚'IQueryable 。Where(Expression >)'與''IQueryable 。任何(表達式>),因爲它好像後者不夠靈活。 – mckamey 2009-08-20 21:15:47

+1

這種不靈活性來自於回報類型的差異。名爲'bool'的類型不能有延遲行爲。 – 2009-08-20 23:41:18

+0

這個建議對我來說似乎是線程不安全的(考慮DataContext) – 2009-10-15 11:46:26

0

表達

Func[Bar,IQueryable[Blah],IQueryable[Foo]] queryMaker = 
(criteria, queryable) => from foo in queryable.Foos 
     where foo.Bar == criteria 
     select foo; 

中創建您的查詢的部分應用程序,然後你可以使用它...

IQueryable[Blah] blah = context.Blah; 
Bar someCriteria = new Bar(); 
IQueryable[Foo] someFoosQuery = queryMaker(blah, someCriteria); 

的查詢可以在一個類中被封裝,如果你想使其更便攜/可重用。

public class FooBarQuery 
{ 
    public Bar Criteria { get; set; } 

    public IQueryable[Foo] GetQuery(IQueryable[Blah] queryable) 
    { 
    return from foo in queryable.Foos 
     where foo.Bar == Criteria 
     select foo; 
    } 
}