Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 (!_hasServiceProperties && newEntry.EntityType.HasServiceProperties())
{
_hasServiceProperties = true;
}

return newEntry;
}

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

if (!_hasServiceProperties && 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
16 changes: 13 additions & 3 deletions src/EFCore/Metadata/RuntimeEntityType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ private readonly SortedDictionary<string, RuntimeTrigger> _triggers
private readonly PropertyInfo? _indexerPropertyInfo;
private readonly bool _isPropertyBag;
private readonly object? _discriminatorValue;
private bool _hasServiceProperties;

// Warning: Never access these fields directly as access needs to be thread-safe
private PropertyCounts? _counts;
Expand Down Expand Up @@ -742,6 +743,7 @@ public virtual RuntimeServiceProperty AddServiceProperty(
propertyAccessMode);

_serviceProperties[serviceProperty.Name] = serviceProperty;
_hasServiceProperties = true;

return serviceProperty;
}
Expand All @@ -763,11 +765,14 @@ public virtual RuntimeServiceProperty AddServiceProperty(
? property
: null;

private bool HasServiceProperties()
=> _hasServiceProperties || _baseType != null && _baseType.HasServiceProperties();

private IEnumerable<RuntimeServiceProperty> GetServiceProperties()
=> _baseType != null
? _serviceProperties.Count == 0
? _baseType.GetServiceProperties()
: _baseType.GetServiceProperties().Concat(_serviceProperties.Values)
? _hasServiceProperties
? _baseType.GetServiceProperties().Concat(_serviceProperties.Values)
: _baseType.GetServiceProperties()
: _serviceProperties.Values;

private IEnumerable<RuntimeServiceProperty> GetDeclaredServiceProperties()
Expand Down Expand Up @@ -1395,6 +1400,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