重写LINQ表达式查询以启用缓存SQL执行计划

Edd*_*fen 8 c# linq sql-server entity-framework

在阅读有关实体框架性能的文章时,我发现了这条信息:

其次,问题[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();
Run Code Online (Sandbox Code Playgroud)

在这种风格:

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();
Run Code Online (Sandbox Code Playgroud)

这允许实体框架知道这些是变量,并且生成的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);
Run Code Online (Sandbox Code Playgroud)

问题是使用Expression.Constant(convertedId)导致将常量插入到生成的SQL中.这会导致SQL更改为您查找的每个新项目,从而停止任何执行计划缓存:

WHERE [Extent1].[Id] = 1234
Run Code Online (Sandbox Code Playgroud)

和:

WHERE [Extent1].[Id] = 1235
Run Code Online (Sandbox Code Playgroud)

和:

WHERE [Extent1].[Id] = 1236
Run Code Online (Sandbox Code Playgroud)

接下来的问题是,如何以强制生成SQL参数化的方式使用Expression构建?() => convertedId语法将无法正常工作.我在下面回答了这个问题.

Edd*_*fen 5

经过大量的反复试验,我们发现您仍然可以convertedId通过稍微改变我们传递它的方式来强制Entity Framework识别为参数:

....

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);
Run Code Online (Sandbox Code Playgroud)

这导致生成的SQL对任何给定的id使用相同的参数(和代码):

WHERE [Extent1].[Id] = @p__linq__0 
Run Code Online (Sandbox Code Playgroud)

我们正在处理的查询需要很长时间来生成执行计划,因此我们看到访问新ID的执行时间显着减少(从3~4秒减少到~300毫秒)