From 41b147c380df27cf8e3dd8e9eeb08b4212dbadff Mon Sep 17 00:00:00 2001 From: Arthur Vickers Date: Sat, 11 May 2019 16:37:11 -0700 Subject: [PATCH] Remove internal notification indirection Fixes #10897 This was added years ago to support multiple dispatch, but we don't use it and it's internal, so removing. --- .../Internal/IChangeDetector.cs | 19 ++- .../Internal/IEntityStateListener.cs | 47 ------ .../ChangeTracking/Internal/IKeyListener.cs | 42 ----- .../Internal/ILocalViewListener.cs | 21 ++- .../Internal/INavigationFixer.cs | 71 ++++++++- .../Internal/INavigationListener.cs | 52 ------- .../Internal/IPropertyListener.cs | 43 ------ .../Internal/IQueryTrackingListener.cs | 38 ----- .../Internal/InternalEntityEntryNotifier.cs | 143 +++--------------- .../EntityFrameworkServicesBuilder.cs | 11 -- .../Internal/ChangeDetectorTest.cs | 22 ++- .../InternalEntryEntrySubscriberTest.cs | 70 ++++++--- .../Internal/StateManagerTest.cs | 61 ++------ .../EntityFrameworkServicesBuilderTest.cs | 51 +++---- .../InternalSeviceCollectionMapTest.cs | 17 --- 15 files changed, 234 insertions(+), 474 deletions(-) delete mode 100644 src/EFCore/ChangeTracking/Internal/IEntityStateListener.cs delete mode 100644 src/EFCore/ChangeTracking/Internal/IKeyListener.cs delete mode 100644 src/EFCore/ChangeTracking/Internal/INavigationListener.cs delete mode 100644 src/EFCore/ChangeTracking/Internal/IPropertyListener.cs delete mode 100644 src/EFCore/ChangeTracking/Internal/IQueryTrackingListener.cs diff --git a/src/EFCore/ChangeTracking/Internal/IChangeDetector.cs b/src/EFCore/ChangeTracking/Internal/IChangeDetector.cs index d35990905ba..2246602fbd7 100644 --- a/src/EFCore/ChangeTracking/Internal/IChangeDetector.cs +++ b/src/EFCore/ChangeTracking/Internal/IChangeDetector.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.Extensions.DependencyInjection; namespace Microsoft.EntityFrameworkCore.ChangeTracking.Internal @@ -20,8 +21,24 @@ namespace Microsoft.EntityFrameworkCore.ChangeTracking.Internal /// The implementation does not need to be thread-safe. /// /// - public interface IChangeDetector : IPropertyListener + public interface IChangeDetector { + /// + /// 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. + /// + void PropertyChanged([NotNull] InternalEntityEntry entry, [NotNull] IPropertyBase propertyBase, bool setModified); + + /// + /// 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. + /// + void PropertyChanging([NotNull] InternalEntityEntry entry, [NotNull] IPropertyBase propertyBase); + /// /// 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 diff --git a/src/EFCore/ChangeTracking/Internal/IEntityStateListener.cs b/src/EFCore/ChangeTracking/Internal/IEntityStateListener.cs deleted file mode 100644 index 427d86147a8..00000000000 --- a/src/EFCore/ChangeTracking/Internal/IEntityStateListener.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using JetBrains.Annotations; -using Microsoft.Extensions.DependencyInjection; - -namespace Microsoft.EntityFrameworkCore.ChangeTracking.Internal -{ - // TODO: Consider which of listeners/events/interceptors/etc is better here - // See issue #737 - /// - /// - /// 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. - /// - /// - /// The service lifetime is and multiple registrations - /// are allowed. This means that each instance will use its own - /// set of instances of this service. - /// The implementations may depend on other services registered with any lifetime. - /// The implementations do not need to be thread-safe. - /// - /// - public interface IEntityStateListener - { - /// - /// 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. - /// - void StateChanging([NotNull] InternalEntityEntry entry, EntityState newState); - - /// - /// 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. - /// - void StateChanged( - [NotNull] InternalEntityEntry entry, - EntityState oldState, - bool fromQuery); - } -} diff --git a/src/EFCore/ChangeTracking/Internal/IKeyListener.cs b/src/EFCore/ChangeTracking/Internal/IKeyListener.cs deleted file mode 100644 index b275dacaddb..00000000000 --- a/src/EFCore/ChangeTracking/Internal/IKeyListener.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Collections.Generic; -using JetBrains.Annotations; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.Extensions.DependencyInjection; - -namespace Microsoft.EntityFrameworkCore.ChangeTracking.Internal -{ - /// - /// - /// 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. - /// - /// - /// The service lifetime is and multiple registrations - /// are allowed. This means that each instance will use its own - /// set of instances of this service. - /// The implementations may depend on other services registered with any lifetime. - /// The implementations do not need to be thread-safe. - /// - /// - public interface IKeyListener - { - /// - /// 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. - /// - void KeyPropertyChanged( - [NotNull] InternalEntityEntry entry, - [NotNull] IProperty property, - [NotNull] IReadOnlyList containingPrincipalKeys, - [NotNull] IReadOnlyList containingForeignKeys, - [CanBeNull] object oldValue, - [CanBeNull] object newValue); - } -} diff --git a/src/EFCore/ChangeTracking/Internal/ILocalViewListener.cs b/src/EFCore/ChangeTracking/Internal/ILocalViewListener.cs index 3a699bd76dc..8a11d3abfa6 100644 --- a/src/EFCore/ChangeTracking/Internal/ILocalViewListener.cs +++ b/src/EFCore/ChangeTracking/Internal/ILocalViewListener.cs @@ -21,7 +21,7 @@ namespace Microsoft.EntityFrameworkCore.ChangeTracking.Internal /// The implementation does not need to be thread-safe. /// /// - public interface ILocalViewListener : IEntityStateListener + public interface ILocalViewListener { /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -30,5 +30,24 @@ public interface ILocalViewListener : IEntityStateListener /// doing so can result in application failures when updating to a new Entity Framework Core release. /// void RegisterView([NotNull] Action viewAction); + + /// + /// 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. + /// + void StateChanging([NotNull] InternalEntityEntry entry, EntityState newState); + + /// + /// 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. + /// + void StateChanged( + [NotNull] InternalEntityEntry entry, + EntityState oldState, + bool fromQuery); } } diff --git a/src/EFCore/ChangeTracking/Internal/INavigationFixer.cs b/src/EFCore/ChangeTracking/Internal/INavigationFixer.cs index 77e6aa13897..4343261a430 100644 --- a/src/EFCore/ChangeTracking/Internal/INavigationFixer.cs +++ b/src/EFCore/ChangeTracking/Internal/INavigationFixer.cs @@ -1,6 +1,9 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Collections.Generic; +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.Extensions.DependencyInjection; namespace Microsoft.EntityFrameworkCore.ChangeTracking.Internal @@ -19,7 +22,73 @@ namespace Microsoft.EntityFrameworkCore.ChangeTracking.Internal /// The implementation does not need to be thread-safe. /// /// - public interface INavigationFixer : IEntityStateListener, INavigationListener, IKeyListener, IQueryTrackingListener + public interface INavigationFixer { + /// + /// 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. + /// + void NavigationReferenceChanged( + [NotNull] InternalEntityEntry entry, + [NotNull] INavigation navigation, + [CanBeNull] object oldValue, + [CanBeNull] object newValue); + + /// + /// 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. + /// + void NavigationCollectionChanged( + [NotNull] InternalEntityEntry entry, + [NotNull] INavigation navigation, + [NotNull] IEnumerable added, + [NotNull] IEnumerable removed); + + /// + /// 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. + /// + void TrackedFromQuery( + [NotNull] InternalEntityEntry entry, + [CanBeNull] ISet handledForeignKeys); + + /// + /// 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. + /// + void KeyPropertyChanged( + [NotNull] InternalEntityEntry entry, + [NotNull] IProperty property, + [NotNull] IReadOnlyList containingPrincipalKeys, + [NotNull] IReadOnlyList containingForeignKeys, + [CanBeNull] object oldValue, + [CanBeNull] object newValue); + + /// + /// 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. + /// + void StateChanging([NotNull] InternalEntityEntry entry, EntityState newState); + + /// + /// 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. + /// + void StateChanged( + [NotNull] InternalEntityEntry entry, + EntityState oldState, + bool fromQuery); } } diff --git a/src/EFCore/ChangeTracking/Internal/INavigationListener.cs b/src/EFCore/ChangeTracking/Internal/INavigationListener.cs deleted file mode 100644 index 94bfc78ccd7..00000000000 --- a/src/EFCore/ChangeTracking/Internal/INavigationListener.cs +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Collections.Generic; -using JetBrains.Annotations; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.Extensions.DependencyInjection; - -namespace Microsoft.EntityFrameworkCore.ChangeTracking.Internal -{ - /// - /// - /// 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. - /// - /// - /// The service lifetime is and multiple registrations - /// are allowed. This means that each instance will use its own - /// set of instances of this service. - /// The implementations may depend on other services registered with any lifetime. - /// The implementations do not need to be thread-safe. - /// - /// - public interface INavigationListener - { - /// - /// 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. - /// - void NavigationReferenceChanged( - [NotNull] InternalEntityEntry entry, - [NotNull] INavigation navigation, - [CanBeNull] object oldValue, - [CanBeNull] object newValue); - - /// - /// 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. - /// - void NavigationCollectionChanged( - [NotNull] InternalEntityEntry entry, - [NotNull] INavigation navigation, - [NotNull] IEnumerable added, - [NotNull] IEnumerable removed); - } -} diff --git a/src/EFCore/ChangeTracking/Internal/IPropertyListener.cs b/src/EFCore/ChangeTracking/Internal/IPropertyListener.cs deleted file mode 100644 index 8a4db28bb81..00000000000 --- a/src/EFCore/ChangeTracking/Internal/IPropertyListener.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using JetBrains.Annotations; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.Extensions.DependencyInjection; - -namespace Microsoft.EntityFrameworkCore.ChangeTracking.Internal -{ - /// - /// - /// 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. - /// - /// - /// The service lifetime is and multiple registrations - /// are allowed. This means that each instance will use its own - /// set of instances of this service. - /// The implementations may depend on other services registered with any lifetime. - /// The implementations do not need to be thread-safe. - /// - /// - public interface IPropertyListener - { - /// - /// 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. - /// - void PropertyChanged([NotNull] InternalEntityEntry entry, [NotNull] IPropertyBase propertyBase, bool setModified); - - /// - /// 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. - /// - void PropertyChanging([NotNull] InternalEntityEntry entry, [NotNull] IPropertyBase propertyBase); - } -} diff --git a/src/EFCore/ChangeTracking/Internal/IQueryTrackingListener.cs b/src/EFCore/ChangeTracking/Internal/IQueryTrackingListener.cs deleted file mode 100644 index 85d6c45050d..00000000000 --- a/src/EFCore/ChangeTracking/Internal/IQueryTrackingListener.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Collections.Generic; -using JetBrains.Annotations; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.Extensions.DependencyInjection; - -namespace Microsoft.EntityFrameworkCore.ChangeTracking.Internal -{ - /// - /// - /// 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. - /// - /// - /// The service lifetime is and multiple registrations - /// are allowed. This means that each instance will use its own - /// set of instances of this service. - /// The implementations may depend on other services registered with any lifetime. - /// The implementations do not need to be thread-safe. - /// - /// - public interface IQueryTrackingListener - { - /// - /// 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. - /// - void TrackedFromQuery( - [NotNull] InternalEntityEntry entry, - [CanBeNull] ISet handledForeignKeys); - } -} diff --git a/src/EFCore/ChangeTracking/Internal/InternalEntityEntryNotifier.cs b/src/EFCore/ChangeTracking/Internal/InternalEntityEntryNotifier.cs index 4fc9622f245..b1daacd50fe 100644 --- a/src/EFCore/ChangeTracking/Internal/InternalEntityEntryNotifier.cs +++ b/src/EFCore/ChangeTracking/Internal/InternalEntityEntryNotifier.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; -using System.Linq; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.Extensions.DependencyInjection; @@ -25,11 +24,9 @@ namespace Microsoft.EntityFrameworkCore.ChangeTracking.Internal /// public class InternalEntityEntryNotifier : IInternalEntityEntryNotifier { - private readonly IQueryTrackingListener[] _queryTrackingListeners; - private readonly IEntityStateListener[] _entityStateListeners; - private readonly IPropertyListener[] _propertyListeners; - private readonly INavigationListener[] _navigationListeners; - private readonly IKeyListener[] _keyListeners; + private readonly ILocalViewListener _localViewListener; + private readonly IChangeDetector _changeDetector; + private readonly INavigationFixer _navigationFixer; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -38,41 +35,13 @@ public class InternalEntityEntryNotifier : IInternalEntityEntryNotifier /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public InternalEntityEntryNotifier( - [CanBeNull] IEnumerable entityStateListeners, - [CanBeNull] IEnumerable propertyListeners, - [CanBeNull] IEnumerable navigationListeners, - [CanBeNull] IEnumerable keyListeners, - [CanBeNull] IEnumerable queryTrackingListeners) + [CanBeNull] ILocalViewListener localViewListener, + [CanBeNull] IChangeDetector changeDetector, + [CanBeNull] INavigationFixer navigationFixer) { - if (entityStateListeners != null) - { - var listeners = entityStateListeners.ToArray(); - _entityStateListeners = listeners.Length == 0 ? null : listeners; - } - - if (propertyListeners != null) - { - var listeners = propertyListeners.ToArray(); - _propertyListeners = listeners.Length == 0 ? null : listeners; - } - - if (navigationListeners != null) - { - var listeners = navigationListeners.ToArray(); - _navigationListeners = listeners.Length == 0 ? null : listeners; - } - - if (keyListeners != null) - { - var listeners = keyListeners.ToArray(); - _keyListeners = listeners.Length == 0 ? null : listeners; - } - - if (queryTrackingListeners != null) - { - var listeners = queryTrackingListeners.ToArray(); - _queryTrackingListeners = listeners.Length == 0 ? null : listeners; - } + _localViewListener = localViewListener; + _changeDetector = changeDetector; + _navigationFixer = navigationFixer; } /// @@ -83,15 +52,8 @@ public InternalEntityEntryNotifier( /// public virtual void StateChanging(InternalEntityEntry entry, EntityState newState) { - if (_entityStateListeners == null) - { - return; - } - - foreach (var listener in _entityStateListeners) - { - listener.StateChanging(entry, newState); - } + _navigationFixer.StateChanging(entry, newState); + _localViewListener.StateChanging(entry, newState); } /// @@ -102,15 +64,8 @@ public virtual void StateChanging(InternalEntityEntry entry, EntityState newStat /// public virtual void StateChanged(InternalEntityEntry entry, EntityState oldState, bool fromQuery) { - if (_entityStateListeners == null) - { - return; - } - - foreach (var listener in _entityStateListeners) - { - listener.StateChanged(entry, oldState, fromQuery); - } + _navigationFixer.StateChanged(entry, oldState, fromQuery); + _localViewListener.StateChanged(entry, oldState, fromQuery); } /// @@ -122,17 +77,7 @@ public virtual void StateChanged(InternalEntityEntry entry, EntityState oldState public virtual void TrackedFromQuery( InternalEntityEntry entry, ISet handledForeignKeys) - { - if (_entityStateListeners == null) - { - return; - } - - foreach (var listener in _queryTrackingListeners) - { - listener.TrackedFromQuery(entry, handledForeignKeys); - } - } + => _navigationFixer.TrackedFromQuery(entry, handledForeignKeys); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -145,17 +90,7 @@ public virtual void NavigationReferenceChanged( INavigation navigation, object oldValue, object newValue) - { - if (_navigationListeners == null) - { - return; - } - - foreach (var listener in _navigationListeners) - { - listener.NavigationReferenceChanged(entry, navigation, oldValue, newValue); - } - } + => _navigationFixer.NavigationReferenceChanged(entry, navigation, oldValue, newValue); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -168,17 +103,7 @@ public virtual void NavigationCollectionChanged( INavigation navigation, IEnumerable added, IEnumerable removed) - { - if (_navigationListeners == null) - { - return; - } - - foreach (var listener in _navigationListeners) - { - listener.NavigationCollectionChanged(entry, navigation, added, removed); - } - } + => _navigationFixer.NavigationCollectionChanged(entry, navigation, added, removed); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -193,17 +118,7 @@ public virtual void KeyPropertyChanged( IReadOnlyList foreignKeys, object oldValue, object newValue) - { - if (_keyListeners == null) - { - return; - } - - foreach (var listener in _keyListeners) - { - listener.KeyPropertyChanged(entry, property, keys, foreignKeys, oldValue, newValue); - } - } + => _navigationFixer.KeyPropertyChanged(entry, property, keys, foreignKeys, oldValue, newValue); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -212,17 +127,7 @@ public virtual void KeyPropertyChanged( /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual void PropertyChanged(InternalEntityEntry entry, IPropertyBase property, bool setModified) - { - if (_propertyListeners == null) - { - return; - } - - foreach (var listener in _propertyListeners) - { - listener.PropertyChanged(entry, property, setModified); - } - } + => _changeDetector.PropertyChanged(entry, property, setModified); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -231,16 +136,6 @@ public virtual void PropertyChanged(InternalEntityEntry entry, IPropertyBase pro /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual void PropertyChanging(InternalEntityEntry entry, IPropertyBase property) - { - if (_propertyListeners == null) - { - return; - } - - foreach (var listener in _propertyListeners) - { - listener.PropertyChanging(entry, property); - } - } + => _changeDetector.PropertyChanging(entry, property); } } diff --git a/src/EFCore/Infrastructure/EntityFrameworkServicesBuilder.cs b/src/EFCore/Infrastructure/EntityFrameworkServicesBuilder.cs index f444b76e348..59a1d271c7d 100644 --- a/src/EFCore/Infrastructure/EntityFrameworkServicesBuilder.cs +++ b/src/EFCore/Infrastructure/EntityFrameworkServicesBuilder.cs @@ -149,11 +149,6 @@ public static readonly IDictionary CoreServices { typeof(ITypeMappingSourcePlugin), new ServiceCharacteristics(ServiceLifetime.Singleton, multipleRegistrations: true) }, { typeof(ISingletonOptions), new ServiceCharacteristics(ServiceLifetime.Singleton, multipleRegistrations: true) }, { typeof(IConventionSetCustomizer), new ServiceCharacteristics(ServiceLifetime.Scoped, multipleRegistrations: true) }, - { typeof(IEntityStateListener), new ServiceCharacteristics(ServiceLifetime.Scoped, multipleRegistrations: true) }, - { typeof(INavigationListener), new ServiceCharacteristics(ServiceLifetime.Scoped, multipleRegistrations: true) }, - { typeof(IKeyListener), new ServiceCharacteristics(ServiceLifetime.Scoped, multipleRegistrations: true) }, - { typeof(IQueryTrackingListener), new ServiceCharacteristics(ServiceLifetime.Scoped, multipleRegistrations: true) }, - { typeof(IPropertyListener), new ServiceCharacteristics(ServiceLifetime.Scoped, multipleRegistrations: true) }, { typeof(IResettableService), new ServiceCharacteristics(ServiceLifetime.Scoped, multipleRegistrations: true) }, // New Query related services @@ -284,12 +279,6 @@ public virtual EntityFrameworkServicesBuilder TryAddCoreServices() TryAdd(p => GetContextServices(p).Model); TryAdd(p => GetContextServices(p).CurrentContext); TryAdd(p => GetContextServices(p).ContextOptions); - TryAdd(p => p.GetService()); - TryAdd(p => p.GetService()); - TryAdd(p => p.GetService()); - TryAdd(p => p.GetService()); - TryAdd(p => p.GetService()); - TryAdd(p => p.GetService()); TryAdd(p => p.GetService()); TryAdd(p => p.GetService()); TryAdd(); diff --git a/test/EFCore.Tests/ChangeTracking/Internal/ChangeDetectorTest.cs b/test/EFCore.Tests/ChangeTracking/Internal/ChangeDetectorTest.cs index 769958f695b..052fbf66223 100644 --- a/test/EFCore.Tests/ChangeTracking/Internal/ChangeDetectorTest.cs +++ b/test/EFCore.Tests/ChangeTracking/Internal/ChangeDetectorTest.cs @@ -2369,8 +2369,7 @@ private static IServiceProvider CreateContextServices(IModel model = null) new ServiceCollection() .AddScoped() .AddScoped() - .AddScoped(p => p.GetRequiredService()) - .AddScoped(p => p.GetRequiredService()), + .AddScoped(p => p.GetRequiredService()), model ?? BuildModel()); } @@ -2395,8 +2394,13 @@ public override void AttachGraph( } } - private class TestRelationshipListener : INavigationListener, IKeyListener + private class TestRelationshipListener : NavigationFixer { + public TestRelationshipListener(IChangeDetector changeDetector, IEntityGraphAttacher attacher) + : base(changeDetector, attacher) + { + } + public Tuple, IReadOnlyList, object, object> KeyChange { get; @@ -2406,18 +2410,22 @@ public Tuple, IReadOnlyList< public Tuple ReferenceChange { get; set; } public Tuple, IEnumerable> CollectionChange { get; set; } - public void NavigationReferenceChanged(InternalEntityEntry entry, INavigation navigation, object oldValue, object newValue) + public override void NavigationReferenceChanged(InternalEntityEntry entry, INavigation navigation, object oldValue, object newValue) { ReferenceChange = Tuple.Create(entry, navigation, oldValue, newValue); + + base.NavigationReferenceChanged(entry, navigation, oldValue, newValue); } - public void NavigationCollectionChanged( + public override void NavigationCollectionChanged( InternalEntityEntry entry, INavigation navigation, IEnumerable added, IEnumerable removed) { CollectionChange = Tuple.Create(entry, navigation, added, removed); + + base.NavigationCollectionChanged(entry, navigation, added, removed); } - public void KeyPropertyChanged( + public override void KeyPropertyChanged( InternalEntityEntry entry, IProperty property, IReadOnlyList containingPrincipalKeys, @@ -2426,6 +2434,8 @@ public void KeyPropertyChanged( object newValue) { KeyChange = Tuple.Create(entry, property, containingPrincipalKeys, containingForeignKeys, oldValue, newValue); + + base.KeyPropertyChanged(entry, property, containingPrincipalKeys, containingForeignKeys, oldValue, newValue); } } } diff --git a/test/EFCore.Tests/ChangeTracking/Internal/InternalEntryEntrySubscriberTest.cs b/test/EFCore.Tests/ChangeTracking/Internal/InternalEntryEntrySubscriberTest.cs index 68e35904b93..b380b12489b 100644 --- a/test/EFCore.Tests/ChangeTracking/Internal/InternalEntryEntrySubscriberTest.cs +++ b/test/EFCore.Tests/ChangeTracking/Internal/InternalEntryEntrySubscriberTest.cs @@ -227,11 +227,11 @@ private static TestNavigationListener SetupTestCollectionListener( ICollection collection) { var contextServices = InMemoryTestHelpers.Instance.CreateContextServices( - new ServiceCollection().AddScoped(), + new ServiceCollection().AddScoped(), BuildModel()); var testListener = contextServices - .GetRequiredService>() + .GetRequiredService>() .OfType() .Single(); @@ -249,10 +249,10 @@ private static TestNavigationListener SetupTestCollectionListener( public void Entry_subscribes_to_INotifyPropertyChanging_and_INotifyPropertyChanged_for_properties() { var contextServices = InMemoryTestHelpers.Instance.CreateContextServices( - new ServiceCollection().AddScoped(), + new ServiceCollection().AddScoped(), BuildModel()); - var testListener = contextServices.GetRequiredService>().OfType().Single(); + var testListener = contextServices.GetRequiredService>().OfType().Single(); var entity = new FullNotificationEntity(); var entry = contextServices.GetRequiredService().GetOrCreateEntry(entity); @@ -272,10 +272,10 @@ public void Entry_subscribes_to_INotifyPropertyChanging_and_INotifyPropertyChang public void Entry_handles_null_or_empty_string_in_INotifyPropertyChanging_and_INotifyPropertyChanged() { var contextServices = InMemoryTestHelpers.Instance.CreateContextServices( - new ServiceCollection().AddScoped(), + new ServiceCollection().AddScoped(), BuildModel()); - var testListener = contextServices.GetRequiredService>().OfType().Single(); + var testListener = contextServices.GetRequiredService>().OfType().Single(); var entity = new FullNotificationEntity(); var entry = contextServices.GetRequiredService().GetOrCreateEntry(entity); @@ -303,10 +303,10 @@ public void Entry_handles_null_or_empty_string_in_INotifyPropertyChanging_and_IN public void Entry_subscribes_to_INotifyPropertyChanging_and_INotifyPropertyChanged_for_navigations() { var contextServices = InMemoryTestHelpers.Instance.CreateContextServices( - new ServiceCollection().AddScoped(), + new ServiceCollection().AddScoped(), BuildModel()); - var testListener = contextServices.GetRequiredService>().OfType().Single(); + var testListener = contextServices.GetRequiredService>().OfType().Single(); var entity = new FullNotificationEntity(); var entry = contextServices.GetRequiredService().GetOrCreateEntry(entity); @@ -326,10 +326,10 @@ public void Entry_subscribes_to_INotifyPropertyChanging_and_INotifyPropertyChang public void Subscriptions_to_INotifyPropertyChanging_and_INotifyPropertyChanged_ignore_unmapped_properties() { var contextServices = InMemoryTestHelpers.Instance.CreateContextServices( - new ServiceCollection().AddScoped(), + new ServiceCollection().AddScoped(), BuildModel()); - var testListener = contextServices.GetRequiredService>().OfType().Single(); + var testListener = contextServices.GetRequiredService>().OfType().Single(); var entity = new FullNotificationEntity(); contextServices.GetRequiredService().GetOrCreateEntry(entity); @@ -347,11 +347,11 @@ public void Subscriptions_to_INotifyPropertyChanging_and_INotifyPropertyChanged_ public void Entry_unsubscribes_to_INotifyPropertyChanging_and_INotifyPropertyChanged() { var contextServices = InMemoryTestHelpers.Instance.CreateContextServices( - new ServiceCollection().AddScoped(), + new ServiceCollection().AddScoped(), BuildModel()); var testListener = contextServices - .GetRequiredService>() + .GetRequiredService>() .OfType().Single(); var entities = new List(); @@ -405,11 +405,11 @@ public void Entry_unsubscribes_to_INotifyPropertyChanging_and_INotifyPropertyCha public void Entry_unsubscribes_to_INotifyCollectionChanged() { var contextServices = InMemoryTestHelpers.Instance.CreateContextServices( - new ServiceCollection().AddScoped(), + new ServiceCollection().AddScoped(), BuildModel()); var testListener = contextServices - .GetRequiredService>() + .GetRequiredService>() .OfType() .Single(); @@ -456,11 +456,11 @@ public void Entry_unsubscribes_to_INotifyCollectionChanged() public void Entries_are_unsubscribed_when_context_is_disposed() { var context = InMemoryTestHelpers.Instance.CreateContext( - new ServiceCollection().AddScoped(), + new ServiceCollection().AddScoped(), BuildModel()); var testListener = context - .GetService>() + .GetService>() .OfType().Single(); var entities = new List(); @@ -496,7 +496,7 @@ public void Entries_are_unsubscribed_when_context_is_disposed() Assert.Equal(2, testListener.Changed.Count); } - private class TestPropertyListener : IPropertyListener + private class TestPropertyListener : IChangeDetector { public List> Changing { get; } = new List>(); @@ -508,9 +508,25 @@ public void PropertyChanged(InternalEntityEntry entry, IPropertyBase property, b public void PropertyChanging(InternalEntityEntry entry, IPropertyBase property) => Changing.Add(Tuple.Create(entry, property)); + + public void DetectChanges(IStateManager stateManager) + { + } + + public void DetectChanges(InternalEntityEntry entry) + { + } + + public void Suspend() + { + } + + public void Resume() + { + } } - private class TestNavigationListener : INavigationListener + private class TestNavigationListener : INavigationFixer { public List, IEnumerable>> CollectionChanged { get; } = new List, IEnumerable>>(); @@ -523,6 +539,24 @@ public void NavigationReferenceChanged( public void NavigationCollectionChanged( InternalEntityEntry entry, INavigation navigation, IEnumerable added, IEnumerable removed) => CollectionChanged.Add(Tuple.Create(entry, navigation, added, removed)); + + public void TrackedFromQuery(InternalEntityEntry entry, ISet handledForeignKeys) + { + } + + public void StateChanging(InternalEntityEntry entry, EntityState newState) + { + } + + public void StateChanged(InternalEntityEntry entry, EntityState oldState, bool fromQuery) + { + } + + public void KeyPropertyChanged( + InternalEntityEntry entry, IProperty property, IReadOnlyList containingPrincipalKeys, IReadOnlyList containingForeignKeys, + object oldValue, object newValue) + { + } } private static IModel BuildModel( diff --git a/test/EFCore.Tests/ChangeTracking/Internal/StateManagerTest.cs b/test/EFCore.Tests/ChangeTracking/Internal/StateManagerTest.cs index fc857da219d..1c98b3f5600 100644 --- a/test/EFCore.Tests/ChangeTracking/Internal/StateManagerTest.cs +++ b/test/EFCore.Tests/ChangeTracking/Internal/StateManagerTest.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Linq; using Microsoft.EntityFrameworkCore.Diagnostics; -using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.TestUtilities; @@ -824,60 +823,30 @@ public void Can_get_all_entities() .ToArray()); } - [Fact] - public void Listeners_are_notified_when_entity_states_change() + private class TestListener : INavigationFixer { - var listeners = new[] - { - new TestListener(), - new TestListener(), - new TestListener() - }; - - var services = new ServiceCollection() - .AddSingleton(listeners[0]) - .AddSingleton(listeners[1]) - .AddSingleton(listeners[2]); - - var contextServices = InMemoryTestHelpers.Instance.CreateContextServices(services, BuildModel()); - - var stateManager = contextServices.GetRequiredService(); - - var entry = stateManager.GetOrCreateEntry( - new Category - { - Id = 77, - PrincipalId = 777 - }); - entry.SetEntityState(EntityState.Added); + public int ChangingCount; + public int ChangedCount; + public EntityState ChangingState; + public EntityState ChangedState; - foreach (var listener in listeners) + public void NavigationReferenceChanged(InternalEntityEntry entry, INavigation navigation, object oldValue, object newValue) { - Assert.Equal(1, listener.ChangingCount); - Assert.Equal(1, listener.ChangedCount); - - Assert.Equal(EntityState.Added, listener.ChangingState); - Assert.Equal(EntityState.Detached, listener.ChangedState); } - entry.SetEntityState(EntityState.Modified); - - foreach (var listener in listeners) + public void NavigationCollectionChanged(InternalEntityEntry entry, INavigation navigation, IEnumerable added, IEnumerable removed) { - Assert.Equal(2, listener.ChangingCount); - Assert.Equal(2, listener.ChangedCount); + } - Assert.Equal(EntityState.Modified, listener.ChangingState); - Assert.Equal(EntityState.Added, listener.ChangedState); + public void TrackedFromQuery(InternalEntityEntry entry, ISet handledForeignKeys) + { } - } - private class TestListener : IEntityStateListener - { - public int ChangingCount; - public int ChangedCount; - public EntityState ChangingState; - public EntityState ChangedState; + public void KeyPropertyChanged( + InternalEntityEntry entry, IProperty property, IReadOnlyList containingPrincipalKeys, IReadOnlyList containingForeignKeys, + object oldValue, object newValue) + { + } public void StateChanging(InternalEntityEntry entry, EntityState newState) { diff --git a/test/EFCore.Tests/Infrastructure/EntityFrameworkServicesBuilderTest.cs b/test/EFCore.Tests/Infrastructure/EntityFrameworkServicesBuilderTest.cs index bd27f37c66f..eadf56530f2 100644 --- a/test/EFCore.Tests/Infrastructure/EntityFrameworkServicesBuilderTest.cs +++ b/test/EFCore.Tests/Infrastructure/EntityFrameworkServicesBuilderTest.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.InMemory.Storage.Internal; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.Extensions.DependencyInjection; using Xunit; @@ -189,19 +190,19 @@ private static void TestSingleton(Action tryAdd) [Fact] public void Can_register_multiple_scoped_service_with_concrete_implementation() { - TestMultipleScoped(b => b.TryAdd()); + TestMultipleScoped(b => b.TryAdd()); } [Fact] public void Can_register_multiple_scoped_service_with_concrete_implementation_non_generic() { - TestMultipleScoped(b => b.TryAdd(typeof(IEntityStateListener), typeof(FakeEntityStateListener))); + TestMultipleScoped(b => b.TryAdd(typeof(IResettableService), typeof(FakeResetableService))); } [Fact] public void Can_register_multiple_scoped_service_with_full_factory() { - TestMultipleScoped(b => b.TryAdd(p => new FakeEntityStateListener())); + TestMultipleScoped(b => b.TryAdd(p => new FakeResetableService())); } [Fact] @@ -210,9 +211,9 @@ public void Cannot_register_multiple_scoped_service_with_half_factory() var builder = new EntityFrameworkServicesBuilder(new ServiceCollection()); Assert.Equal( - CoreStrings.ImplementationTypeRequired(nameof(IEntityStateListener)), + CoreStrings.ImplementationTypeRequired(nameof(IResettableService)), Assert.Throws( - () => builder.TryAdd(p => new FakeEntityStateListener())) + () => builder.TryAdd(p => new FakeResetableService())) .Message); } @@ -220,7 +221,7 @@ public void Cannot_register_multiple_scoped_service_with_half_factory() public void Can_register_multiple_scoped_service_with_full_factory_non_generic() { TestMultipleScoped( - b => b.TryAdd(typeof(IEntityStateListener), typeof(FakeEntityStateListener), p => new FakeEntityStateListener())); + b => b.TryAdd(typeof(IResettableService), typeof(FakeResetableService), p => new FakeResetableService())); } [Fact] @@ -229,10 +230,10 @@ public void Cannot_register_multiple_scoped_service_with_half_factory_non_generi var builder = new EntityFrameworkServicesBuilder(new ServiceCollection()); Assert.Equal( - CoreStrings.ImplementationTypeRequired(nameof(IEntityStateListener)), + CoreStrings.ImplementationTypeRequired(nameof(IResettableService)), Assert.Throws( () => builder.TryAdd( - typeof(IEntityStateListener), typeof(IEntityStateListener), p => new FakeEntityStateListener())) + typeof(IResettableService), typeof(IResettableService), p => new FakeResetableService())) .Message); } @@ -242,9 +243,9 @@ public void Cannot_register_multiple_scoped_service_with_object_factory() var builder = new EntityFrameworkServicesBuilder(new ServiceCollection()); Assert.Equal( - CoreStrings.ImplementationTypeRequired(nameof(IEntityStateListener)), + CoreStrings.ImplementationTypeRequired(nameof(IResettableService)), Assert.Throws( - () => builder.TryAdd(typeof(IEntityStateListener), typeof(object), p => new FakeEntityStateListener())) + () => builder.TryAdd(typeof(IResettableService), typeof(object), p => new FakeResetableService())) .Message); } @@ -254,9 +255,9 @@ public void Cannot_register_multiple_scoped_with_instance() var builder = new EntityFrameworkServicesBuilder(new ServiceCollection()); Assert.Equal( - CoreStrings.SingletonRequired("Scoped", nameof(IEntityStateListener)), + CoreStrings.SingletonRequired("Scoped", nameof(IResettableService)), Assert.Throws( - () => builder.TryAdd(new FakeEntityStateListener())) + () => builder.TryAdd(new FakeResetableService())) .Message); } @@ -266,9 +267,9 @@ public void Cannot_register_multiple_scoped_with_instance_non_generic() var builder = new EntityFrameworkServicesBuilder(new ServiceCollection()); Assert.Equal( - CoreStrings.SingletonRequired("Scoped", nameof(IEntityStateListener)), + CoreStrings.SingletonRequired("Scoped", nameof(IResettableService)), Assert.Throws( - () => builder.TryAdd(typeof(IEntityStateListener), new FakeEntityStateListener())) + () => builder.TryAdd(typeof(IResettableService), new FakeResetableService())) .Message); } @@ -283,18 +284,18 @@ private static void TestMultipleScoped(Action tr var serviceProvider = serviceCollection.BuildServiceProvider(); - var services = new List(); + var services = new List(); using (var context = CreateContext(serviceProvider)) { - services = context.GetService>().ToList(); + services = context.GetService>().ToList(); Assert.Equal(3, services.Count); - Assert.Contains(typeof(FakeEntityStateListener), services.Select(s => s.GetType())); - Assert.Contains(typeof(NavigationFixer), services.Select(s => s.GetType())); - Assert.Contains(typeof(LocalViewListener), services.Select(s => s.GetType())); + Assert.Contains(typeof(FakeResetableService), services.Select(s => s.GetType())); + Assert.Contains(typeof(StateManager), services.Select(s => s.GetType())); + Assert.Contains(typeof(InMemoryTransactionManager), services.Select(s => s.GetType())); - foreach (var service in context.GetService>()) + foreach (var service in context.GetService>()) { Assert.Contains(service, services); } @@ -302,7 +303,7 @@ private static void TestMultipleScoped(Action tr using (var context = CreateContext(serviceProvider)) { - var newServices = context.GetService>().ToList(); + var newServices = context.GetService>().ToList(); Assert.Equal(3, newServices.Count); @@ -338,13 +339,9 @@ public DbSet CreateSet(DbContext context) public object CreateSet(DbContext context, Type type) => throw new NotImplementedException(); } - private class FakeEntityStateListener : IEntityStateListener + private class FakeResetableService : IResettableService { - public void StateChanging(InternalEntityEntry entry, EntityState newState) - { - } - - public void StateChanged(InternalEntityEntry entry, EntityState oldState, bool fromQuery) + public void ResetState() { } } diff --git a/test/EFCore.Tests/Infrastructure/InternalSeviceCollectionMapTest.cs b/test/EFCore.Tests/Infrastructure/InternalSeviceCollectionMapTest.cs index b63c51f61d9..62ec1726263 100644 --- a/test/EFCore.Tests/Infrastructure/InternalSeviceCollectionMapTest.cs +++ b/test/EFCore.Tests/Infrastructure/InternalSeviceCollectionMapTest.cs @@ -248,23 +248,6 @@ public void Can_patch_singleton_service_with_instance_registered_non_generic() Assert.IsType(Can_patch_singleton_service(serviceMap)); } - [Fact] - public virtual void Same_INavigationFixer_is_returned_for_all_registrations() - { - using (var context = new DbContext( - new DbContextOptionsBuilder() - .UseInternalServiceProvider(InMemoryFixture.DefaultServiceProvider) - .UseInMemoryDatabase(Guid.NewGuid().ToString()).Options)) - { - var navFixer = context.GetService(); - - Assert.Contains(navFixer, context.GetService>()); - Assert.Contains(navFixer, context.GetService>()); - Assert.Contains(navFixer, context.GetService>()); - Assert.Contains(navFixer, context.GetService>()); - } - } - private static FakeSingletonService Can_patch_singleton_service(ServiceCollectionMap serviceMap) { var serviceProvider = serviceMap.ServiceCollection.BuildServiceProvider();