Skip to content

Commit fd96b94

Browse files
committed
Dispose service properties when context is disposed or returned to the pool
Fixes #25486
1 parent b7e11eb commit fd96b94

File tree

14 files changed

+217
-33
lines changed

14 files changed

+217
-33
lines changed

src/EFCore/ChangeTracking/ChangeTracker.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -609,7 +609,7 @@ Task IResettableService.ResetStateAsync(CancellationToken cancellationToken)
609609
/// </para>
610610
/// </remarks>
611611
public virtual void Clear()
612-
=> StateManager.Clear();
612+
=> StateManager.Clear(resetting: false);
613613

614614
/// <summary>
615615
/// <para>

src/EFCore/ChangeTracking/Internal/IStateManager.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -457,7 +457,7 @@ IEnumerable<IUpdateEntry> GetDependentsUsingRelationshipSnapshot(
457457
/// any release. You should only use it directly in your code with extreme caution and knowing that
458458
/// doing so can result in application failures when updating to a new Entity Framework Core release.
459459
/// </summary>
460-
void Unsubscribe();
460+
void Unsubscribe(bool resetting);
461461

462462
/// <summary>
463463
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -584,5 +584,5 @@ void SetEvents(
584584
/// any release. You should only use it directly in your code with extreme caution and knowing that
585585
/// doing so can result in application failures when updating to a new Entity Framework Core release.
586586
/// </summary>
587-
void Clear();
587+
void Clear(bool resetting);
588588
}

src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -470,26 +470,29 @@ private void FireStateChanged(EntityState oldState)
470470

471471
private void SetServiceProperties(EntityState oldState, EntityState newState)
472472
{
473-
if (oldState == EntityState.Detached)
473+
if (EntityType.HasServiceProperties())
474474
{
475-
foreach (var serviceProperty in EntityType.GetServiceProperties())
475+
if (oldState == EntityState.Detached)
476476
{
477-
this[serviceProperty] = (this[serviceProperty] is IInjectableService injectableService
478-
? injectableService.Attaching(Context, Entity, injectableService)
479-
: null)
480-
?? serviceProperty
481-
.ParameterBinding
482-
.ServiceDelegate(new MaterializationContext(ValueBuffer.Empty, Context), EntityType, Entity);
477+
foreach (var serviceProperty in EntityType.GetServiceProperties())
478+
{
479+
this[serviceProperty] = (this[serviceProperty] is IInjectableService injectableService
480+
? injectableService.Attaching(Context, Entity, injectableService)
481+
: null)
482+
?? serviceProperty
483+
.ParameterBinding
484+
.ServiceDelegate(new MaterializationContext(ValueBuffer.Empty, Context), EntityType, Entity);
485+
}
483486
}
484-
}
485-
else if (newState == EntityState.Detached)
486-
{
487-
foreach (var serviceProperty in EntityType.GetServiceProperties())
487+
else if (newState == EntityState.Detached)
488488
{
489-
if (!(this[serviceProperty] is IInjectableService detachable)
490-
|| detachable.Detaching(Context, Entity))
489+
foreach (var serviceProperty in EntityType.GetServiceProperties())
491490
{
492-
this[serviceProperty] = null;
491+
if (!(this[serviceProperty] is IInjectableService detachable)
492+
|| detachable.Detaching(Context, Entity))
493+
{
494+
this[serviceProperty] = null;
495+
}
493496
}
494497
}
495498
}
@@ -2030,6 +2033,11 @@ public bool IsLoaded(INavigationBase navigation)
20302033

20312034
private ILazyLoader? GetLazyLoader()
20322035
{
2036+
if (!EntityType.HasServiceProperties())
2037+
{
2038+
return null;
2039+
}
2040+
20332041
var lazyLoaderProperty = EntityType.GetServiceProperties().FirstOrDefault(p => p.ClrType == typeof(ILazyLoader));
20342042
return lazyLoaderProperty != null ? (ILazyLoader?)this[lazyLoaderProperty] : null;
20352043
}

src/EFCore/ChangeTracking/Internal/StateManager.cs

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ public class StateManager : IStateManager
2222
private IIdentityMap? _identityMap1;
2323
private Dictionary<IKey, IIdentityMap>? _identityMaps;
2424
private bool _needsUnsubscribe;
25+
private bool _hasServiceProperties;
2526
private IChangeDetector? _changeDetector;
2627

2728
private readonly IDiagnosticsLogger<DbLoggerCategory.ChangeTracking> _changeTrackingLogger;
@@ -361,6 +362,11 @@ public virtual InternalEntityEntry StartTrackingFromQuery(
361362
_needsUnsubscribe = true;
362363
}
363364

365+
if (newEntry.EntityType.HasServiceProperties())
366+
{
367+
_hasServiceProperties = true;
368+
}
369+
364370
return newEntry;
365371
}
366372

@@ -600,6 +606,11 @@ public virtual InternalEntityEntry StartTracking(InternalEntityEntry entry)
600606
_needsUnsubscribe = true;
601607
}
602608

609+
if (entry.EntityType.HasServiceProperties())
610+
{
611+
_hasServiceProperties = true;
612+
}
613+
603614
return entry;
604615
}
605616

@@ -661,7 +672,7 @@ public virtual void StopTracking(InternalEntityEntry entry, EntityState oldState
661672
/// any release. You should only use it directly in your code with extreme caution and knowing that
662673
/// doing so can result in application failures when updating to a new Entity Framework Core release.
663674
/// </summary>
664-
public virtual void Unsubscribe()
675+
public virtual void Unsubscribe(bool resetting)
665676
{
666677
if (_needsUnsubscribe)
667678
{
@@ -670,6 +681,27 @@ public virtual void Unsubscribe()
670681
_internalEntityEntrySubscriber.Unsubscribe(entry);
671682
}
672683
}
684+
685+
if (_hasServiceProperties)
686+
{
687+
foreach (var entry in Entries)
688+
{
689+
foreach (var serviceProperty in entry.EntityType.GetServiceProperties())
690+
{
691+
var service = entry[serviceProperty];
692+
if (resetting
693+
&& service is IDisposable disposable)
694+
{
695+
disposable.Dispose();
696+
}
697+
else if (!(service is IInjectableService detachable)
698+
|| detachable.Detaching(Context, entry.Entity))
699+
{
700+
entry[serviceProperty] = null;
701+
}
702+
}
703+
}
704+
}
673705
}
674706

675707
/// <summary>
@@ -680,7 +712,7 @@ public virtual void Unsubscribe()
680712
/// </summary>
681713
public virtual void ResetState()
682714
{
683-
Clear();
715+
Clear(resetting: true);
684716
Dependencies.NavigationFixer.AbortDelayedFixup();
685717
_changeDetector?.ResetState();
686718

@@ -696,9 +728,9 @@ public virtual void ResetState()
696728
/// any release. You should only use it directly in your code with extreme caution and knowing that
697729
/// doing so can result in application failures when updating to a new Entity Framework Core release.
698730
/// </summary>
699-
public virtual void Clear()
731+
public virtual void Clear(bool resetting)
700732
{
701-
Unsubscribe();
733+
Unsubscribe(resetting);
702734
ChangedCount = 0;
703735
_entityReferenceMap.Clear();
704736
_referencedUntrackedEntities = null;
@@ -708,6 +740,7 @@ public virtual void Clear()
708740
_identityMap1?.Clear();
709741

710742
_needsUnsubscribe = false;
743+
_hasServiceProperties = false;
711744

712745
SavingChanges = false;
713746

src/EFCore/DbContext.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1092,7 +1092,7 @@ private bool DisposeSync(bool leaseActive, bool contextShouldBeDisposed)
10921092

10931093
_disposed = true;
10941094

1095-
_dbContextDependencies?.StateManager.Unsubscribe();
1095+
_dbContextDependencies?.StateManager.Unsubscribe(resetting: true);
10961096

10971097
_dbContextDependencies = null;
10981098
_changeTracker = null;

src/EFCore/Internal/EntityFinder.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -447,7 +447,7 @@ public virtual void Load(INavigation navigation, InternalEntityEntry entry, bool
447447
{
448448
if (stateManager != _stateManager)
449449
{
450-
stateManager.ResetState();
450+
stateManager.Clear(resetting: false);
451451
}
452452
}
453453
}
@@ -509,7 +509,7 @@ public virtual async Task LoadAsync(
509509
{
510510
if (stateManager != _stateManager)
511511
{
512-
await stateManager.ResetStateAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
512+
stateManager.Clear(resetting: false);
513513
}
514514
}
515515
}

src/EFCore/Internal/ManyToManyLoader.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ public virtual void Load(InternalEntityEntry entry, bool forceIdentityResolution
6161
{
6262
if (stateManager != entry.StateManager)
6363
{
64-
stateManager.ResetState();
64+
stateManager.Clear(resetting: false);
6565
}
6666
}
6767
}
@@ -106,7 +106,7 @@ public virtual async Task LoadAsync(
106106
{
107107
if (stateManager != entry.StateManager)
108108
{
109-
await stateManager.ResetStateAsync(cancellationToken).ConfigureAwait(false);
109+
stateManager.Clear(resetting: false);
110110
}
111111
}
112112
}

src/EFCore/Metadata/Conventions/ServicePropertyDiscoveryConvention.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,8 @@ void DiscoverServiceProperties(IEnumerable<MemberInfo> members)
8181
}
8282

8383
var memberType = memberInfo.GetMemberType();
84-
if (entityType.GetServiceProperties().Any(p => p.ClrType == memberType))
84+
if (entityType.HasServiceProperties()
85+
&& entityType.GetServiceProperties().Any(p => p.ClrType == memberType))
8586
{
8687
continue;
8788
}

src/EFCore/Metadata/IReadOnlyEntityType.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -741,6 +741,12 @@ IReadOnlyProperty GetProperty(string name)
741741
/// <returns>Derived service properties.</returns>
742742
IEnumerable<IReadOnlyServiceProperty> GetDerivedServiceProperties();
743743

744+
/// <summary>
745+
/// Checks whether or not this entity type has any <see cref="IServiceProperty" /> defined.
746+
/// </summary>
747+
/// <returns><see langword="true"/> if there are any service properties defined on this entity type or base types.</returns>
748+
bool HasServiceProperties();
749+
744750
/// <summary>
745751
/// Gets all the <see cref="IReadOnlyServiceProperty" /> defined on this entity type.
746752
/// </summary>

src/EFCore/Metadata/Internal/EntityType.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2939,6 +2939,15 @@ public virtual ServiceProperty RemoveServiceProperty(ServiceProperty property)
29392939
return property;
29402940
}
29412941

2942+
/// <summary>
2943+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
2944+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
2945+
/// any release. You should only use it directly in your code with extreme caution and knowing that
2946+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
2947+
/// </summary>
2948+
public virtual bool HasServiceProperties()
2949+
=> _serviceProperties.Count != 0 || _baseType != null && _baseType.HasServiceProperties();
2950+
29422951
/// <summary>
29432952
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
29442953
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
@@ -5252,6 +5261,16 @@ IEnumerable<IServiceProperty> IEntityType.GetDeclaredServiceProperties()
52525261
IEnumerable<IReadOnlyServiceProperty> IReadOnlyEntityType.GetDerivedServiceProperties()
52535262
=> GetDerivedServiceProperties();
52545263

5264+
/// <summary>
5265+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
5266+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
5267+
/// any release. You should only use it directly in your code with extreme caution and knowing that
5268+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
5269+
/// </summary>
5270+
[DebuggerStepThrough]
5271+
bool IReadOnlyEntityType.HasServiceProperties()
5272+
=> HasServiceProperties();
5273+
52555274
/// <summary>
52565275
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
52575276
/// the same compatibility standards as public APIs. It may be changed or removed without notice in

0 commit comments

Comments
 (0)