diff --git a/All.sln.DotSettings b/All.sln.DotSettings index 10b8b9be4a6..d1773f1c53d 100644 --- a/All.sln.DotSettings +++ b/All.sln.DotSettings @@ -192,6 +192,7 @@ Licensed under the Apache License, Version 2.0. See License.txt in the project r True 2 DO_NOTHING + True True True True diff --git a/src/EFCore/DbContext.cs b/src/EFCore/DbContext.cs index d63a38ec922..b3237df1021 100644 --- a/src/EFCore/DbContext.cs +++ b/src/EFCore/DbContext.cs @@ -756,9 +756,7 @@ void IResettableService.ResetState() service.ResetState(); } - SavingChanges = null; - SavedChanges = null; - SaveChangesFailed = null; + ClearEvents(); _disposed = true; } @@ -769,7 +767,6 @@ void IResettableService.ResetState() /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - /// A to observe while waiting for the task to complete. [EntityFrameworkInternal] async Task IResettableService.ResetStateAsync(CancellationToken cancellationToken) { @@ -778,6 +775,8 @@ async Task IResettableService.ResetStateAsync(CancellationToken cancellationToke await service.ResetStateAsync(cancellationToken).ConfigureAwait(false); } + ClearEvents(); + _disposed = true; } @@ -825,6 +824,9 @@ private bool DisposeSync() if (_lease.ContextDisposed()) { _disposed = true; + + ClearEvents(); + _lease = DbContextLease.InactiveLease; } } @@ -842,6 +844,8 @@ private bool DisposeSync() _changeTracker = null; _database = null; + ClearEvents(); + return true; } @@ -854,6 +858,14 @@ private bool DisposeSync() public virtual ValueTask DisposeAsync() => DisposeSync() ? _serviceScope.DisposeAsyncIfAvailable() : default; + + private void ClearEvents() + { + SavingChanges = null; + SavedChanges = null; + SaveChangesFailed = null; + } + /// /// Gets an for the given entity. The entry provides /// access to change tracking information and operations for the entity. diff --git a/test/EFCore.SqlServer.FunctionalTests/DbContextPoolingTest.cs b/test/EFCore.SqlServer.FunctionalTests/DbContextPoolingTest.cs index 146b391a72d..ebd29551904 100644 --- a/test/EFCore.SqlServer.FunctionalTests/DbContextPoolingTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/DbContextPoolingTest.cs @@ -4,6 +4,7 @@ using System; using System.Diagnostics; using System.Linq; +using System.Reflection; using System.Threading; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore.ChangeTracking; @@ -496,9 +497,20 @@ public async Task Context_configuration_is_reset(bool useInterface, bool async) context1.ChangeTracker.CascadeDeleteTiming = CascadeTiming.Immediate; context1.ChangeTracker.DeleteOrphansTiming = CascadeTiming.Immediate; context1.Database.AutoTransactionsEnabled = true; + context1.SavingChanges += (sender, args) => { }; + context1.SavedChanges += (sender, args) => { }; + context1.SaveChangesFailed += (sender, args) => { }; + + Assert.NotNull(GetContextEventField(context1, nameof(DbContext.SavingChanges))); + Assert.NotNull(GetContextEventField(context1, nameof(DbContext.SavedChanges))); + Assert.NotNull(GetContextEventField(context1, nameof(DbContext.SaveChangesFailed))); await Dispose(serviceScope, async); + Assert.Null(GetContextEventField(context1, nameof(DbContext.SavingChanges))); + Assert.Null(GetContextEventField(context1, nameof(DbContext.SavedChanges))); + Assert.Null(GetContextEventField(context1, nameof(DbContext.SaveChangesFailed))); + serviceScope = serviceProvider.CreateScope(); scopedProvider = serviceScope.ServiceProvider; @@ -531,9 +543,20 @@ public async Task Context_configuration_is_reset_with_factory(bool async) context1.ChangeTracker.CascadeDeleteTiming = CascadeTiming.Immediate; context1.ChangeTracker.DeleteOrphansTiming = CascadeTiming.Immediate; context1.Database.AutoTransactionsEnabled = true; + context1.SavingChanges += (sender, args) => { }; + context1.SavedChanges += (sender, args) => { }; + context1.SaveChangesFailed += (sender, args) => { }; + + Assert.NotNull(GetContextEventField(context1, nameof(DbContext.SavingChanges))); + Assert.NotNull(GetContextEventField(context1, nameof(DbContext.SavedChanges))); + Assert.NotNull(GetContextEventField(context1, nameof(DbContext.SaveChangesFailed))); await Dispose(context1, async); + Assert.Null(GetContextEventField(context1, nameof(DbContext.SavingChanges))); + Assert.Null(GetContextEventField(context1, nameof(DbContext.SavedChanges))); + Assert.Null(GetContextEventField(context1, nameof(DbContext.SaveChangesFailed))); + var context2 = factory.CreateDbContext(); Assert.Same(context1, context2); @@ -553,6 +576,10 @@ public void Change_tracker_can_be_cleared_without_resetting_context_config() new DbContextOptionsBuilder().UseSqlServer( SqlServerNorthwindTestStoreFactory.NorthwindConnectionString).Options); + Assert.Null(GetContextEventField(context, nameof(DbContext.SavingChanges))); + Assert.Null(GetContextEventField(context, nameof(DbContext.SavedChanges))); + Assert.Null(GetContextEventField(context, nameof(DbContext.SaveChangesFailed))); + context.ChangeTracker.AutoDetectChangesEnabled = true; context.ChangeTracker.LazyLoadingEnabled = true; context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; @@ -561,6 +588,9 @@ public void Change_tracker_can_be_cleared_without_resetting_context_config() context.Database.AutoTransactionsEnabled = true; context.ChangeTracker.Tracked += ChangeTracker_OnTracked; context.ChangeTracker.StateChanged += ChangeTracker_OnStateChanged; + context.SavingChanges += (sender, args) => { }; + context.SavedChanges += (sender, args) => { }; + context.SaveChangesFailed += (sender, args) => { }; context.ChangeTracker.Clear(); @@ -579,8 +609,16 @@ public void Change_tracker_can_be_cleared_without_resetting_context_config() Assert.True(_changeTracker_OnTracked); Assert.True(_changeTracker_OnStateChanged); + + Assert.NotNull(GetContextEventField(context, nameof(DbContext.SavingChanges))); + Assert.NotNull(GetContextEventField(context, nameof(DbContext.SavedChanges))); + Assert.NotNull(GetContextEventField(context, nameof(DbContext.SaveChangesFailed))); } + private object GetContextEventField(DbContext context, string eventName) + => typeof(DbContext) + .GetField(eventName, BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance) + .GetValue(context); private bool _changeTracker_OnTracked; private void ChangeTracker_OnTracked(object sender, EntityTrackedEventArgs e)