Skip to content

Commit

Permalink
Dispose service properties when context is disposed or returned to th…
Browse files Browse the repository at this point in the history
…e pool

Fixes #25486
  • Loading branch information
ajcvickers committed Dec 24, 2022
1 parent b7e11eb commit fd96b94
Show file tree
Hide file tree
Showing 14 changed files with 217 additions and 33 deletions.
2 changes: 1 addition & 1 deletion src/EFCore/ChangeTracking/ChangeTracker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -609,7 +609,7 @@ Task IResettableService.ResetStateAsync(CancellationToken cancellationToken)
/// </para>
/// </remarks>
public virtual void Clear()
=> StateManager.Clear();
=> StateManager.Clear(resetting: false);

/// <summary>
/// <para>
Expand Down
4 changes: 2 additions & 2 deletions src/EFCore/ChangeTracking/Internal/IStateManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,7 @@ IEnumerable<IUpdateEntry> GetDependentsUsingRelationshipSnapshot(
/// 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.
/// </summary>
void Unsubscribe();
void Unsubscribe(bool resetting);

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand Down Expand Up @@ -584,5 +584,5 @@ void SetEvents(
/// 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.
/// </summary>
void Clear();
void Clear(bool resetting);
}
38 changes: 23 additions & 15 deletions src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -470,26 +470,29 @@ private void FireStateChanged(EntityState oldState)

private void SetServiceProperties(EntityState oldState, EntityState newState)
{
if (oldState == EntityState.Detached)
if (EntityType.HasServiceProperties())
{
foreach (var serviceProperty in EntityType.GetServiceProperties())
if (oldState == EntityState.Detached)
{
this[serviceProperty] = (this[serviceProperty] is IInjectableService injectableService
? injectableService.Attaching(Context, Entity, injectableService)
: null)
?? serviceProperty
.ParameterBinding
.ServiceDelegate(new MaterializationContext(ValueBuffer.Empty, Context), EntityType, Entity);
foreach (var serviceProperty in EntityType.GetServiceProperties())
{
this[serviceProperty] = (this[serviceProperty] is IInjectableService injectableService
? injectableService.Attaching(Context, Entity, injectableService)
: null)
?? serviceProperty
.ParameterBinding
.ServiceDelegate(new MaterializationContext(ValueBuffer.Empty, Context), EntityType, Entity);
}
}
}
else if (newState == EntityState.Detached)
{
foreach (var serviceProperty in EntityType.GetServiceProperties())
else if (newState == EntityState.Detached)
{
if (!(this[serviceProperty] is IInjectableService detachable)
|| detachable.Detaching(Context, Entity))
foreach (var serviceProperty in EntityType.GetServiceProperties())
{
this[serviceProperty] = null;
if (!(this[serviceProperty] is IInjectableService detachable)
|| detachable.Detaching(Context, Entity))
{
this[serviceProperty] = null;
}
}
}
}
Expand Down Expand Up @@ -2030,6 +2033,11 @@ public bool IsLoaded(INavigationBase navigation)

private ILazyLoader? GetLazyLoader()
{
if (!EntityType.HasServiceProperties())
{
return null;
}

var lazyLoaderProperty = EntityType.GetServiceProperties().FirstOrDefault(p => p.ClrType == typeof(ILazyLoader));
return lazyLoaderProperty != null ? (ILazyLoader?)this[lazyLoaderProperty] : null;
}
Expand Down
41 changes: 37 additions & 4 deletions src/EFCore/ChangeTracking/Internal/StateManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public class StateManager : IStateManager
private IIdentityMap? _identityMap1;
private Dictionary<IKey, IIdentityMap>? _identityMaps;
private bool _needsUnsubscribe;
private bool _hasServiceProperties;
private IChangeDetector? _changeDetector;

private readonly IDiagnosticsLogger<DbLoggerCategory.ChangeTracking> _changeTrackingLogger;
Expand Down Expand Up @@ -361,6 +362,11 @@ public virtual InternalEntityEntry StartTrackingFromQuery(
_needsUnsubscribe = true;
}

if (newEntry.EntityType.HasServiceProperties())
{
_hasServiceProperties = true;
}

return newEntry;
}

Expand Down Expand Up @@ -600,6 +606,11 @@ public virtual InternalEntityEntry StartTracking(InternalEntityEntry entry)
_needsUnsubscribe = true;
}

if (entry.EntityType.HasServiceProperties())
{
_hasServiceProperties = true;
}

return entry;
}

Expand Down Expand Up @@ -661,7 +672,7 @@ public virtual void StopTracking(InternalEntityEntry entry, EntityState oldState
/// 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.
/// </summary>
public virtual void Unsubscribe()
public virtual void Unsubscribe(bool resetting)
{
if (_needsUnsubscribe)
{
Expand All @@ -670,6 +681,27 @@ public virtual void Unsubscribe()
_internalEntityEntrySubscriber.Unsubscribe(entry);
}
}

if (_hasServiceProperties)
{
foreach (var entry in Entries)
{
foreach (var serviceProperty in entry.EntityType.GetServiceProperties())
{
var service = entry[serviceProperty];
if (resetting
&& service is IDisposable disposable)
{
disposable.Dispose();
}
else if (!(service is IInjectableService detachable)
|| detachable.Detaching(Context, entry.Entity))
{
entry[serviceProperty] = null;
}
}
}
}
}

