Добавление подсказки при вызове функции с табличными значениями

Я вызываю табличную функцию из структуры сущности и должен иметь возможность добавить к ней 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;

Я видел этот вопрос:

Как я могу контролировать параметры нюхания и/или подсказки в структуре сущности?

Но он старый, и принятое решение действительно не дает достаточной информации о том, как реально реализовать предлагаемое решение (использовать справочники плана) с инфраструктурой сущности. Если это единственное решение, как вы все равно получаете структуру сущностей для использования руководства по планированию?

Ответ 1

Я наткнулся на это:

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 of 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; }
}

И расширил мой контекст db как это (вы могли бы, конечно, просто использовать частичный класс для расширения класса, сгенерированного 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 каждый раз и рисковать опечаткой), но он решил мою непосредственную проблему.

Ответ 2

Существуют ли другие вызывающие лица 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);. Это было бы более гибким решением:). Это должно было быть Multistatment TVF вместо типично лучшего Inline TVF, поскольку я только что попробовал, и Inline TVF, похоже, не ценит предложение OPTION, но Multistatement TVF отлично справляется с этим.

EDIT:
Или, если вы хотите обработать это чисто в 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;