1

I have configured a retry strategy with Entity Framework (.NET framework) which works OK most of the time. I've never seen it not working for updates/write. But I do occasionally get issues that do not get intercepted.

In particular, I have a case where a deadlock is encountered during reading data. Then the retry mechanism doesn't work, as is visible from this stack trace excerpt:

Exception: System.Data.Entity.Core.EntityCommandExecutionException: An error occurred while reading from the store provider's data reader. See the inner exception for details. ---> System.Data.SqlClient.SqlException: Transaction (Process ID 56) was deadlocked on lock | communication buffer resources with another process and has been chosen as the deadlock victim. Rerun the transaction.
   at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
   at System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
   at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose)
   at System.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady)
   at System.Data.SqlClient.SqlDataReader.TryHasMoreRows(Boolean& moreRows)
   at System.Data.SqlClient.SqlDataReader.TryReadInternal(Boolean setTimeout, Boolean& more)
   at System.Data.SqlClient.SqlDataReader.Read()
   at System.Data.Entity.Core.Common.Internal.Materialization.Shaper`1.StoreRead()
   --- End of inner exception stack trace ---
   at System.Data.Entity.Core.Common.Internal.Materialization.Shaper`1.HandleReaderException(Exception e)
   at System.Data.Entity.Core.Common.Internal.Materialization.Shaper`1.StoreRead()
   at System.Data.Entity.Core.Common.Internal.Materialization.Shaper`1.SimpleEnumerator.MoveNext()
   at System.Data.Entity.Internal.LazyEnumerator`1.MoveNext()

Here is an equivalent snipped of the code that causes it. It's the iterator in foreach that hits the deadlock, not the SaveChanges() method:

using (var context = new MyContest())
{
    var query = context.messages.Where(m => m.unique_id == uniqueId);
    foreach (var message in query) // <- The exception is hit here.
    {
        message.processed = 1;
        message.update_date = DateTime.Now;
    }
    context.SaveChanges();
}

The context is bound to the retry strategy (and some logging interceptor) by a config class attribute:

[DbConfigurationType(typeof(MyDbConfiguration))]
public partial class MyContext

Lastly, the configuration contains the retry strategy, and some logging:

public CommonDbConfiguration()
{
    DbInterception.Add(new CommonDbLogInterceptor(s =>
        {
            if (string.IsNullOrWhiteSpace(s)) return;
            // some logging
        }
        ));
    SetExecutionStrategy("System.Data.SqlClient", () => new CommonExecutionStrategy());
}

I expect that the retry and logging interceptions would kick in and would be visible similarly to that update failure which got retried successfully:

   at System.Data.Entity.Infrastructure.Interception.DbCommandDispatcher.Reader(DbCommand command, DbCommandInterceptionContext interceptionContext)
   at System.Data.Entity.Core.Mapping.Update.Internal.DynamicUpdateCommand.Execute(Dictionary`2 identifierValues, List`1 generatedValues)
   at System.Data.Entity.Core.Mapping.Update.Internal.UpdateTranslator.Update()
   at System.Data.Entity.Core.Objects.ObjectContext.ExecuteInTransaction[T](Func`1 func, IDbExecutionStrategy executionStrategy, Boolean startLocalTransaction, Boolean releaseConnectionOnSuccess)
   at System.Data.Entity.Core.Objects.ObjectContext.SaveChangesToStore(SaveOptions options, IDbExecutionStrategy executionStrategy, Boolean startLocalTransaction)
   at System.Data.Entity.Infrastructure.DbExecutionStrategy.Execute[TResult](Func`1 operation)
   at System.Data.Entity.Core.Objects.ObjectContext.SaveChangesInternal(SaveOptions options, Boolean executeInExistingTransaction)
   at System.Data.Entity.Internal.InternalContext.SaveChanges()

Is there a way to remedy this without me having to call the execution strategy or some other retry mechanism explicitly?

tonank
  • 73
  • 7
  • 1
    I'm not sure, but you can use the library named Polly. Polly allows you to create retry strategies, in first instance it's used to retry HTTP class, but you can create generic policies so you should be able to do it with EF requests. – OrcusZ Dec 05 '19 at 13:12
  • Could you please add codes where you bind Interception to EF as well? – Selim Yildiz Dec 06 '19 at 04:46
  • 1
    Have you looked at https://stackoverflow.com/a/38442826/578552 ? – rfcdejong Dec 06 '19 at 08:55
  • Thanks, I had a look. My shouldRetryOn currently returns true in most cases, definitely deadlocks included. – tonank Dec 06 '19 at 09:01

0 Answers0