/// <summary>
Expand All @@ -680,7 +712,7 @@ public virtual void Unsubscribe()
/// </summary>
public virtual void ResetState()
{
Clear();
Clear(resetting: true);
Dependencies.NavigationFixer.AbortDelayedFixup();
_changeDetector?.ResetState();

Expand All @@ -696,9 +728,9 @@ public virtual void 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.
/// </summary>
public virtual void Clear()
public virtual void Clear(bool resetting)
{
Unsubscribe();
Unsubscribe(resetting);
ChangedCount = 0;
_entityReferenceMap.Clear();
_referencedUntrackedEntities = null;
Expand All @@ -708,6 +740,7 @@ public virtual void Clear()
_identityMap1?.Clear();

_needsUnsubscribe = false;
_hasServiceProperties = false;

SavingChanges = false;

Expand Down
2 changes: 1 addition & 1 deletion src/EFCore/DbContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1092,7 +1092,7 @@ private bool DisposeSync(bool leaseActive, bool contextShouldBeDisposed)

_disposed = true;

_dbContextDependencies?.StateManager.Unsubscribe();
_dbContextDependencies?.StateManager.Unsubscribe(resetting: true);

_dbContextDependencies = null;
_changeTracker = null;
Expand Down
4 changes: 2 additions & 2 deletions src/EFCore/Internal/EntityFinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -447,7 +447,7 @@ public virtual void Load(INavigation navigation, InternalEntityEntry entry, bool
{
if (stateManager != _stateManager)
{
stateManager.ResetState();
stateManager.Clear(resetting: false);
}
}
}
Expand Down Expand Up @@ -509,7 +509,7 @@ public virtual async Task LoadAsync(
{
if (stateManager != _stateManager)
{
await stateManager.ResetStateAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
stateManager.Clear(resetting: false);
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/EFCore/Internal/ManyToManyLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public virtual void Load(InternalEntityEntry entry, bool forceIdentityResolution
{
if (stateManager != entry.StateManager)
{
stateManager.ResetState();
stateManager.Clear(resetting: false);
}
}
}
Expand Down Expand Up @@ -106,7 +106,7 @@ public virtual async Task LoadAsync(
{
if (stateManager != entry.StateManager)
{
await stateManager.ResetStateAsync(cancellationToken).ConfigureAwait(false);
stateManager.Clear(resetting: false);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ void DiscoverServiceProperties(IEnumerable<MemberInfo> members)
}

var memberType = memberInfo.GetMemberType();
if (entityType.GetServiceProperties().Any(p => p.ClrType == memberType))
if (entityType.HasServiceProperties()
&& entityType.GetServiceProperties().Any(p => p.ClrType == memberType))
{
continue;
}
Expand Down
6 changes: 6 additions & 0 deletions src/EFCore/Metadata/IReadOnlyEntityType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -741,6 +741,12 @@ IReadOnlyProperty GetProperty(string name)
/// <returns>Derived service properties.</returns>
IEnumerable<IReadOnlyServiceProperty> GetDerivedServiceProperties();

/// <summary>
/// Checks whether or not this entity type has any <see cref="IServiceProperty" /> defined.
/// </summary>
/// <returns><see langword="true"/> if there are any service properties defined on this entity type or base types.</returns>
bool HasServiceProperties();

/// <summary>
/// Gets all the <see cref="IReadOnlyServiceProperty" /> defined on this entity type.
/// </summary>
Expand Down
19 changes: 19 additions & 0 deletions src/EFCore/Metadata/Internal/EntityType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2939,6 +2939,15 @@ public virtual ServiceProperty RemoveServiceProperty(ServiceProperty property)
return property;
}

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// 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.
/// </summary>
public virtual bool HasServiceProperties()
=> _serviceProperties.Count != 0 || _baseType != null && _baseType.HasServiceProperties();

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
Expand Down Expand Up @@ -5252,6 +5261,16 @@ IEnumerable<IServiceProperty> IEntityType.GetDeclaredServiceProperties()
IEnumerable<IReadOnlyServiceProperty> IReadOnlyEntityType.GetDerivedServiceProperties()
=> GetDerivedServiceProperties();

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// 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.
/// </summary>
[DebuggerStepThrough]
bool IReadOnlyEntityType.HasServiceProperties()
=> HasServiceProperties();

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
Expand Down
8 changes: 8 additions & 0 deletions src/EFCore/Metadata/RuntimeEntityType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -763,6 +763,9 @@ public virtual RuntimeServiceProperty AddServiceProperty(
? property
: null;

private bool HasServiceProperties()
=> _serviceProperties.Count != 0 || _baseType != null && _baseType.HasServiceProperties();

private IEnumerable<RuntimeServiceProperty> GetServiceProperties()
=> _baseType != null
? _serviceProperties.Count == 0
Expand Down Expand Up @@ -1395,6 +1398,11 @@ IEnumerable<IServiceProperty> IEntityType.GetDeclaredServiceProperties()
IEnumerable<IReadOnlyServiceProperty> IReadOnlyEntityType.GetDerivedServiceProperties()
=> GetDerivedServiceProperties();

/// <inheritdoc />
[DebuggerStepThrough]
bool IReadOnlyEntityType.HasServiceProperties()
=> HasServiceProperties();

/// <inheritdoc />
[DebuggerStepThrough]
IEnumerable<IReadOnlyServiceProperty> IReadOnlyEntityType.GetServiceProperties()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,9 @@ public IEnumerable<IReadOnlyProperty> GetDerivedProperties()
public IEnumerable<IReadOnlyServiceProperty> GetDerivedServiceProperties()
=> throw new NotImplementedException();

public bool HasServiceProperties()
=> throw new NotImplementedException();

public IEnumerable<IReadOnlySkipNavigation> GetDerivedSkipNavigations()
=> throw new NotImplementedException();

Expand Down
Loading

0 comments on commit fd96b94

Please sign in to comment.