簡體   English   中英

重寫LINQ表達式查詢以啟用緩存SQL執行計划

[英]Rewriting a LINQ Expression query to enable caching SQL Execution Plan

在閱讀有關實體框架性能的文章時,我發現了這條信息:

其次,問題[SQL Server不會重用執行計划]首先發生因為(由於實現細節)將int傳遞給Skip()和Take()方法,Entity Framework無法查看是否它們傳遞的絕對值如Take(100)或變量如Take(resultsPerPage),因此它不知道該值是否應該參數化。

建議的解決方案是改變這種代碼風格:

var schools = db.Schools
    .OrderBy(s => s.PostalZipCode)
    .Skip(model.Page * model.ResultsPerPage)
    .Take(model.ResultsPerPage)
    .ToList();

在這種風格:

int resultsToSkip = model.Page * model.ResultsPerPage;
var schools = db.Schools
    .OrderBy(s => s.PostalZipCode)
    .Skip(() => resultsToSkip) //must pre-calculate this value
    .Take(() => model.ResultsPerPage)
    .ToList();

這允許實體框架知道這些是變量,並且生成的SQL應該被參數化,這反過來允許重用執行計划。

我們的應用程序中有一些代碼以相同的方式使用變量,但我們必須在運行時構建Expression,因為事先不知道類型。

以下是它過去的樣子:

var convertedId = typeof(T).GetConvertedIdValue(id);
var prop = GetIdProperty(typeof(T));

var itemParameter = Expression.Parameter(typeof(T), "item");
var whereExpression = Expression.Lambda<Func<T, bool>>
    (
    Expression.Equal(
        Expression.Property(
            itemParameter,
            prop.Name
            ),
        Expression.Constant(convertedId)
        ),
    new[] { itemParameter }
    );

return Get<T>().Where(whereExpression);

問題是使用Expression.Constant(convertedId)會導致將常量插入到生成的SQL中。 這會導致SQL更改為您查找的每個新項目,從而停止任何執行計划緩存:

WHERE [Extent1].[Id] = 1234

和:

WHERE [Extent1].[Id] = 1235

和:

WHERE [Extent1].[Id] = 1236

接下來的問題是, 如何以強制生成SQL參數化的方式使用Expression構建? () => convertedId語法不起作用。 我在下面回答了這個問題。

經過大量的反復試驗,我們發現您仍然可以通過稍微改變傳入方式來強制Entity Framework將convertedId識別為參數:

....

var convObj = new
{
    id = convertedId
};
var rightExp = Expression.Convert(Expression.Property(Expression.Constant(convObj), "id"), convertedId.GetType());

var whereExpression = Expression.Lambda<Func<T, bool>>
    (
    Expression.Equal(
        Expression.Property(
            itemParameter,
            prop.Name
            ),
        rightExp
        ),
    new[] { itemParameter }
    );

return Get<T>().Where(whereExpression);

這導致生成的SQL對任何給定的id使用相同的參數(和代碼):

WHERE [Extent1].[Id] = @p__linq__0 

我們正在處理的查詢需要很長時間來生成執行計划,因此我們看到訪問新ID的執行時間顯着減少(從3~4秒減少到~300毫秒)

讓我回顧一下。

你正在構建Expression<Func<T, bool>>

var item = Expression.Parameter(typeof(T), "item");
var left = Expression.Property(item, idPropertyName);
Expression right = ...;
var body = Expression.Equal(left, right);
var predicate = Expression.Lambda<Func<T, bool>>(body, item);

問題是什么應該用於right ,以使EF不將其視為常數。

顯然原始價值就像

var right = Expression.Convert(Expression.Constant(convertedId), left.Type);

不起作用,所以解決方案是提供某個類實例屬性 你通過使用匿名類型解決了它,但當然還有很多其他方法可以做到這一點。

例如,使用閉包(就像你沒有手動創建表達式一樣)

Expression<Func<object>> closure = () => convertedId;
var right = Expresion.Convert(closure.Body, left.Type);

或者Tuple<T>實例(有點冗長,但是消除了Expression.Convert

var tuple = Activator.CreateInstance(
    typeof(Tuple<>).MakeGenericType(left.Type), convertedId);
var right = Expression.Property(Expression.Constant(tuple), "Item1");

等等

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM