From d460e88ee1046450fd953c0598112a0a38efd35f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Mar 2026 18:01:19 +0000 Subject: [PATCH 1/6] Initial plan From b385fa0e913b5da2a42a5f9fc8b0f1c5040fa50b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Mar 2026 18:09:38 +0000 Subject: [PATCH 2/6] Add ChangeTracker.GetEntriesForState() and GetEntriesForState() methods Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com> --- src/EFCore/ChangeTracking/ChangeTracker.cs | 67 +++++++++++++++++ .../ChangeTracking/ChangeTrackerTest.cs | 74 +++++++++++++++++++ 2 files changed, 141 insertions(+) diff --git a/src/EFCore/ChangeTracking/ChangeTracker.cs b/src/EFCore/ChangeTracking/ChangeTracker.cs index 76f5400f052..3fb88fb5250 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) + .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) + .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..1674cac77dd 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); + } + else + { + context.ChangeTracker.GetEntriesForState(unchanged: true); + } + + 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)] From cdb0b02b3a59029408ddf9c15f8c1adc33b78142 Mon Sep 17 00:00:00 2001 From: Andriy Svyryd Date: Wed, 4 Mar 2026 12:08:55 -0800 Subject: [PATCH 3/6] Update src/EFCore/ChangeTracking/ChangeTracker.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/EFCore/ChangeTracking/ChangeTracker.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/EFCore/ChangeTracking/ChangeTracker.cs b/src/EFCore/ChangeTracking/ChangeTracker.cs index 3fb88fb5250..33125065ff1 100644 --- a/src/EFCore/ChangeTracking/ChangeTracker.cs +++ b/src/EFCore/ChangeTracking/ChangeTracker.cs @@ -244,7 +244,7 @@ public virtual IEnumerable GetEntriesForState( bool modified = false, bool deleted = false, bool unchanged = false) - => StateManager.GetEntriesForState(added, modified, deleted, unchanged) + => StateManager.GetEntriesForState(added, modified, deleted, unchanged, returnDeletedSharedIdentity: true) .Select(e => new EntityEntry(e)); /// From 95ed66b985fb4f5c6b9b91783e1d6c5bb524e148 Mon Sep 17 00:00:00 2001 From: Andriy Svyryd Date: Wed, 4 Mar 2026 12:09:14 -0800 Subject: [PATCH 4/6] Update test/EFCore.Tests/ChangeTracking/ChangeTrackerTest.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- test/EFCore.Tests/ChangeTracking/ChangeTrackerTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/EFCore.Tests/ChangeTracking/ChangeTrackerTest.cs b/test/EFCore.Tests/ChangeTracking/ChangeTrackerTest.cs index 1674cac77dd..dccf5c60562 100644 --- a/test/EFCore.Tests/ChangeTracking/ChangeTrackerTest.cs +++ b/test/EFCore.Tests/ChangeTracking/ChangeTrackerTest.cs @@ -3499,11 +3499,11 @@ public void GetEntriesForState_does_not_call_DetectChanges(bool useGenericOverlo if (useGenericOverload) { - context.ChangeTracker.GetEntriesForState(unchanged: true); + _ = context.ChangeTracker.GetEntriesForState(unchanged: true).ToList(); } else { - context.ChangeTracker.GetEntriesForState(unchanged: true); + _ = context.ChangeTracker.GetEntriesForState(unchanged: true).ToList(); } Assert.Equal(EntityState.Unchanged, entry.State); From a2090c1065c8bc18be145973b1fe32dfa861b574 Mon Sep 17 00:00:00 2001 From: Andriy Svyryd Date: Wed, 4 Mar 2026 12:09:59 -0800 Subject: [PATCH 5/6] Apply suggestion from @AndriySvyryd --- src/EFCore/ChangeTracking/ChangeTracker.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/EFCore/ChangeTracking/ChangeTracker.cs b/src/EFCore/ChangeTracking/ChangeTracker.cs index 33125065ff1..104a099e7fd 100644 --- a/src/EFCore/ChangeTracking/ChangeTracker.cs +++ b/src/EFCore/ChangeTracking/ChangeTracker.cs @@ -278,7 +278,7 @@ public virtual IEnumerable> GetEntriesForState( bool deleted = false, bool unchanged = false) where TEntity : class - => StateManager.GetEntriesForState(added, modified, deleted, unchanged) + => StateManager.GetEntriesForState(added, modified, deleted, unchanged, returnDeletedSharedIdentity: true) .Where(e => e.Entity is TEntity) .Select(e => new EntityEntry(e)); From 97b86c7478ef6d32ad6498fe95640389eee9cdbb Mon Sep 17 00:00:00 2001 From: Andriy Svyryd Date: Wed, 4 Mar 2026 12:13:17 -0800 Subject: [PATCH 6/6] Change returnDeletedSharedIdentity to returnSharedIdentity --- src/EFCore/ChangeTracking/ChangeTracker.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/EFCore/ChangeTracking/ChangeTracker.cs b/src/EFCore/ChangeTracking/ChangeTracker.cs index 104a099e7fd..51be795ed3f 100644 --- a/src/EFCore/ChangeTracking/ChangeTracker.cs +++ b/src/EFCore/ChangeTracking/ChangeTracker.cs @@ -244,7 +244,7 @@ public virtual IEnumerable GetEntriesForState( bool modified = false, bool deleted = false, bool unchanged = false) - => StateManager.GetEntriesForState(added, modified, deleted, unchanged, returnDeletedSharedIdentity: true) + => StateManager.GetEntriesForState(added, modified, deleted, unchanged, returnSharedIdentity: true) .Select(e => new EntityEntry(e)); /// @@ -278,7 +278,7 @@ public virtual IEnumerable> GetEntriesForState( bool deleted = false, bool unchanged = false) where TEntity : class - => StateManager.GetEntriesForState(added, modified, deleted, unchanged, returnDeletedSharedIdentity: true) + => StateManager.GetEntriesForState(added, modified, deleted, unchanged, returnSharedIdentity: true) .Where(e => e.Entity is TEntity) .Select(e => new EntityEntry(e));