Background
Applications connecting a database server have always been vulnerable to connection breaks due to back-end failures. However, in a LAN based environment these errors are rare enough that extra logic to handle those failures is not often required. With the rise of cloud based database servers and connections over less reliable networks it is now more common for connection breaks to occur, the same networks can also have much higher latency.Goals
- Provide the ability to retry actions on a variety of failures automatically
- A user should be able to implement their own retry strategies and decide which exceptions should cause a retry and which should not.
Non Goals
- Fully supporting existing EF applications without changes being made to those applications
- Resiliency is off by default, although we will try to help people know about it by proving information in transient failure exceptions
Design Meeting Notes
http://entityframework.codeplex.com/wikipage?title=Design%20Meeting%20Notes%20-%20November%208%2c%202012http://entityframework.codeplex.com/wikipage?title=Design%20Meeting%20Notes%20-%20December%206%2c%202012
http://entityframework.codeplex.com/wikipage?title=Design%20Meeting%20Notes%20-%20January%2024%2c%202013
Implementation
Connection retry is taken care of by an implementation of the IExecutionStrategy interface. Implementations of the IExecutionStrategy will be responsible for accepting an action and, if an exception occurs, determining if a retry is appropriate and retrying if it is. EF will use an IExecutionStrategy each time it executes an action against the database. The IExecutionStrategy interface looks like the following (Async methods omitted):publicinterface IExecutionStrategy { bool RetriesOnFailure { get; } void Execute(Action action); TResult Execute<TResult>(Func<TResult> func); }
IRetriableExceptionDetector
Implementations of the IRetriableExceptionDetector are responsible for determining whether or not a given exception is considered transient, and can be retried.publicinterface IRetriableExceptionDetector { bool ShouldRetryOn(Exception ex); }
IRetryDelayStrategy
Implementations of the IRetryDelayStrategy will be responsible for determining how long EF should wait before trying again.publicinterface IRetryDelayStrategy { TimeSpan? GetNextDelay(Exception lastException); }
Default Implementations
EF will ship with four execution strategies:- NonRetryingExecutionStrategy: this execution strategy does not retry any actions, it is the default for databases other than sql server.
- DefaultSqlExecutionStrategy: this execution strategy does not retry at all, however, it will wrap any exceptions that the SqlAzureExceptionDetector considers transient to inform users that they might want to enable connection resiliency.
- ExecutionStrategy: this class is suitable as a base class for other execution strategies, or used on its own. It implements retry logic based on a provided IRetriableExceptionDetector and IRetryDelayStrategy
- SqlAzureExecutionStrategy: this execution strategy inherits from ExecutionStrategy and will retry on exceptions that are known to be possibly transient when working with SqlAzure. It will use an exponential retry delay strategy, so that the delay gets longer as the number of retries grows.
Using an Execution Strategy
The main way to use execution strategies is to configure EF so that it will use the given strategy for all actions. In order to do this you need to add a resolver to the DependencyResolver chain that EF will use to find an IExecutionStrategy when it requires one.There is a default dependency resolver that will ask the provider being used for a default execution strategy, the SqlServer provider that ships with EF will return the DefaultSqlExecutioStrategy described in the section above. Other providers will return whichever strategy they think is appropriate for their database. If no execution strategy is found then EF will use the NonRetryingExecutionStrategy.
To use the SqlAzureExecution strategy then you need to add a class to the same assembly as your DbContext that inherits from SqlAzureDbConfiguration. The SqlAzureDbConfiguration class inherits from DbConfiguration and adds the SqlAzureExecutionStrategy to the resolver chain for you.
If you want to configure EF to use a different IExecutionStrategy, one that you have implemented yourself for example, then you need to create a class that inherits from DbConfiguration and add a dependency resolver to the chain. You will need to implement your own IDependencyResolver.
The following is the implementation of SqlAzureExecutionStrategy, which shows how to create the derived DbConfiguration and add a resolver to the chain:
publicclass SqlAzureDbConfiguration : DbConfiguration { public SqlAzureDbConfiguration() { AddExecutionStrategy(() => new SqlAzureExecutionStrategy(), null); } protectedvoid AddExecutionStrategy(Func<IExecutionStrategy> getExecutionStrategy, string serverName) { AddDependencyResolver(new SqlExecutionStrategyResolver(getExecutionStrategy, serverName)); } }
var executionStrategy = new SqlAzureExecutionStrategy(); var blogs = executionStrategy.Execute<IEnumerable<Blog>>(GetBlogs);
Using your own IRetryDelayStrategy or IRetriableExceptionDetector
If you wish to replace the implementations of the IRetryDelayStrategy or the IReliableExceptionDetector without writing your own ExecutionStrategy then you can do so by using the ExecutionStrategy class in EF.If you only need to execute a single action, then you can create an instance of the ExecutionStrategy class in EF and pass it the IRetryDelayStrategy and IRetriableExceptionDetector that you want to use.
If you want to register the execution strategy so that EF will use it for all actions, then you need to a create a class that implements IDependencyResolver interface. The GetService method of the resolver should return an instance of the ExecutionStrategy class with appropriate IRetryDelayStrategy and IReliableExceptionDetector implementations passed into the constructor. This class can be added to the dependency resolver in the same way that the SqlExecutionStrategyResolver is added in the example above.
Buffering
Buffering is required when using connection resiliency in order to keep the state manager consistent when there is a retry. However, there are also other benefits to buffering the main ones being:- The connection is potentially open and in-use for a shorter period of time
- Multiple Active Result Sets (MARS) would not be needed for nested queries, such as those that come with lazy loading.
Implementation
When in streaming mode EF uses a data reader that is wrapped in an IEnumerable that reads from the data reader as rows are required for materialization.With the buffering enabled the process is the same, except that the underlying provider DataReader will be read into memory, and close the connection, before it begins to yield rows from in-memory collection of data.