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

Entity framework 在调用表值函数时添加查询提示,entity-framework,sql-server-2012,Entity Framework,Sql Server 2012,我正在从entity framework调用一个表值函数,需要能够向其中添加选项(重新编译),因为它选择的执行计划不是最优的。在SQL Server Management Studio中运行查询时,其外观如下所示: select * from dbo.fDE_myquery(0, 0, 3309, '7/1/2013', '7/1/2014', 0, 0) option (recompile) public class HintInterceptor : DbCommandI

我正在从entity framework调用一个表值函数,需要能够向其中添加
选项(重新编译)
,因为它选择的执行计划不是最优的。在SQL Server Management Studio中运行查询时,其外观如下所示:

select 
       * 
from dbo.fDE_myquery(0, 0, 3309, '7/1/2013', '7/1/2014', 0, 0)
option (recompile)
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);
    }
}
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);
    }
}
public interface IQueryHintContext
{
    string QueryHint { get; set; }
    bool ApplyHint { get; set; }
}
根据EF的说法,没有办法添加这个提示,阿福。EF部分看起来像:

var query = from f in ctx.fDE_myQuery(aBool, anotherBool, StartDate, 
            EndDate, someInt, moreBool)
            select f;
我看到这个问题:

但它很旧,而且公认的解决方案并没有提供足够的信息,说明如何使用实体框架实际实现建议的解决方案(使用计划指南)。如果这是唯一的解决方案,您如何让entity framework使用计划指南?

我遇到了以下问题:

看起来你可以这样做:

select 
       * 
from dbo.fDE_myquery(0, 0, 3309, '7/1/2013', '7/1/2014', 0, 0)
option (recompile)
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);
    }
}
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);
    }
}
public interface IQueryHintContext
{
    string QueryHint { get; set; }
    bool ApplyHint { get; set; }
}
它将允许您更改
命令文本
。唯一的问题是,它现在附加到每个读者查询,这可能是一个问题,因为其中一些可能会受到该提示的负面影响。我猜我可以通过上下文来判断提示是否正确,或者更糟的是,我可以检查
CommandText
本身

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

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

select 
       * 
from dbo.fDE_myquery(0, 0, 3309, '7/1/2013', '7/1/2014', 0, 0)
option (recompile)
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);
    }
}
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);
    }
}
public interface IQueryHintContext
{
    string QueryHint { get; set; }
    bool ApplyHint { get; set; }
}
然后创建了一个从原始DbContext(由EF生成)派生的类,并实现了上述接口。然后我将拦截器更改为如下所示:

select 
       * 
from dbo.fDE_myquery(0, 0, 3309, '7/1/2013', '7/1/2014', 0, 0)
option (recompile)
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);
    }
}
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);
    }
}
public interface IQueryHintContext
{
    string QueryHint { get; set; }
    bool ApplyHint { get; set; }
}
并像这样扩展了我的db上下文(当然,您也可以使用分部类来扩展EF生成的类):

然后,为了使打开、关闭部分更易于处理,我定义了以下内容:

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
query提示,这样您就不需要每次都指定字符串
recompile
,从而冒着键入错误的风险),但它解决了我眼前的问题。

在您的特定用法之外,是否还有其他的
fDE\u myquery
调用方?多久打一次电话?问题不是您的
SELECT*FROM dbo.fDE_myquery()
正在获得次优计划,即
fDE_myquery
中的一个或多个查询正在获得次优计划。因此,您可以将
选项(重新编译)
添加到该TVF中的一个或多个查询中

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

但是,如果此TVF的其他几个调用者没有遇到问题,则将
重新编译
放入TVF中可能不是一个好办法。不过,在这种情况下,您可以创建一个包装器TVF来封装
SELECT*FROM dbo.fDE_myquery()选项(重新编译)。这似乎是一个更灵活的解决方案:)。它必须是一个多状态TVF,而不是通常更好的内联TVF,因为我刚刚尝试过它,内联TVF似乎不喜欢
选项
子句,但多状态TVF对它很好

编辑:
或者,如果您想纯粹在EF中处理此问题,只需使用一行代码发出重新编译请求:

ctx.context.ExecuteStoreCommand("EXEC sp_recompile 'dbo.fDE_myquery';");
然后做你的:


我确信我可以在TVF中进行拼写(我没有写),甚至把TVF包裹在另一个TVF中,只是用所需的查询提示调用它,但我认为这是最后的办法。似乎应该有一种方法可以在EF中指定一个查询提示,而不必更改数据库中的内容。@MattBurland:这很公平。我刚刚有了另一个想法,我更新了我的答案。有不止一种方法可以获得
重新编译
:)。您能详细解释一下您是如何调用它的吗?添加
选项(重新编译)
花费了>10秒的linq查询,我已经处理了几天了,一直到@drexdrex:我扩展了一点。希望能有所帮助。我要特别感谢@MattBurland提供的这种清洁解决方案。我根据你的拦截器调整了我的项目,现在我对执行时间非常满意。Briliant解决方案!!我提到过。