From b01b396e62443a014790861efd6da3a07c8e38a7 Mon Sep 17 00:00:00 2001 From: Arthur Vickers Date: Fri, 9 Jun 2017 08:52:16 -0700 Subject: [PATCH] Provide access to referencing entity in TrackGraph Issue #7389 Doing for preview2 due to discussion here: #8744 --- src/EFCore/ChangeTracking/ChangeTracker.cs | 2 +- .../ChangeTracking/EntityEntryGraphNode.cs | 33 ++-- .../Internal/EntityGraphAttacher.cs | 4 +- .../ChangeTracking/ChangeTrackerTest.cs | 152 +++++++++++++++++- 4 files changed, 172 insertions(+), 19 deletions(-) diff --git a/src/EFCore/ChangeTracking/ChangeTracker.cs b/src/EFCore/ChangeTracking/ChangeTracker.cs index c11384388ba..806e93f32f4 100644 --- a/src/EFCore/ChangeTracking/ChangeTracker.cs +++ b/src/EFCore/ChangeTracking/ChangeTracker.cs @@ -203,7 +203,7 @@ public virtual void TrackGraph( var rootEntry = StateManager.GetOrCreateEntry(rootEntity); GraphIterator.TraverseGraph( - new EntityEntryGraphNode(rootEntry, null), + new EntityEntryGraphNode(rootEntry, null, null), n => { if (n.Entry.State != EntityState.Detached) diff --git a/src/EFCore/ChangeTracking/EntityEntryGraphNode.cs b/src/EFCore/ChangeTracking/EntityEntryGraphNode.cs index 12e84e0b048..d64dacbac73 100644 --- a/src/EFCore/ChangeTracking/EntityEntryGraphNode.cs +++ b/src/EFCore/ChangeTracking/EntityEntryGraphNode.cs @@ -15,22 +15,30 @@ namespace Microsoft.EntityFrameworkCore.ChangeTracking /// public class EntityEntryGraphNode : IInfrastructure { - private readonly InternalEntityEntry _internalEntityEntry; + private readonly InternalEntityEntry _sourceEntry; + private readonly InternalEntityEntry _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 EntityEntryGraphNode( - [NotNull] InternalEntityEntry internalEntityEntry, + [NotNull] InternalEntityEntry entry, + [CanBeNull] InternalEntityEntry sourceEntry, [CanBeNull] INavigation inboundNavigation) { - Check.NotNull(internalEntityEntry, nameof(internalEntityEntry)); + Check.NotNull(entry, nameof(entry)); - _internalEntityEntry = internalEntityEntry; + _entry = entry; + _sourceEntry = sourceEntry; InboundNavigation = inboundNavigation; } + /// + /// Gets the entry tracking information about this entity. + /// + public virtual EntityEntry SourceEntry => _sourceEntry == null ? null : new EntityEntry(_sourceEntry); + /// /// Gets the navigation property that is being traversed to reach this node in the graph. /// @@ -44,7 +52,7 @@ public EntityEntryGraphNode( /// /// Gets the entry tracking information about this entity. /// - public virtual EntityEntry Entry => new EntityEntry(_internalEntityEntry); + public virtual EntityEntry Entry => new EntityEntry(_entry); /// /// @@ -55,7 +63,7 @@ public EntityEntryGraphNode( /// application code. /// /// - InternalEntityEntry IInfrastructure.Instance => _internalEntityEntry; + InternalEntityEntry IInfrastructure.Instance => _entry; /// /// Creates a new node for the entity that is being traversed next in the graph. @@ -70,11 +78,18 @@ public virtual EntityEntryGraphNode CreateNode( [NotNull] EntityEntryGraphNode currentNode, [NotNull] InternalEntityEntry internalEntityEntry, [NotNull] INavigation reachedVia) - => new EntityEntryGraphNode( - Check.NotNull(internalEntityEntry, nameof(internalEntityEntry)), - Check.NotNull(reachedVia, nameof(reachedVia))) + { + Check.NotNull(currentNode, nameof(currentNode)); + Check.NotNull(internalEntityEntry, nameof(internalEntityEntry)); + Check.NotNull(reachedVia, nameof(reachedVia)); + + return new EntityEntryGraphNode( + internalEntityEntry, + currentNode.Entry.GetInfrastructure(), + reachedVia) { NodeState = Check.NotNull(currentNode, nameof(currentNode)).NodeState }; + } } } diff --git a/src/EFCore/ChangeTracking/Internal/EntityGraphAttacher.cs b/src/EFCore/ChangeTracking/Internal/EntityGraphAttacher.cs index 3b27853ef23..254aacca284 100644 --- a/src/EFCore/ChangeTracking/Internal/EntityGraphAttacher.cs +++ b/src/EFCore/ChangeTracking/Internal/EntityGraphAttacher.cs @@ -31,7 +31,7 @@ public EntityGraphAttacher([NotNull] IEntityEntryGraphIterator graphIterator) /// public virtual void AttachGraph(InternalEntityEntry rootEntry, EntityState entityState) => _graphIterator.TraverseGraph( - new EntityEntryGraphNode(rootEntry, null) + new EntityEntryGraphNode(rootEntry, null, null) { NodeState = entityState }, @@ -46,7 +46,7 @@ public virtual Task AttachGraphAsync( EntityState entityState, CancellationToken cancellationToken = default(CancellationToken)) => _graphIterator.TraverseGraphAsync( - new EntityEntryGraphNode(rootEntry, null) + new EntityEntryGraphNode(rootEntry, null, null) { NodeState = entityState }, diff --git a/test/EFCore.Tests/ChangeTracking/ChangeTrackerTest.cs b/test/EFCore.Tests/ChangeTracking/ChangeTrackerTest.cs index 83f7a2b865c..21cc7dcdab0 100644 --- a/test/EFCore.Tests/ChangeTracking/ChangeTrackerTest.cs +++ b/test/EFCore.Tests/ChangeTracking/ChangeTrackerTest.cs @@ -8,6 +8,7 @@ using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Internal; +using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.Extensions.DependencyInjection; using Xunit; @@ -71,6 +72,16 @@ public void Can_get_Context() } } + private static string NodeString(EntityEntryGraphNode node) + => EntryString(node.SourceEntry) + + " ---" + node.InboundNavigation?.Name + "--> " + + EntryString(node.Entry); + + private static string EntryString(EntityEntry entry) + => entry == null + ? "" + : entry.Metadata.DisplayName() + ":" + entry.Property("Id").CurrentValue; + [Fact] public void Can_attach_parent_with_child_collection() { @@ -87,7 +98,23 @@ public void Can_attach_parent_with_child_collection() } }; - context.ChangeTracker.TrackGraph(category, e => e.Entry.State = EntityState.Modified); + var traversal = new List(); + + context.ChangeTracker.TrackGraph(category, e => + { + traversal.Add(NodeString(e)); + e.Entry.State = EntityState.Modified; + }); + + Assert.Equal( + new List + { + " -----> Category:1", + "Category:1 ---Products--> Product:1", + "Category:1 ---Products--> Product:2", + "Category:1 ---Products--> Product:3" + }, + traversal); Assert.Equal(4, context.ChangeTracker.Entries().Count()); @@ -113,7 +140,21 @@ public void Can_attach_child_with_reference_to_parent() { var product = new Product { Id = 1, Category = new Category { Id = 1 } }; - context.ChangeTracker.TrackGraph(product, e => e.Entry.State = EntityState.Modified); + var traversal = new List(); + + context.ChangeTracker.TrackGraph(product, e => + { + traversal.Add(NodeString(e)); + e.Entry.State = EntityState.Modified; + }); + + Assert.Equal( + new List + { + " -----> Product:1", + "Product:1 ---Category--> Category:1" + }, + traversal); Assert.Equal(2, context.ChangeTracker.Entries().Count()); @@ -132,7 +173,22 @@ public void Can_attach_parent_with_one_to_one_children() { var product = new Product { Id = 1, Details = new ProductDetails { Id = 1, Tag = new ProductDetailsTag { Id = 1 } } }; - context.ChangeTracker.TrackGraph(product, e => e.Entry.State = EntityState.Unchanged); + var traversal = new List(); + + context.ChangeTracker.TrackGraph(product, e => + { + traversal.Add(NodeString(e)); + e.Entry.State = EntityState.Unchanged; + }); + + Assert.Equal( + new List + { + " -----> Product:1", + "Product:1 ---Details--> ProductDetails:1", + "ProductDetails:1 ---Tag--> ProductDetailsTag:1" + }, + traversal); Assert.Equal(3, context.ChangeTracker.Entries().Count()); @@ -152,7 +208,22 @@ public void Can_attach_child_with_one_to_one_parents() { var tag = new ProductDetailsTag { Id = 1, Details = new ProductDetails { Id = 1, Product = new Product { Id = 1 } } }; - context.ChangeTracker.TrackGraph(tag, e => e.Entry.State = EntityState.Unchanged); + var traversal = new List(); + + context.ChangeTracker.TrackGraph(tag, e => + { + traversal.Add(NodeString(e)); + e.Entry.State = EntityState.Unchanged; + }); + + Assert.Equal( + new List + { + " -----> ProductDetailsTag:1", + "ProductDetailsTag:1 ---Details--> ProductDetails:1", + "ProductDetails:1 ---Product--> Product:1" + }, + traversal); Assert.Equal(3, context.ChangeTracker.Entries().Count()); @@ -172,7 +243,22 @@ public void Can_attach_entity_with_one_to_one_parent_and_child() { var details = new ProductDetails { Id = 1, Product = new Product { Id = 1 }, Tag = new ProductDetailsTag { Id = 1 } }; - context.ChangeTracker.TrackGraph(details, e => e.Entry.State = EntityState.Unchanged); + var traversal = new List(); + + context.ChangeTracker.TrackGraph(details, e => + { + traversal.Add(NodeString(e)); + e.Entry.State = EntityState.Unchanged; + }); + + Assert.Equal( + new List + { + " -----> ProductDetails:1", + "ProductDetails:1 ---Product--> Product:1", + "ProductDetails:1 ---Tag--> ProductDetailsTag:1" + }, + traversal); Assert.Equal(3, context.ChangeTracker.Entries().Count()); @@ -203,7 +289,22 @@ public void Entities_that_are_already_tracked_will_not_get_attached() } }; - context.ChangeTracker.TrackGraph(category, e => e.Entry.State = EntityState.Modified); + var traversal = new List(); + + context.ChangeTracker.TrackGraph(category, e => + { + traversal.Add(NodeString(e)); + e.Entry.State = EntityState.Modified; + }); + + Assert.Equal( + new List + { + " -----> Category:1", + "Category:1 ---Products--> Product:1", + "Category:1 ---Products--> Product:3" + }, + traversal); Assert.Equal(4, context.ChangeTracker.Entries().Count()); @@ -238,8 +339,11 @@ public void Further_graph_traversal_stops_if_an_entity_is_not_attached() } }; + var traversal = new List(); + context.ChangeTracker.TrackGraph(category, e => { + traversal.Add(NodeString(e)); var product = e.Entry.Entity as Product; if ((product == null) || (product.Id != 2)) @@ -248,6 +352,18 @@ public void Further_graph_traversal_stops_if_an_entity_is_not_attached() } }); + Assert.Equal( + new List + { + " -----> Category:1", + "Category:1 ---Products--> Product:1", + "Product:1 ---Details--> ProductDetails:1", + "Category:1 ---Products--> Product:2", + "Category:1 ---Products--> Product:3", + "Product:3 ---Details--> ProductDetails:3" + }, + traversal); + Assert.Equal(5, context.ChangeTracker.Entries().Count(e => e.State != EntityState.Detached)); Assert.Equal(EntityState.Unchanged, context.Entry(category).State); @@ -280,7 +396,16 @@ public void Graph_iterator_does_not_go_visit_Apple() var details = new ProductDetails { Id = 1, Product = new Product { Id = 1 } }; details.Product.Details = details; - context.ChangeTracker.TrackGraph(details, e => { }); + var traversal = new List(); + + context.ChangeTracker.TrackGraph(details, e => traversal.Add(NodeString(e))); + + Assert.Equal( + new List + { + " -----> ProductDetails:1" + }, + traversal); Assert.Equal(0, context.ChangeTracker.Entries().Count(e => e.State != EntityState.Detached)); } @@ -291,13 +416,26 @@ public void Can_attach_parent_with_some_new_and_some_existing_entities() { KeyValueAttachTest((category, changeTracker) => { + var traversal = new List(); + changeTracker.TrackGraph( category, e => { + traversal.Add(NodeString(e)); var product = e.Entry.Entity as Product; e.Entry.State = (product != null) && (product.Id == 0) ? EntityState.Added : EntityState.Unchanged; }); + + Assert.Equal( + new List + { + " -----> Category:77", + "Category:77 ---Products--> Product:77", + "Category:77 ---Products--> Product:0", + "Category:77 ---Products--> Product:78" + }, + traversal); }); }