To deal with SQL timeouts I'm trying to use SqlAzureExecutionStrategy (https://msdn.microsoft.com/en-us/data/dn456835.aspx)
The problem I am running into is it prevents "user initiated transactions" which seem to be the recommended way to implement "with (nolock)" in EF (http://www.hanselman.com/blog/GettingLINQToSQLAndLINQToEntitiesToUseNOLOCK.aspx, NOLOCK with Linq to SQL).
example code
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();
}
}
throws error
The configured execution strategy 'SqlAzureExecutionStrategy' does not support user initiated transactions. See http://go.microsoft.com/fwlink/?LinkId=309381 for additional information.
I've seen the answers that say to turn off the SqlAzureExecutionStrategy on a per call basis, but that would defeat the purpose of using it, if all my reads ignored the strategy. It is possible to have both "NoLock" and SqlAzureExecutionStrategy
SqlAzureExecutionStrategy doesn't support transactions initiated outside the action to be retried. To work around this restriction you would need to suspend the strategy, create the transaction scope and do the work as an action that you manually pass to the execution strategy to be retried:
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();
}
});
}
Here I am using an alternative to the suspendable strategy from https://msdn.microsoft.com/en-us/data/dn307226 that will suspend any nested invocations automatically:
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());
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With