diff --git a/src/EFCore/DbContext.cs b/src/EFCore/DbContext.cs index 168ca6c6246..32e806dbfd3 100644 --- a/src/EFCore/DbContext.cs +++ b/src/EFCore/DbContext.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using System.Linq; using System.Linq.Expressions; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore.ChangeTracking; @@ -788,7 +789,7 @@ void IDbContextPoolable.SnapshotConfiguration() [EntityFrameworkInternal] void IResettableService.ResetState() { - foreach (var service in _cachedResettableServices ??= GetResettableServices()) + foreach (var service in GetResettableServices()) { service.ResetState(); } @@ -807,7 +808,7 @@ void IResettableService.ResetState() [EntityFrameworkInternal] async Task IResettableService.ResetStateAsync(CancellationToken cancellationToken) { - foreach (var service in _cachedResettableServices ??= GetResettableServices()) + foreach (var service in GetResettableServices()) { await service.ResetStateAsync(cancellationToken).ConfigureAwait(false); } @@ -817,22 +818,29 @@ async Task IResettableService.ResetStateAsync(CancellationToken cancellationToke _disposed = true; } - private List GetResettableServices() + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private IEnumerable GetResettableServices() { - var resettableServices = new List(); + if (_cachedResettableServices is not null) + { + return _cachedResettableServices; + } - var services - = _contextServices?.InternalServiceProvider? - .GetService>(); + var resettableServices = new List(); - if (services != null) + var services = _contextServices?.InternalServiceProvider.GetService>(); + if (services is not null) { resettableServices.AddRange(services); + + // Note that if the context hasn't been initialized yet, we don't cache the resettable services + // (since some services haven't been added yet). + _cachedResettableServices = resettableServices; } - if (_sets != null) + if (_sets is not null) { - resettableServices.AddRange((_sets.Values.OfType())); + resettableServices.AddRange(_sets.Values.OfType()); } return resettableServices; diff --git a/test/EFCore.SqlServer.FunctionalTests/DbContextPoolingTest.cs b/test/EFCore.SqlServer.FunctionalTests/DbContextPoolingTest.cs index 5ca1e52a016..a2bcd7f568f 100644 --- a/test/EFCore.SqlServer.FunctionalTests/DbContextPoolingTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/DbContextPoolingTest.cs @@ -159,10 +159,17 @@ private interface ISecondContext private class SecondContext : DbContext, ISecondContext { + public DbSet Blogs { get; set; } + public SecondContext(DbContextOptions options) : base(options) { } + + public class Blog + { + public int Id { get; set; } + } } [ConditionalFact] @@ -717,6 +724,36 @@ public async Task Context_configuration_is_reset(bool useInterface, bool async) Assert.Null(context1.Database.GetCommandTimeout()); } + [ConditionalTheory] + [InlineData(false)] + [InlineData(true)] + public async Task Uninitialized_context_configuration_is_reset_properly(bool async) + { + var serviceProvider = BuildServiceProvider(); + + DbContext ctx; + using (var scope = serviceProvider.CreateScope()) + using (var ctx1 = scope.ServiceProvider.GetRequiredService()) + { + await ctx1.DisposeAsync(); + ctx = ctx1; + } + + using (var scope = serviceProvider.CreateScope()) + using (var ctx2 = scope.ServiceProvider.GetRequiredService()) + { + Assert.Same(ctx, ctx2); + ctx2.Blogs.Add(new SecondContext.Blog()); + } + + using (var scope = serviceProvider.CreateScope()) + using (var ctx3 = scope.ServiceProvider.GetRequiredService()) + { + Assert.Same(ctx, ctx3); + Assert.Empty(ctx3.ChangeTracker.Entries()); + } + } + [ConditionalTheory] [InlineData(false, false)] [InlineData(true, false)]