22

我正在从实体框架调用一个表值函数,并且需要能够添加option (recompile)它,因为它选择的执行计划不是最优的。在 SQL Server Management Studio 中运行查询,它看起来像这样:

select 
       * 
from dbo.fDE_myquery(0, 0, 3309, '7/1/2013', '7/1/2014', 0, 0)
option (recompile)

来自 EF,没有办法添加该提示,AFAIK。EF 部分看起来像:

var query = from f in ctx.fDE_myQuery(aBool, anotherBool, StartDate, 
            EndDate, someInt, moreBool)
            select f;

我看到了这个问题:

如何控制实体框架中的参数嗅探和/或查询提示?

但它已经过时了,并且公认的解决方案并没有真正提供足够的信息来说明如何使用实体框架实际实施建议的解决方案(使用计划指南)。如果这是唯一的解决方案,那么您如何让实体框架使用计划指南?

4

2 回答 2

41

我遇到了这个:

https://entityframework.codeplex.com/wikipage?title=拦截

看来您可以执行以下操作:

public class HintInterceptor : DbCommandInterceptor
{
    public override void ReaderExecuting(System.Data.Common.DbCommand command, DbCommandInterceptionContext<System.Data.Common.DbDataReader> interceptionContext)
    {
        command.CommandText += " option (recompile)";
        base.ReaderExecuting(command, interceptionContext);
    }
}

并像这样注册它(我在Application_Startof 中做到了global.asax.cs):

DbInterception.Add(new HintInterceptor());

它会让你改变CommandText. 唯一的问题是它现在附加到每个读者查询中,这可能是一个问题,因为其中一些可能会受到该提示的负面影响。我猜我可以根据上下文做一些事情来确定提示是否合适,或者更糟糕的情况我可以检查它CommandText本身。

似乎不是最优雅或最细粒度的解决方案。

编辑:从interceptorContext,你可以得到DbContexts,所以我定义了一个如下所示的接口:

public interface IQueryHintContext
{
    string QueryHint { get; set; }
    bool ApplyHint { get; set; }
}

然后创建了一个派生自我原来的 DbContext(由 EF 生成)并实现上述接口的类。然后我将拦截器更改为如下所示:

public class HintInterceptor : DbCommandInterceptor
{
    public override void ReaderExecuting(System.Data.Common.DbCommand command, DbCommandInterceptionContext<System.Data.Common.DbDataReader> interceptionContext)
    {
        if (interceptionContext.DbContexts.Any(db => db is Dal.IQueryHintContext))
        {
            var ctx = interceptionContext.DbContexts.First(db => db is Dal.IQueryHintContext) as Dal.IQueryHintContext;
            if (ctx.ApplyHint)
            {
                command.CommandText += string.Format(" option ({0})", ctx.QueryHint);
            }
        }
        base.ReaderExecuting(command, interceptionContext);
    }
}

现在要使用它,我使用派生类而不是原始类创建一个上下文,设置QueryHint为我想要的任何内容(recompile在本例中)并ApplyHint在我执行命令之前设置,然后将其设置回 false。

为了让这一切更加独立,我最终定义了一个这样的接口:

public interface IQueryHintContext
{
    string QueryHint { get; set; }
    bool ApplyHint { get; set; }
}

并像这样扩展了我的数据库上下文(当然,您也可以使用部分类来扩展 EF 生成的类):

public class MyEntities_Ext : MyEntities, IQueryHintContext
{
    public string QueryHint { get; set; }
    public bool ApplyHint { get; set; }
}

然后,为了使开启、关闭部分更容易处理,我定义了这个:

public class HintScope : IDisposable
{
    public IQueryHintContext Context { get; private set; }
    public void Dispose()
    {
        Context.ApplyHint = false;
    }

    public HintScope(IQueryHintContext context, string hint)
    {
        Context = context;
        Context.ApplyHint = true;
        Context.QueryHint = hint;
    }
}

现在要使用它,我可以这样做:

using (var ctx = new MyEntities_Ext()) 
{
    // any code that didn't need the query hint
    // ....
    // Now we want the query hint
    using (var qh = new HintScope(ctx, "recompile"))
    {
        // query that needs the recompile hint
    }
    // back to non-hint code
}

这可能有点矫枉过正并且可以进一步开发(例如,使用枚举而不是字符串来获取可用提示 - 或子类化recompile查询提示,这样您就不需要recompile每次都指定字符串并冒着打字错误的风险),但它解决了我的直接问题。

于 2014-11-05T16:55:15.170 回答
3

fDE_myquery在您的特定用途之外还有其他来电者吗?多久调用一次?问题不在于您SELECT * FROM dbo.fDE_myquery();获得了次优计划,而在于其中的一个或多个查询fDE_myquery获得了次优计划。因此,您可以OPTION(RECOMPILE)在该 TVF 中添加一个或多个查询。

如果多次调用此 TVF,则会对性能产生负面影响。这就是为什么我询问此 TVF 的其他用途:如果这是此 TVF 的唯一用途,或者迄今为止主要用途,那么如果经常选择糟糕的计划,那可能是值得的。

但是,如果此 TVF 的其他几个调用者没有遇到问题,那么将其RECOMPILE放入 TVF 可能不是可行的方法。虽然,在这种情况下,您可以创建一个包装 TVF 来封装SELECT * FROM dbo.fDE_myquery() OPTION (RECOMPILE);. 这似乎是一个更灵活的解决方案:)。它必须是多语句 TVF,而不是我刚刚尝试过的通常更好的内联 TVF,并且内联 TVF 似乎不喜欢该OPTION子句,但多语句 TVF 很好。

编辑:
或者,如果您想纯粹在 EF 中处理这个问题,您可以简单地使用一行代码发出重新编译请求:

ctx.context.ExecuteStoreCommand("EXEC sp_recompile 'dbo.fDE_myquery';");

然后你的:

var query = from f in ctx.fDE_myQuery(aBool, anotherBool, StartDate, 
            EndDate, someInt, moreBool)
            select f;
于 2014-11-05T17:41:03.140 回答