My original problem was that I was experiencing deadlocks often when updating my SQL database. Through a little bit of research, I found that I'm able to define a custom DbConfiguration and with it a DbExecutionStrategy which instructs Entity Framework to automatically retry after getting certain errors after x milliseconds and y number of times. Great!
So, following the guide at https://msdn.microsoft.com/en-us/data/jj680699, I build my custom DbConfiguration, which is being used, but the associated DbExecutionStrategy seems to be ignored.
Originally, my entire DbConfiguration was being ignored, but I found that was because I was referencing it in my app.config as well as decorating my entity constructor with the DbConfigurationType attribute [DbConfigurationType(typeof(MyConfiguration))]. Now that I'm only using the app.config, at least my custom configuration is being called.
In its simplest form, my custom config looks like this:
public class MyConfiguration : DbConfiguration
{
public MyConfiguration()
{
System.Windows.MessageBox.Show("Hey! Here I am!"); //I threw this in just to check that I was calling the constructor. Simple breakpoints don't seem to work here.
SetExecutionStrategy("System.Data.SqlClient", () => new MyExecutionStrategy(3, TimeSpan.FromMilliseconds(500)));
}
}
My custom DbConfiguration is referenced in my app.config like so:
<entityFramework codeConfigurationType="MyDataLayer.MyConfiguration, MyDataLayer">
...
</entityFramework>
My custom DbExecutionStrategy is built like so:
private class MyExecutionStrategy : DbExecutionStrategy
{
public MyExecutionStrategy() : this(3, TimeSpan.FromSeconds(2))
{
System.Windows.MessageBox.Show($"MyExecutionStrategy instantiated through default constructor.");
}
public MyExecutionStrategy(int maxRetryCount, TimeSpan maxDelay) : base(maxRetryCount, maxDelay)
{
System.Windows.MessageBox.Show($"MyExecutionStrategy instantiated through parametered constructor.");
}
protected override bool ShouldRetryOn(Exception ex)
{
System.Windows.MessageBox.Show($"Overriding ShouldRetryOn.");
bool retry = false;
SqlException sqlException = GetSqlException(ex);
if (sqlException != null)
{
int[] errorsToRetry =
{
1205, //Deadlock
-2 //Timeout
};
if (sqlException.Errors.Cast<SqlError>().Any(x => errorsToRetry.Contains(x.Number)))
{
retry = true;
}
}
if (ex is TimeoutException)
{
retry = true;
}
return retry;
}
}
I'm not hitting anything at all in this particular piece of the code.
One thing that may be of note, is that every example I've seen so far (for example http://blog.analystcircle.com/2015/08/01/connection-resiliency-in-entity-framework-6-0-and-above/) has casted the exception in ShouldRetryOn directly to a SqlException using
SqlException sqlException = ex as SqlException;
I found that using this method always resulted in a null SqlException because my program is throwing an EntityException which can't be cast into a SqlException. My underlying SqlException is actually the inner exception of the inner exception of the EntityException. So, I put together a short recursive call to dig in and find it.
private SqlException GetSqlException(Exception ex)
{
SqlException result = ex as SqlException;
if (result == null && ex.InnerException != null)
result = GetSqlException(ex.InnerException);
return result;
}
This works properly, but the fact that I need to do it when the examples I've found don't is probably a clue as to what's going wrong. Do EntityExceptions not trigger a DbExecutionStrategy? If not, why is this listed as a solution to be used with EF 6? Any insight would be much appreciated.
EDIT: Doing some more digging into the source for DbExecutionStrategy (https://github.com/aspnet/EntityFramework6/blob/master/src/EntityFramework/Infrastructure/DbExecutionStrategy.cs), I've found that my recursive function to find my SqlException from the EntityException is unnecessary. DbExecutionStrategy has a function UnwrapAndHandleException which does just that and passes the SqlException on to ShouldRetryOn. So, it seems I'm right back at square one.
EDIT 2: Not really a solution, because it doesn't explain why my DbExecutionStrategy isn't being called as it should, but I have found that if I explicitly call the execution strategy, it works.
The code to use the execution strategy explicitly is:
var executionStrategy = new MyConfiguration.MyExecutionStrategy();
executionStrategy.Execute(
() =>
{
//build your context and execute db functions here
using (var context = new Entities())
{
...do stuff
}
});