Как использовать SqlAzureExecutionStrategy и "Nolock"

Чтобы справиться с таймаутами SQL, я пытаюсь использовать SqlAzureExecutionStrategy ( https://msdn.microsoft.com/en-us/data/dn456835.aspx)

Проблема, с которой я сталкиваюсь, заключается в том, что она предотвращает "инициируемые пользователем транзакции", которые, как представляется, являются рекомендуемым способом реализации "с (nolock)" в EF ( http://www.hanselman.com/blog/GettingLINQToSQLAndLINQToEntitiesToUseNOLOCK.aspx, NOLOCK с Linq to SQL).

пример кода

    public AspnetUser GetAspnetUserByUserName(string userName)
    {
        using (var tx = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions() { IsolationLevel = IsolationLevel.ReadUncommitted }))
        {
            return context.AspnetUsers.Where(x => x.UserName == userName).FirstOrDefault();
        }
    }

выдает ошибку

Сконфигурированная стратегия выполнения SqlAzureExecutionStrategy не поддерживает инициированные пользователем транзакции. См. http://go.microsoft.com/fwlink/?LinkId=309381 для получения дополнительной информации.

Я видел ответы, в которых говорится, что нужно отключить SqlAzureExecutionStrategy для каждого вызова, но это лишило бы смысла его использование, если бы все мои чтения игнорировали стратегию. Возможно иметь как "NoLock", так и SqlAzureExecutionStrategy

1 ответ

SqlAzureExecutionStrategy не поддерживает транзакции, инициированные вне действия, которое необходимо повторить. Чтобы обойти это ограничение, вам нужно приостановить стратегию, создать область транзакции и выполнить работу как действие, которое вы вручную передаете в стратегию выполнения для повторной попытки:

public AspnetUser GetAspnetUserByUserName(string userName)
{
    new SuspendableSqlAzureExecutionStrategy().Execute(() =>
        {
            using (var tx = new TransactionScope(
                    TransactionScopeOption.Required,
                    new TransactionOptions() { IsolationLevel = IsolationLevel.ReadUncommitted }))
            {
                return context.AspnetUsers.Where(x => x.UserName == userName).FirstOrDefault();
            }
        });
}

Здесь я использую альтернативу приостановленной стратегии с https://msdn.microsoft.com/en-us/data/dn307226 которая автоматически приостанавливает любые вложенные вызовы:

using System.Data.Entity.Infrastructure;
using System.Data.Entity.SqlServer;
using System.Data.Entity.Utilities;
using System.Runtime.Remoting.Messaging;
using System.Threading;
using System.Threading.Tasks;

public class SuspendableSqlAzureExecutionStrategy : IDbExecutionStrategy
{
    private readonly IDbExecutionStrategy _azureExecutionStrategy;

    public SuspendableSqlAzureExecutionStrategy()
    {
        _azureExecutionStrategy = new SqlAzureExecutionStrategy();
    }

    private static bool Suspend
    {
        get { return (bool?)CallContext.LogicalGetData("SuspendExecutionStrategy") ?? false; }
        set { CallContext.LogicalSetData("SuspendExecutionStrategy", value); }
    }

    public bool RetriesOnFailure
    {
        get { return !Suspend; }
    }

    public virtual void Execute(Action operation)
    {
        if (!RetriesOnFailure)
        {
            operation();
            return;
        }

        try
        {
            Suspend = true;
            _azureExecutionStrategy.Execute(operation);
        }
        finally
        {
            Suspend = false;
        }
    }

    public virtual TResult Execute<TResult>(Func<TResult> operation)
    {
        if (!RetriesOnFailure)
        {
            return operation();
        }

        try
        {
            Suspend = true;
            return _azureExecutionStrategy.Execute(operation);
        }
        finally
        {
            Suspend = false;
        }
    }

    public virtual async Task ExecuteAsync(Func<Task> operation, CancellationToken cancellationToken)
    {
        if (!RetriesOnFailure)
        {
            await operation();
            return;
        }

        try
        {
            Suspend = true;
            await _azureExecutionStrategy.ExecuteAsync(operation, cancellationToken);
        }
        finally
        {
            Suspend = false;
        }
    }

    public virtual async Task<TResult> ExecuteAsync<TResult>(Func<Task<TResult>> operation, CancellationToken cancellationToken)
    {
        if (!RetriesOnFailure)
        {
            return await operation();
        }

        try
        {
            Suspend = true;
            return await _azureExecutionStrategy.ExecuteAsync(operation, cancellationToken);
        }
        finally
        {
            Suspend = false;
        }
    }
}

public class MyConfiguration : DbConfiguration
{
    public MyConfiguration()
    {
        SetExecutionStrategy("System.Data.SqlClient", () => new SuspendableSqlAzureExecutionStrategy());
    }
}
Другие вопросы по тегам