调用表值函数时添加查询提示

时间:2014-11-05 16:11:26

标签: entity-framework sql-server-2012

我从实体框架调用表值函数,并且需要能够将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;

我看到了这个问题:

How do I control parameter sniffing and/or query hints in entity framework?

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

2 个答案:

答案 0 :(得分:39)

我遇到了这个:

https://entityframework.codeplex.com/wikipage?title=Interception

看来你可以这样做:

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_Start的{​​{1}}中进行了注册):

global.asax.cs

它会让你改变DbInterception.Add(new HintInterceptor()); 。唯一的问题是它现在附加了每个读者查询,这可能是一个问题,因为其中一些可能会受到该提示的负面影响。我猜测我可以用上下文做一些事情来判断提示是否合适,或者更糟糕的是我可以检查CommandText本身。

看起来不是最优雅或细粒度的解决方案。

修改:从CommandText,您可以获得interceptorContext,因此我定义了一个如下所示的界面:

DbContexts

然后创建一个派生自我的原始DbContext(由EF生成)并实现上述接口的类。然后我改变了我的拦截器,看起来像这样:

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

现在使用它,我使用派生类而不是原始类创建上下文,将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)并设置{{1}在我执行命令之前,然后将其设置为false。

为了使这一切更加独立,我最终定义了一个这样的界面:

recompile

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

ApplyHint

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

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

现在使用它,我可以做到这一点:

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; } } 查询提示进行子类化,这样您就不需要指定字符串{ {1}}每次冒着错误的风险),但它解决了我当前的问题。

答案 1 :(得分:2)

您的特定用途之外是否还有fDE_myquery的其他来电者?这种情况多久被召唤一次?问题不在于您的SELECT * FROM dbo.fDE_myquery();获得了次优计划,而是fDE_myquery内的一个或多个查询正在获得次优计划。因此,您可以只将OPTION(RECOMPILE)添加到该TVF内的一个或多个查询中。

如果此TVF被称为很多,那么这将对性能产生负面影响。这就是为什么我问到这个TVF的其他用途:如果这是TVF的唯一用途,或者到目前为止主要用途,那么如果糟糕的计划被频繁接收,那么它可能是值得的。

但如果此TVF中有其他几个呼叫者没有遇到问题,那么将RECOMPILE放入TVF可能不是可行的方法。虽然,在这种情况下,您可以创建封装SELECT * FROM dbo.fDE_myquery() OPTION (RECOMPILE);的包装TVF。这似乎是一个更灵活的解决方案:)。它必须是多功能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;