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)