diff --git a/src/EFCore/ChangeTracking/ChangeTracker.cs b/src/EFCore/ChangeTracking/ChangeTracker.cs index 76f5400f052..51be795ed3f 100644 --- a/src/EFCore/ChangeTracking/ChangeTracker.cs +++ b/src/EFCore/ChangeTracking/ChangeTracker.cs @@ -215,6 +215,73 @@ public virtual IEnumerable> Entries() .Select(e => new EntityEntry(e)); } + /// + /// Gets an for each entity being tracked by the context that has the given state(s). + /// The entries provide access to change tracking information and operations for each entity. + /// + /// + /// + /// This method does not call and returns entries based on the current state + /// of the change tracker. This makes it suitable for use in high-performance scenarios or in contexts + /// that track many entities, where calling may be expensive. + /// + /// + /// Note that modification of entity state while iterating over the returned enumeration may result in + /// an indicating that the collection was modified while enumerating. + /// To avoid this, create a defensive copy using or similar before iterating. + /// + /// + /// See EF Core change tracking for more information and examples. + /// + /// + /// If , entries with state are included. + /// If , entries with state are included. + /// If , entries with state are included. + /// If , entries with state are included. + /// An entry for each entity that has the given state(s). + public virtual IEnumerable GetEntriesForState( + bool added = false, + bool modified = false, + bool deleted = false, + bool unchanged = false) + => StateManager.GetEntriesForState(added, modified, deleted, unchanged, returnSharedIdentity: true) + .Select(e => new EntityEntry(e)); + + /// + /// Gets an for each entity of a given type being tracked by the context + /// that has the given state(s). The entries provide access to change tracking information and operations for each entity. + /// + /// + /// + /// This method does not call and returns entries based on the current state + /// of the change tracker. This makes it suitable for use in high-performance scenarios or in contexts + /// that track many entities, where calling may be expensive. + /// + /// + /// Note that modification of entity state while iterating over the returned enumeration may result in + /// an indicating that the collection was modified while enumerating. + /// To avoid this, create a defensive copy using or similar before iterating. + /// + /// + /// See EF Core change tracking for more information and examples. + /// + /// + /// The type of entities to get entries for. + /// If , entries with state are included. + /// If , entries with state are included. + /// If , entries with state are included. + /// If , entries with state are included. + /// An entry for each entity of the given type that has the given state(s). + public virtual IEnumerable> GetEntriesForState( + bool added = false, + bool modified = false, + bool deleted = false, + bool unchanged = false) + where TEntity : class + => StateManager.GetEntriesForState(added, modified, deleted, unchanged, returnSharedIdentity: true) + .Where(e => e.Entity is TEntity) + .Select(e => new EntityEntry(e)); + private void TryDetectChanges() { if (AutoDetectChangesEnabled) diff --git a/test/EFCore.Tests/ChangeTracking/ChangeTrackerTest.cs b/test/EFCore.Tests/ChangeTracking/ChangeTrackerTest.cs index 0368f169496..dccf5c60562 100644 --- a/test/EFCore.Tests/ChangeTracking/ChangeTrackerTest.cs +++ b/test/EFCore.Tests/ChangeTracking/ChangeTrackerTest.cs @@ -3484,6 +3484,80 @@ public void Auto_DetectChanges_for_Entries_can_be_switched_off(bool useGenericOv Assert.Equal(EntityState.Unchanged, entry.State); } + [ConditionalTheory, + InlineData(false), + InlineData(true)] + public void GetEntriesForState_does_not_call_DetectChanges(bool useGenericOverload) + { + using var context = new EarlyLearningCenter(); + var entry = context.Attach( + new Product { Id = 1, CategoryId = 66 }); + + entry.Entity.CategoryId = 77; + + Assert.Equal(EntityState.Unchanged, entry.State); + + if (useGenericOverload) + { + _ = context.ChangeTracker.GetEntriesForState(unchanged: true).ToList(); + } + else + { + _ = context.ChangeTracker.GetEntriesForState(unchanged: true).ToList(); + } + + Assert.Equal(EntityState.Unchanged, entry.State); + } + + [ConditionalTheory, + InlineData(false), + InlineData(true)] + public void GetEntriesForState_returns_only_entries_with_matching_state(bool useGenericOverload) + { + using var context = new EarlyLearningCenter(); + + context.Add(new Product { Id = 1, CategoryId = 1 }); + context.Attach(new Product { Id = 2, CategoryId = 2 }); + var modifiedEntry = context.Attach(new Product { Id = 3, CategoryId = 3 }); + modifiedEntry.State = EntityState.Modified; + var deletedEntry = context.Attach(new Product { Id = 4, CategoryId = 4 }); + deletedEntry.State = EntityState.Deleted; + + if (useGenericOverload) + { + Assert.Single(context.ChangeTracker.GetEntriesForState(added: true)); + Assert.Single(context.ChangeTracker.GetEntriesForState(unchanged: true)); + Assert.Single(context.ChangeTracker.GetEntriesForState(modified: true)); + Assert.Single(context.ChangeTracker.GetEntriesForState(deleted: true)); + Assert.Equal(2, context.ChangeTracker.GetEntriesForState(added: true, modified: true).Count()); + Assert.Equal(4, context.ChangeTracker.GetEntriesForState(added: true, modified: true, deleted: true, unchanged: true).Count()); + Assert.Empty(context.ChangeTracker.GetEntriesForState()); + } + else + { + Assert.Single(context.ChangeTracker.GetEntriesForState(added: true)); + Assert.Single(context.ChangeTracker.GetEntriesForState(unchanged: true)); + Assert.Single(context.ChangeTracker.GetEntriesForState(modified: true)); + Assert.Single(context.ChangeTracker.GetEntriesForState(deleted: true)); + Assert.Equal(2, context.ChangeTracker.GetEntriesForState(added: true, modified: true).Count()); + Assert.Equal(4, context.ChangeTracker.GetEntriesForState(added: true, modified: true, deleted: true, unchanged: true).Count()); + Assert.Empty(context.ChangeTracker.GetEntriesForState()); + } + } + + [ConditionalFact] + public void GetEntriesForState_generic_filters_by_type() + { + using var context = new EarlyLearningCenter(); + + context.Add(new Product { Id = 1, CategoryId = 1 }); + context.Add(new Category { Id = 1 }); + + Assert.Single(context.ChangeTracker.GetEntriesForState(added: true)); + Assert.Single(context.ChangeTracker.GetEntriesForState(added: true)); + Assert.Equal(2, context.ChangeTracker.GetEntriesForState(added: true).Count()); + } + [ConditionalTheory, InlineData(false), InlineData(true)]