You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Connection Resiliency is implemented in EF Core by setting the execution strategy used by EF Core queries. Usually .EnableRetryOnFailure() is called at startup to enable a provider specialized retrying execution strategy, which is made available in the DBContext dependencies.
Note that both of these classes derive from the abstract ExecutionStrategy class, which in turn implements the IExecutionStrategy interface.
The issue
ExecutionStrategy.Current is a static property of the abstract type ExecutionStrategy backed by an AsyncLocal that is set to the current execution strategy when ExecutionStrategy.Execute() or ExecutionStrategy.ExecuteAsync() is called, such that code within the delegate passed to the execute methods can observe the ExecutionStrategy.Current value and use it appropriately.
NonRetryingExecutionStrategy only implements IExecutionStrategy. It does not derive from ExecutionStrategy. Therefore it does not, and cannot, set ExecutionStrategy.Current to itself when NonRetryingExecutionStrategy.Execute() is called, so ExecutionStrategy.Current will be null when observed within the executed delegate:
varnonRetryingExecutionStrategy=new NonRetryingExecutionStrategy(dbContext);await nonRetryingExecutionStrategy.ExecuteAsync(async()=>{// ExecutionStrategy.Current is null here!// Any query you run here will use the default execution strategy Debug.Assert(ExecutionStrategy.Current !=null);// Boom! Debug.Assert(ExecutionStrategy.Current ==nonRetryingExecutionStrategy);}
Several features rely on ExecutionStrategy.Current, notably QueryCompilationContext.IsBuffering, which is set based on ExecutionStrategy.Current.RetriesOnFailure. If ExecutionStrategy.Current is null, it will fall back to the value given by QueryCompilationContextDependencies, which usually contains the "globally" configured execution strategy set during application startup:
If the user has configured a retrying execution strategy using .EnableRetryOnFailure() (eg. MySqlRetryingExecutionStrategy), but wishes to disable this execution strategy for a single query to avoid query response buffering, they will likely attempt to do so by running the query within NonRetryingExecutionStrategy.Execute().
ExecutionStrategy.Current will be set to null, and the query will instead run as if it is executed within the default retrying query strategy, which causes the query response to buffer.
Workaround
It is currently possible to work around this issue either by creating a custom execution strategy that derives from ExecutionStrategy, or by abusing the existing execution strategies. For example, MySqlRetryingExecutionStrategy can be constructed with maxRetryCount = 0, which causes it to return ExecutionStrategy.RetriesOnFailure as false.
varmySqlNonRetryingExecutionStrategy=new MySqlRetryingExecutionStrategy(dbContext,0);await mySqlNonRetryingExecutionStrategy.ExecuteAsync(async()=>{// ExecutionStrategy.Current is successfully set to our mySqlNonRetryingExecutionStrategy instance.// Any code that uses ExecutionStrategy.Current?.RetriesOnFailure will read it as false. Debug.Assert(ExecutionStrategy.Current !=null);// OK! Debug.Assert(ExecutionStrategy.Current ==mySqlNonRetryingExecutionStrategy);// OK!}
Include provider and version information
EF Core version: release/8.0
Target framework: .NET 8.0
The text was updated successfully, but these errors were encountered:
Background
Connection Resiliency is implemented in EF Core by setting the execution strategy used by EF Core queries. Usually
.EnableRetryOnFailure()
is called at startup to enable a provider specialized retrying execution strategy, which is made available in the DBContext dependencies.For example, on the Pomelo.EntityFrameworkCore.MySql provider this is the MySqlRetryingExecutionStrategy. On Npgsql this is the NpgsqlRetryingExecutionStrategy.
Note that both of these classes derive from the abstract ExecutionStrategy class, which in turn implements the IExecutionStrategy interface.
The issue
ExecutionStrategy.Current
is a static property of the abstract typeExecutionStrategy
backed by anAsyncLocal
that is set to the current execution strategy whenExecutionStrategy.Execute()
orExecutionStrategy.ExecuteAsync()
is called, such that code within the delegate passed to the execute methods can observe theExecutionStrategy.Current
value and use it appropriately.Unfortunately, this property can only contain the abstract ExecutionStrategy class, not an instance of the IExecutionStrategy interface.
NonRetryingExecutionStrategy only implements IExecutionStrategy. It does not derive from ExecutionStrategy. Therefore it does not, and cannot, set
ExecutionStrategy.Current
to itself whenNonRetryingExecutionStrategy.Execute()
is called, soExecutionStrategy.Current
will benull
when observed within the executed delegate:Several features rely on
ExecutionStrategy.Current
, notablyQueryCompilationContext.IsBuffering
, which is set based onExecutionStrategy.Current.RetriesOnFailure
. IfExecutionStrategy.Current
is null, it will fall back to the value given byQueryCompilationContextDependencies
, which usually contains the "globally" configured execution strategy set during application startup:efcore/src/EFCore/Query/QueryCompilationContext.cs
Line 120 in cb9bb05
efcore/src/EFCore.Relational/Query/RelationalCompiledQueryCacheKeyGenerator.cs
Line 44 in cb9bb05
The issue conclusion
If the user has configured a retrying execution strategy using
.EnableRetryOnFailure()
(eg.MySqlRetryingExecutionStrategy
), but wishes to disable this execution strategy for a single query to avoid query response buffering, they will likely attempt to do so by running the query withinNonRetryingExecutionStrategy.Execute()
.ExecutionStrategy.Current
will be set tonull
, and the query will instead run as if it is executed within the default retrying query strategy, which causes the query response to buffer.Workaround
It is currently possible to work around this issue either by creating a custom execution strategy that derives from
ExecutionStrategy
, or by abusing the existing execution strategies. For example,MySqlRetryingExecutionStrategy
can be constructed withmaxRetryCount = 0
, which causes it to return ExecutionStrategy.RetriesOnFailure asfalse
.Include provider and version information
EF Core version: release/8.0
Target framework: .NET 8.0
The text was updated successfully, but these errors were encountered: