diff --git a/src/EFCore/ChangeTracking/ChangeTracker.cs b/src/EFCore/ChangeTracking/ChangeTracker.cs index bd63218bec4..36745548a52 100644 --- a/src/EFCore/ChangeTracking/ChangeTracker.cs +++ b/src/EFCore/ChangeTracking/ChangeTracker.cs @@ -13,7 +13,6 @@ namespace Microsoft.EntityFrameworkCore.ChangeTracking { - // This is the app-developer facing public API to the change tracker /// /// Provides access to change tracking information and operations for entity instances the context is tracking. /// Instances of this class are typically obtained from and it is not designed @@ -39,9 +38,8 @@ public ChangeTracker( Check.NotNull(stateManager, nameof(stateManager)); Check.NotNull(changeDetector, nameof(changeDetector)); -#pragma warning disable 612 Context = context; -#pragma warning restore 612 + _queryTrackingBehavior = context .GetService() .Extensions @@ -49,6 +47,7 @@ public ChangeTracker( .FirstOrDefault() ?.QueryTrackingBehavior ?? QueryTrackingBehavior.TrackAll; + StateManager = stateManager; ChangeDetector = changeDetector; _model = model; @@ -257,6 +256,32 @@ public virtual void TrackGraph( private IEntityEntryGraphIterator GraphIterator { get; } + /// + /// An event fired when an entity is tracked by the context, either because it was returned + /// from a tracking query, or because it was attached or added to the context. + /// + public event Action Tracked + { + add => StateManager.Tracked += value; + remove => StateManager.Tracked -= value; + } + + /// + /// + /// An event fired when an entity that is tracked by the associated has moved + /// from one to another. + /// + /// + /// Note that this event does not fire for entities when they are first tracked by the context. + /// Use the event to get notified when the context begins tracking an entity. + /// + /// + public event Action StateChanged + { + add => StateManager.StateChanged += value; + remove => StateManager.StateChanged -= value; + } + #region Hidden System.Object members /// diff --git a/src/EFCore/ChangeTracking/EntityEntryEventArgs.cs b/src/EFCore/ChangeTracking/EntityEntryEventArgs.cs new file mode 100644 index 00000000000..e825bfb62ee --- /dev/null +++ b/src/EFCore/ChangeTracking/EntityEntryEventArgs.cs @@ -0,0 +1,33 @@ +// 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; +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; + +namespace Microsoft.EntityFrameworkCore.ChangeTracking +{ + /// + /// Event arguments for events relating to tracked s. + /// + public class EntityEntryEventArgs : EventArgs + { + private readonly InternalEntityEntry _internalEntityEntry; + private EntityEntry _entry; + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public EntityEntryEventArgs( + [NotNull] InternalEntityEntry internalEntityEntry) + { + _internalEntityEntry = internalEntityEntry; + } + + /// + /// The for the entity. + /// + public virtual EntityEntry Entry => _entry ?? (_entry = new EntityEntry(_internalEntityEntry)); + } +} diff --git a/src/EFCore/ChangeTracking/EntityStateEventArgs.cs b/src/EFCore/ChangeTracking/EntityStateEventArgs.cs new file mode 100644 index 00000000000..2dada886fa0 --- /dev/null +++ b/src/EFCore/ChangeTracking/EntityStateEventArgs.cs @@ -0,0 +1,38 @@ +// 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.ChangeTracking.Internal; + +namespace Microsoft.EntityFrameworkCore.ChangeTracking +{ + /// + /// Event arguments for the event. + /// + public class EntityStateEventArgs : EntityEntryEventArgs + { + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public EntityStateEventArgs( + [NotNull] InternalEntityEntry internalEntityEntry, + EntityState oldState, + EntityState newState) + : base(internalEntityEntry) + { + OldState = oldState; + NewState = newState; + } + + /// + /// The state that the entity is transitioning from. + /// + public virtual EntityState OldState { get; } + + /// + /// The state that the entity is transitioning to. + /// + public virtual EntityState NewState { get; } + } +} diff --git a/src/EFCore/ChangeTracking/EntityTrackedEventArgs.cs b/src/EFCore/ChangeTracking/EntityTrackedEventArgs.cs new file mode 100644 index 00000000000..7d8928407e7 --- /dev/null +++ b/src/EFCore/ChangeTracking/EntityTrackedEventArgs.cs @@ -0,0 +1,31 @@ +// 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.ChangeTracking.Internal; + +namespace Microsoft.EntityFrameworkCore.ChangeTracking +{ + /// + /// Event arguments for the event. + /// + public class EntityTrackedEventArgs : EntityEntryEventArgs + { + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public EntityTrackedEventArgs( + [NotNull] InternalEntityEntry internalEntityEntry, + bool fromQuery) + : base(internalEntityEntry) + { + FromQuery = fromQuery; + } + + /// + /// True if the entity is being tracked as part of a database query; false otherwise. + /// + public virtual bool FromQuery { get; } + } +} diff --git a/src/EFCore/ChangeTracking/Internal/IStateManager.cs b/src/EFCore/ChangeTracking/Internal/IStateManager.cs index 5b9144ef795..adcf36930c6 100644 --- a/src/EFCore/ChangeTracking/Internal/IStateManager.cs +++ b/src/EFCore/ChangeTracking/Internal/IStateManager.cs @@ -235,5 +235,29 @@ IEnumerable GetDependentsUsingRelationshipSnapshot( /// directly from your code. This API may change or be removed in future releases. /// void Unsubscribe(); + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + event Action Tracked; + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + void OnTracked([NotNull] InternalEntityEntry internalEntityEntry, bool fromQuery); + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + event Action StateChanged; + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + void OnStateChanged([NotNull] InternalEntityEntry internalEntityEntry, EntityState oldState); } } diff --git a/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs b/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs index 199a0416684..5bd4273435e 100644 --- a/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs +++ b/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs @@ -199,6 +199,11 @@ private void SetEntityState(EntityState oldState, EntityState newState, bool acc _stateData.FlagAllProperties(EntityType.PropertyCount(), PropertyFlag.TemporaryOrModified, flagged: false); } + if (_stateData.EntityState != oldState) + { + _stateData.EntityState = oldState; + } + StateManager.InternalEntityEntryNotifier.StateChanging(this, newState); if (newState == EntityState.Unchanged @@ -265,7 +270,21 @@ private void SetEntityState(EntityState oldState, EntityState newState, bool acc StateManager.ChangedCount--; } + FireStateChanged(oldState); + } + + private void FireStateChanged(EntityState oldState) + { StateManager.InternalEntityEntryNotifier.StateChanged(this, oldState, fromQuery: false); + + if (oldState != EntityState.Detached) + { + StateManager.OnStateChanged(this, oldState); + } + else + { + StateManager.OnTracked(this, fromQuery: false); + } } private void SetServiceProperties(EntityState oldState, EntityState newState) @@ -293,9 +312,13 @@ private void SetServiceProperties(EntityState oldState, EntityState newState) public virtual void MarkUnchangedFromQuery([CanBeNull] ISet handledForeignKeys) { StateManager.InternalEntityEntryNotifier.StateChanging(this, EntityState.Unchanged); + _stateData.EntityState = EntityState.Unchanged; + StateManager.InternalEntityEntryNotifier.StateChanged(this, EntityState.Detached, fromQuery: true); + StateManager.OnTracked(this, fromQuery: true); + var trackingQueryMode = StateManager.GetTrackingQueryMode(EntityType); if (trackingQueryMode != TrackingQueryMode.Simple) { @@ -405,7 +428,7 @@ public virtual void SetPropertyModified( if (changeState) { StateManager.ChangedCount++; - StateManager.InternalEntityEntryNotifier.StateChanged(this, currentState, fromQuery: false); + FireStateChanged(currentState); } } else if (currentState == EntityState.Modified @@ -416,7 +439,7 @@ public virtual void SetPropertyModified( StateManager.InternalEntityEntryNotifier.StateChanging(this, EntityState.Unchanged); _stateData.EntityState = EntityState.Unchanged; StateManager.ChangedCount--; - StateManager.InternalEntityEntryNotifier.StateChanged(this, currentState, fromQuery: false); + FireStateChanged(currentState); } } diff --git a/src/EFCore/ChangeTracking/Internal/StateManager.cs b/src/EFCore/ChangeTracking/Internal/StateManager.cs index b2fc468ea0b..5169b4c2fb9 100644 --- a/src/EFCore/ChangeTracking/Internal/StateManager.cs +++ b/src/EFCore/ChangeTracking/Internal/StateManager.cs @@ -932,5 +932,39 @@ private static void AcceptAllChanges(IEnumerable changedEnt entry.AcceptChanges(); } } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public event Action Tracked; + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public virtual void OnTracked(InternalEntityEntry internalEntityEntry, bool fromQuery) + { + var @event = Tracked; + + @event?.Invoke(Context.ChangeTracker, new EntityTrackedEventArgs(internalEntityEntry, fromQuery)); + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public event Action StateChanged; + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public virtual void OnStateChanged(InternalEntityEntry internalEntityEntry, EntityState oldState) + { + var @event = StateChanged; + + @event?.Invoke(Context.ChangeTracker, new EntityStateEventArgs(internalEntityEntry, oldState, internalEntityEntry.EntityState)); + } } } diff --git a/test/EFCore.Tests/ChangeTracking/ChangeTrackerTest.cs b/test/EFCore.Tests/ChangeTracking/ChangeTrackerTest.cs index d4dd32c7462..4debfe3c833 100644 --- a/test/EFCore.Tests/ChangeTracking/ChangeTrackerTest.cs +++ b/test/EFCore.Tests/ChangeTracking/ChangeTrackerTest.cs @@ -20,6 +20,356 @@ namespace Microsoft.EntityFrameworkCore.ChangeTracking { public class ChangeTrackerTest { + [Fact] + public void State_change_events_fire_from_query() + { + var tracked = new List(); + var changed = new List(); + + Seed(); + + using (var context = new LikeAZooContext()) + { + RegisterEvents(context, tracked, changed); + + Assert.Equal(2, context.Cats.OrderBy(e => e.Id).ToList().Count); + + Assert.Equal(2, tracked.Count); + Assert.Equal(0, changed.Count); + + AssertTrackedEvent(context, 1, EntityState.Unchanged, tracked[0], fromQuery: true); + AssertTrackedEvent(context, 2, EntityState.Unchanged, tracked[1], fromQuery: true); + } + } + + [Fact] + public void State_change_events_fire_from_Attach() + { + var tracked = new List(); + var changed = new List(); + + using (var context = new LikeAZooContext()) + { + RegisterEvents(context, tracked, changed); + + context.Attach(new Cat(1)); + + Assert.Equal(1, tracked.Count); + Assert.Equal(0, changed.Count); + + AssertTrackedEvent(context, 1, EntityState.Unchanged, tracked[0], fromQuery: false); + + context.Entry(new Cat(2)).State = EntityState.Unchanged; + + Assert.Equal(2, tracked.Count); + Assert.Equal(0, changed.Count); + + AssertTrackedEvent(context, 2, EntityState.Unchanged, tracked[1], fromQuery: false); + } + } + + [Fact] + public void State_change_events_fire_from_Add() + { + var tracked = new List(); + var changed = new List(); + + using (var context = new LikeAZooContext()) + { + RegisterEvents(context, tracked, changed); + + context.Add(new Cat(1)); + + Assert.Equal(1, tracked.Count); + Assert.Equal(0, changed.Count); + + AssertTrackedEvent(context, 1, EntityState.Added, tracked[0], fromQuery: false); + + context.Entry(new Cat(2)).State = EntityState.Added; + + Assert.Equal(2, tracked.Count); + Assert.Equal(0, changed.Count); + + AssertTrackedEvent(context, 2, EntityState.Added, tracked[1], fromQuery: false); + } + } + + [Fact] + public void State_change_events_fire_from_Update() + { + var tracked = new List(); + var changed = new List(); + + using (var context = new LikeAZooContext()) + { + RegisterEvents(context, tracked, changed); + + context.Update(new Cat(1)); + + Assert.Equal(1, tracked.Count); + Assert.Equal(0, changed.Count); + + AssertTrackedEvent(context, 1, EntityState.Modified, tracked[0], fromQuery: false); + + context.Entry(new Cat(2)).State = EntityState.Modified; + + Assert.Equal(2, tracked.Count); + Assert.Equal(0, changed.Count); + + AssertTrackedEvent(context, 2, EntityState.Modified, tracked[1], fromQuery: false); + } + } + + [Fact] + public void State_change_events_fire_for_tracked_state_changes() + { + var tracked = new List(); + var changed = new List(); + + using (var context = new LikeAZooContext()) + { + RegisterEvents(context, tracked, changed); + + context.AddRange(new Cat(1), new Cat(2)); + + Assert.Equal(2, tracked.Count); + Assert.Equal(0, changed.Count); + + AssertTrackedEvent(context, 1, EntityState.Added, tracked[0], fromQuery: false); + AssertTrackedEvent(context, 2, EntityState.Added, tracked[1], fromQuery: false); + + context.Entry(context.Cats.Find(1)).State = EntityState.Unchanged; + context.Entry(context.Cats.Find(2)).State = EntityState.Modified; + + Assert.Equal(2, tracked.Count); + Assert.Equal(2, changed.Count); + + AssertChangedEvent(context, 1, EntityState.Added, EntityState.Unchanged, changed[0]); + AssertChangedEvent(context, 2, EntityState.Added, EntityState.Modified, changed[1]); + + context.Entry(context.Cats.Find(1)).State = EntityState.Added; + context.Entry(context.Cats.Find(2)).State = EntityState.Deleted; + + Assert.Equal(2, tracked.Count); + Assert.Equal(4, changed.Count); + + AssertChangedEvent(context, 1, EntityState.Unchanged, EntityState.Added, changed[2]); + AssertChangedEvent(context, 2, EntityState.Modified, EntityState.Deleted, changed[3]); + + context.Remove(context.Cats.Find(1)); + context.Entry(context.Cats.Find(2)).State = EntityState.Detached; + + Assert.Equal(2, tracked.Count); + Assert.Equal(6, changed.Count); + + AssertChangedEvent(context, null, EntityState.Added, EntityState.Detached, changed[4]); + AssertChangedEvent(context, null, EntityState.Deleted, EntityState.Detached, changed[5]); + } + } + + [Fact] + public void State_change_events_fire_when_saving_changes() + { + var tracked = new List(); + var changed = new List(); + + Seed(); + + using (var context = new LikeAZooContext()) + { + RegisterEvents(context, tracked, changed); + + var cat1 = context.Cats.Find(1); + + Assert.Equal(1, tracked.Count); + Assert.Equal(0, changed.Count); + + AssertTrackedEvent(context, 1, EntityState.Unchanged, tracked[0], fromQuery: true); + + context.Add(new Cat(3)); + cat1.Name = "Clippy"; + + context.ChangeTracker.DetectChanges(); + + Assert.Equal(2, tracked.Count); + Assert.Equal(1, changed.Count); + + AssertTrackedEvent(context, 3, EntityState.Added, tracked[1], fromQuery: false); + AssertChangedEvent(context, 1, EntityState.Unchanged, EntityState.Modified, changed[0]); + + context.SaveChanges(); + + Assert.Equal(2, tracked.Count); + Assert.Equal(3, changed.Count); + + AssertChangedEvent(context, 1, EntityState.Modified, EntityState.Unchanged, changed[1]); + AssertChangedEvent(context, 3, EntityState.Added, EntityState.Unchanged, changed[2]); + } + } + + [Fact] + public void State_change_events_fire_when_property_modified_flags_cause_state_change() + { + var tracked = new List(); + var changed = new List(); + + using (var context = new LikeAZooContext()) + { + RegisterEvents(context, tracked, changed); + + var cat = context.Attach(new Cat(3) { Name = "Achilles" }).Entity; + + Assert.Equal(1, tracked.Count); + Assert.Equal(0, changed.Count); + + AssertTrackedEvent(context, 3, EntityState.Unchanged, tracked[0], fromQuery: false); + + context.Entry(cat).Property(e => e.Name).IsModified = true; + + Assert.Equal(1, tracked.Count); + Assert.Equal(1, changed.Count); + + AssertChangedEvent(context, 3, EntityState.Unchanged, EntityState.Modified, changed[0]); + + context.Entry(cat).Property(e => e.Name).IsModified = false; + + Assert.Equal(1, tracked.Count); + Assert.Equal(2, changed.Count); + + AssertChangedEvent(context, 3, EntityState.Modified, EntityState.Unchanged, changed[1]); + } + } + + [Fact] + public void State_change_events_are_limited_to_the_current_context() + { + var tracked1 = new List(); + var changed1 = new List(); + var tracked2 = new List(); + var changed2 = new List(); + + Seed(); + + using (var context = new LikeAZooContext()) + { + RegisterEvents(context, tracked1, changed1); + + using (var context2 = new LikeAZooContext()) + { + RegisterEvents(context2, tracked2, changed2); + + Assert.Equal(2, context2.Cats.OrderBy(e => e.Id).ToList().Count); + + Assert.Equal(2, tracked2.Count); + Assert.Equal(0, changed2.Count); + + context2.Entry(context2.Cats.Find(1)).State = EntityState.Modified; + + Assert.Equal(2, tracked2.Count); + Assert.Equal(1, changed2.Count); + + Assert.Equal(0, tracked1.Count); + Assert.Equal(0, changed1.Count); + } + + Assert.Equal(2, context.Cats.OrderBy(e => e.Id).ToList().Count); + + Assert.Equal(2, tracked1.Count); + Assert.Equal(0, changed1.Count); + + context.Entry(context.Cats.Find(1)).State = EntityState.Modified; + + Assert.Equal(2, tracked1.Count); + Assert.Equal(1, changed1.Count); + + Assert.Equal(2, tracked2.Count); + Assert.Equal(1, changed2.Count); + } + } + + private static void AssertTrackedEvent( + LikeAZooContext context, + int id, + EntityState newState, + EntityTrackedEventArgs tracked, + bool fromQuery) + { + Assert.Equal(newState, tracked.Entry.State); + Assert.Equal(fromQuery, tracked.FromQuery); + Assert.Same(context.Cats.Find(id), tracked.Entry.Entity); + } + + private static void AssertChangedEvent( + LikeAZooContext context, + int? id, + EntityState oldState, + EntityState newState, + EntityStateEventArgs changed) + { + Assert.Equal(oldState, changed.OldState); + Assert.Equal(newState, changed.NewState); + Assert.Equal(newState, changed.Entry.State); + + if (id != null) + { + Assert.Same(context.Cats.Find(id), changed.Entry.Entity); + } + } + + private static void RegisterEvents( + LikeAZooContext context, + IList tracked, + IList changed) + { + context.ChangeTracker.Tracked += (s, e) => + { + Assert.Same(context.ChangeTracker, s); + tracked.Add(e); + }; + + context.ChangeTracker.StateChanged += (s, e) => + { + Assert.Same(context.ChangeTracker, s); + Assert.Equal(e.NewState, e.Entry.State); + changed.Add(e); + }; + } + + private void ChangeTracker_EnityStateChanging(object arg1, EntityStateEventArgs arg2) + { + throw new NotImplementedException(); + } + + private class Cat + { + public Cat(int id) => Id = id; + + // ReSharper disable once AutoPropertyCanBeMadeGetOnly.Local + public int Id { get; private set; } + + public string Name { get; set; } + } + + private class LikeAZooContext : DbContext + { + public DbSet Cats { get; set; } + + protected internal override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + => optionsBuilder.UseInMemoryDatabase(nameof(LikeAZooContext)); + } + + private void Seed() + { + using (var context = new LikeAZooContext()) + { + context.Database.EnsureDeleted(); + + context.AddRange(new Cat(1), new Cat(2)); + + context.SaveChanges(); + } + } + [Theory] [InlineData(false)] [InlineData(true)] diff --git a/test/EFCore.Tests/TestUtilities/FakeStateManager.cs b/test/EFCore.Tests/TestUtilities/FakeStateManager.cs index 016e6dd7a55..4b2bfb89928 100644 --- a/test/EFCore.Tests/TestUtilities/FakeStateManager.cs +++ b/test/EFCore.Tests/TestUtilities/FakeStateManager.cs @@ -9,6 +9,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Update; @@ -21,11 +22,6 @@ public class FakeStateManager : IStateManager public bool SaveChangesCalled { get; set; } public bool SaveChangesAsyncCalled { get; set; } - public IEntityFinder CreateEntityFinder(IEntityType entityType) - { - throw new NotImplementedException(); - } - public TrackingQueryMode GetTrackingQueryMode(IEntityType entityType) => TrackingQueryMode.Multiple; public void EndSingleQueryMode() @@ -40,33 +36,6 @@ public void Unsubscribe() { } - public void UpdateIdentityMap(InternalEntityEntry entry, IKey principalKey) - { - throw new NotImplementedException(); - } - - public void UpdateDependentMap(InternalEntityEntry entry, IForeignKey foreignKey) - { - throw new NotImplementedException(); - } - - public IEnumerable GetDependents(InternalEntityEntry principalEntry, IForeignKey foreignKey) - { - throw new NotImplementedException(); - } - - public IEnumerable GetDependentsUsingRelationshipSnapshot(InternalEntityEntry principalEntry, IForeignKey foreignKey) - { - throw new NotImplementedException(); - } - - public IEnumerable GetDependentsFromNavigation(InternalEntityEntry principalEntry, IForeignKey foreignKey) - { - throw new NotImplementedException(); - } - - public IReadOnlyList GetEntriesToSave() => Enumerable.Empty().ToList(); - public int SaveChanges(bool acceptAllChangesOnSuccess) { SaveChangesCalled = true; @@ -79,108 +48,41 @@ public int SaveChanges(bool acceptAllChangesOnSuccess) return Task.FromResult(1); } - public virtual void AcceptAllChanges() - { - throw new NotImplementedException(); - } - - public InternalEntityEntry GetOrCreateEntry(object entity) - { - throw new NotImplementedException(); - } - - public InternalEntityEntry GetOrCreateEntry(object entity, IEntityType entityType) - { - throw new NotImplementedException(); - } - - public InternalEntityEntry CreateEntry(IDictionary values, IEntityType entityType) - { - throw new NotImplementedException(); - } - - public InternalEntityEntry StartTrackingFromQuery( - IEntityType baseEntityType, - object entity, - ValueBuffer valueBuffer, - ISet handledForeignKeys) - { - throw new NotImplementedException(); - } - - public void BeginTrackingQuery() - { - throw new NotImplementedException(); - } - - public InternalEntityEntry TryGetEntry(IKey key, object[] keyValues) - { - throw new NotImplementedException(); - } - - public InternalEntityEntry TryGetEntry(IKey key, ValueBuffer valueBuffer, bool throwOnNullKey) - { - throw new NotImplementedException(); - } - - public InternalEntityEntry TryGetEntry(object entity, bool throwOnNonUniqueness = true) - { - throw new NotImplementedException(); - } - - public InternalEntityEntry TryGetEntry(object entity, IEntityType type) - { - throw new NotImplementedException(); - } - public IEnumerable Entries => Entries ?? Enumerable.Empty(); public int ChangedCount { get; set; } + public IEntityFinder CreateEntityFinder(IEntityType entityType) => throw new NotImplementedException(); + public void UpdateIdentityMap(InternalEntityEntry entry, IKey principalKey) => throw new NotImplementedException(); + public void UpdateDependentMap(InternalEntityEntry entry, IForeignKey foreignKey) => throw new NotImplementedException(); + public IEnumerable GetDependents(InternalEntityEntry principalEntry, IForeignKey foreignKey) => throw new NotImplementedException(); + public IEnumerable GetDependentsUsingRelationshipSnapshot(InternalEntityEntry principalEntry, IForeignKey foreignKey) => throw new NotImplementedException(); + public IEnumerable GetDependentsFromNavigation(InternalEntityEntry principalEntry, IForeignKey foreignKey) => throw new NotImplementedException(); + public IReadOnlyList GetEntriesToSave() => Enumerable.Empty().ToList(); + public virtual void AcceptAllChanges() => throw new NotImplementedException(); + public InternalEntityEntry GetOrCreateEntry(object entity) => throw new NotImplementedException(); + public InternalEntityEntry GetOrCreateEntry(object entity, IEntityType entityType) => throw new NotImplementedException(); + public InternalEntityEntry CreateEntry(IDictionary values, IEntityType entityType) => throw new NotImplementedException(); + public InternalEntityEntry StartTrackingFromQuery(IEntityType baseEntityType,object entity, ValueBuffer valueBuffer, ISet handledForeignKeys) => throw new NotImplementedException(); + public void BeginTrackingQuery() => throw new NotImplementedException(); + public InternalEntityEntry TryGetEntry(IKey key, object[] keyValues) => throw new NotImplementedException(); + public InternalEntityEntry TryGetEntry(IKey key, ValueBuffer valueBuffer, bool throwOnNullKey) => throw new NotImplementedException(); + public InternalEntityEntry TryGetEntry(object entity, bool throwOnNonUniqueness = true) => throw new NotImplementedException(); + public InternalEntityEntry TryGetEntry(object entity, IEntityType type) => throw new NotImplementedException(); public IInternalEntityEntryNotifier InternalEntityEntryNotifier => throw new NotImplementedException(); - public IValueGenerationManager ValueGenerationManager => throw new NotImplementedException(); - public IEntityMaterializerSource EntityMaterializerSource { get; } - - public InternalEntityEntry StartTracking(InternalEntityEntry entry) - { - throw new NotImplementedException(); - } - - public void StopTracking(InternalEntityEntry entry) - { - throw new NotImplementedException(); - } - - public void RecordReferencedUntrackedEntity(object referencedEntity, INavigation navigation, InternalEntityEntry referencedFromEntry) - { - throw new NotImplementedException(); - } - - public IEnumerable> GetRecordedReferers(object referencedEntity, bool clear) - { - throw new NotImplementedException(); - } - - public InternalEntityEntry GetPrincipal(InternalEntityEntry entityEntry, IForeignKey foreignKey) - { - throw new NotImplementedException(); - } - - public InternalEntityEntry GetPrincipalUsingPreStoreGeneratedValues(InternalEntityEntry entityEntry, IForeignKey foreignKey) - { - throw new NotImplementedException(); - } - - public InternalEntityEntry GetPrincipalUsingRelationshipSnapshot(InternalEntityEntry entityEntry, IForeignKey foreignKey) - { - throw new NotImplementedException(); - } - - public DbContext Context - { - get { throw new NotImplementedException(); } - } + public InternalEntityEntry StartTracking(InternalEntityEntry entry) => throw new NotImplementedException(); + public void StopTracking(InternalEntityEntry entry) => throw new NotImplementedException(); + public void RecordReferencedUntrackedEntity(object referencedEntity, INavigation navigation, InternalEntityEntry referencedFromEntry) => throw new NotImplementedException(); + public IEnumerable> GetRecordedReferers(object referencedEntity, bool clear) => throw new NotImplementedException(); + public InternalEntityEntry GetPrincipal(InternalEntityEntry entityEntry, IForeignKey foreignKey) => throw new NotImplementedException(); + public InternalEntityEntry GetPrincipalUsingPreStoreGeneratedValues(InternalEntityEntry entityEntry, IForeignKey foreignKey) => throw new NotImplementedException(); + public InternalEntityEntry GetPrincipalUsingRelationshipSnapshot(InternalEntityEntry entityEntry, IForeignKey foreignKey) => throw new NotImplementedException(); + public DbContext Context => throw new NotImplementedException(); + public event Action Tracked; + public void OnTracked(InternalEntityEntry internalEntityEntry, bool fromQuery) => Tracked?.Invoke(null, null); + public event Action StateChanged; + public void OnStateChanged(InternalEntityEntry internalEntityEntry, EntityState oldState) => StateChanged?.Invoke(null, null); } }