我的问题是在更新 SQL 数据库时经常出现死锁。通过一些研究,我发现可以定义自定义的 DbConfiguration,并使用一个 DbExecutionStrategy,在 x 毫秒和 y 次数后出现某些错误后指示 Entity Framework 自动重试。非常好!按照 https://msdn.microsoft.com/en-us/data/jj680699 的指南,我构建了自定义的 DbConfiguration,并正在使用它,但相关的 DbExecutionStrategy 似乎被忽略了。最初,整个 DbConfiguration 都被忽略了,但我发现这是因为我在 app.config 中引用它并在实体构造函数中使用 DbConfigurationType 属性 [DbConfigurationType(typeof(MyConfiguration))] 进行装饰。现在我只使用 app.config,至少我的自定义配置得到了调用。最简单的情况下,我的自定义配置如下:
在这段代码中,我根本没有找到任何问题。
值得注意的一点是,到目前为止我看到的每个例子(例如http://blog.analystcircle.com/2015/08/01/connection-resiliency-in-entity-framework-6-0-and-above/)都直接将ShouldRetryOn中的异常转换为SqlException。
这个方法能正常工作,但是我需要这样做,而其他例子则不需要,这可能是出错的线索。EntityExceptions没有触发DbExecutionStrategy吗?如果不是,为什么在EF 6中将其列为解决方案?任何见解都将不胜感激。
编辑:深入挖掘DbExecutionStrategy源代码(https://github.com/aspnet/EntityFramework6/blob/master/src/EntityFramework/Infrastructure/DbExecutionStrategy.cs),我发现我的递归函数从EntityException中找到我的SqlException是不必要的。DbExecutionStrategy有一个UnwrapAndHandleException函数,它可以做到这一点,并将SqlException传递给ShouldRetryOn。因此,似乎我又回到了起点。
编辑2:不是真正的解决方案,因为它没有解释为什么我的DbExecutionStrategy没有按照应该的方式被调用,但我发现如果我明确调用执行策略,它就可以工作。
显式使用执行策略的代码如下:
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)));
}
}
我的自定义DbConfiguration在app.config中被引用,如下所示:
<entityFramework codeConfigurationType="MyDataLayer.MyConfiguration, MyDataLayer">
...
</entityFramework>
我的自定义DbExecutionStrategy的构建方式如下:
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;
}
}
在这段代码中,我根本没有找到任何问题。
值得注意的一点是,到目前为止我看到的每个例子(例如http://blog.analystcircle.com/2015/08/01/connection-resiliency-in-entity-framework-6-0-and-above/)都直接将ShouldRetryOn中的异常转换为SqlException。
SqlException sqlException = ex as SqlException;
我发现使用这种方法总是导致空的SqlException,因为我的程序会抛出一个无法转换为SqlException的EntityException。我的潜在SqlException实际上是EntityException的内部异常的内部异常。因此,我编写了一个简短的递归调用来查找它。
private SqlException GetSqlException(Exception ex)
{
SqlException result = ex as SqlException;
if (result == null && ex.InnerException != null)
result = GetSqlException(ex.InnerException);
return result;
}
这个方法能正常工作,但是我需要这样做,而其他例子则不需要,这可能是出错的线索。EntityExceptions没有触发DbExecutionStrategy吗?如果不是,为什么在EF 6中将其列为解决方案?任何见解都将不胜感激。
编辑:深入挖掘DbExecutionStrategy源代码(https://github.com/aspnet/EntityFramework6/blob/master/src/EntityFramework/Infrastructure/DbExecutionStrategy.cs),我发现我的递归函数从EntityException中找到我的SqlException是不必要的。DbExecutionStrategy有一个UnwrapAndHandleException函数,它可以做到这一点,并将SqlException传递给ShouldRetryOn。因此,似乎我又回到了起点。
编辑2:不是真正的解决方案,因为它没有解释为什么我的DbExecutionStrategy没有按照应该的方式被调用,但我发现如果我明确调用执行策略,它就可以工作。
显式使用执行策略的代码如下:
var executionStrategy = new MyConfiguration.MyExecutionStrategy();
executionStrategy.Execute(
() =>
{
//build your context and execute db functions here
using (var context = new Entities())
{
...do stuff
}
});