Skip to content

Commit

Permalink
Provide access to referencing entity in TrackGraph
Browse files Browse the repository at this point in the history
Issue #7389

Doing for preview2 due to discussion here: #8744
  • Loading branch information
ajcvickers committed Jun 9, 2017
1 parent b39e445 commit b01b396
Show file tree
Hide file tree
Showing 4 changed files with 172 additions and 19 deletions.
2 changes: 1 addition & 1 deletion src/EFCore/ChangeTracking/ChangeTracker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
33 changes: 24 additions & 9 deletions src/EFCore/ChangeTracking/EntityEntryGraphNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,30 @@ namespace Microsoft.EntityFrameworkCore.ChangeTracking
/// </summary>
public class EntityEntryGraphNode : IInfrastructure<InternalEntityEntry>
{
private readonly InternalEntityEntry _internalEntityEntry;
private readonly InternalEntityEntry _sourceEntry;
private readonly InternalEntityEntry _entry;

/// <summary>
/// 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.
/// </summary>
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;
}

/// <summary>
/// Gets the entry tracking information about this entity.
/// </summary>
public virtual EntityEntry SourceEntry => _sourceEntry == null ? null : new EntityEntry(_sourceEntry);

/// <summary>
/// Gets the navigation property that is being traversed to reach this node in the graph.
/// </summary>
Expand All @@ -44,7 +52,7 @@ public EntityEntryGraphNode(
/// <summary>
/// Gets the entry tracking information about this entity.
/// </summary>
public virtual EntityEntry Entry => new EntityEntry(_internalEntityEntry);
public virtual EntityEntry Entry => new EntityEntry(_entry);

/// <summary>
/// <para>
Expand All @@ -55,7 +63,7 @@ public EntityEntryGraphNode(
/// application code.
/// </para>
/// </summary>
InternalEntityEntry IInfrastructure<InternalEntityEntry>.Instance => _internalEntityEntry;
InternalEntityEntry IInfrastructure<InternalEntityEntry>.Instance => _entry;

/// <summary>
/// Creates a new node for the entity that is being traversed next in the graph.
Expand All @@ -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
};
}
}
}
4 changes: 2 additions & 2 deletions src/EFCore/ChangeTracking/Internal/EntityGraphAttacher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public EntityGraphAttacher([NotNull] IEntityEntryGraphIterator graphIterator)
/// </summary>
public virtual void AttachGraph(InternalEntityEntry rootEntry, EntityState entityState)
=> _graphIterator.TraverseGraph(
new EntityEntryGraphNode(rootEntry, null)
new EntityEntryGraphNode(rootEntry, null, null)
{
NodeState = entityState
},
Expand All @@ -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
},
Expand Down
152 changes: 145 additions & 7 deletions test/EFCore.Tests/ChangeTracking/ChangeTrackerTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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
? "<None>"
: entry.Metadata.DisplayName() + ":" + entry.Property("Id").CurrentValue;

[Fact]
public void Can_attach_parent_with_child_collection()
{
Expand All @@ -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<string>();

context.ChangeTracker.TrackGraph(category, e =>
{
traversal.Add(NodeString(e));
e.Entry.State = EntityState.Modified;
});

Assert.Equal(
new List<string>
{
"<None> -----> 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());

Expand All @@ -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<string>();

context.ChangeTracker.TrackGraph(product, e =>
{
traversal.Add(NodeString(e));
e.Entry.State = EntityState.Modified;
});

Assert.Equal(
new List<string>
{
"<None> -----> Product:1",
"Product:1 ---Category--> Category:1"
},
traversal);

Assert.Equal(2, context.ChangeTracker.Entries().Count());

Expand All @@ -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<string>();

context.ChangeTracker.TrackGraph(product, e =>
{
traversal.Add(NodeString(e));
e.Entry.State = EntityState.Unchanged;
});

Assert.Equal(
new List<string>
{
"<None> -----> Product:1",
"Product:1 ---Details--> ProductDetails:1",
"ProductDetails:1 ---Tag--> ProductDetailsTag:1"
},
traversal);

Assert.Equal(3, context.ChangeTracker.Entries().Count());

Expand All @@ -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<string>();

context.ChangeTracker.TrackGraph(tag, e =>
{
traversal.Add(NodeString(e));
e.Entry.State = EntityState.Unchanged;
});

Assert.Equal(
new List<string>
{
"<None> -----> ProductDetailsTag:1",
"ProductDetailsTag:1 ---Details--> ProductDetails:1",
"ProductDetails:1 ---Product--> Product:1"
},
traversal);

Assert.Equal(3, context.ChangeTracker.Entries().Count());

Expand All @@ -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<string>();

context.ChangeTracker.TrackGraph(details, e =>
{
traversal.Add(NodeString(e));
e.Entry.State = EntityState.Unchanged;
});

Assert.Equal(
new List<string>
{
"<None> -----> ProductDetails:1",
"ProductDetails:1 ---Product--> Product:1",
"ProductDetails:1 ---Tag--> ProductDetailsTag:1"
},
traversal);

Assert.Equal(3, context.ChangeTracker.Entries().Count());

Expand Down Expand Up @@ -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<string>();

context.ChangeTracker.TrackGraph(category, e =>
{
traversal.Add(NodeString(e));
e.Entry.State = EntityState.Modified;
});

Assert.Equal(
new List<string>
{
"<None> -----> Category:1",
"Category:1 ---Products--> Product:1",
"Category:1 ---Products--> Product:3"
},
traversal);

Assert.Equal(4, context.ChangeTracker.Entries().Count());

Expand Down Expand Up @@ -238,8 +339,11 @@ public void Further_graph_traversal_stops_if_an_entity_is_not_attached()
}
};

var traversal = new List<string>();

context.ChangeTracker.TrackGraph(category, e =>
{
traversal.Add(NodeString(e));
var product = e.Entry.Entity as Product;
if ((product == null)
|| (product.Id != 2))
Expand All @@ -248,6 +352,18 @@ public void Further_graph_traversal_stops_if_an_entity_is_not_attached()
}
});

Assert.Equal(
new List<string>
{
"<None> -----> 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);
Expand Down Expand Up @@ -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<string>();

context.ChangeTracker.TrackGraph(details, e => traversal.Add(NodeString(e)));

Assert.Equal(
new List<string>
{
"<None> -----> ProductDetails:1"
},
traversal);

Assert.Equal(0, context.ChangeTracker.Entries().Count(e => e.State != EntityState.Detached));
}
Expand All @@ -291,13 +416,26 @@ public void Can_attach_parent_with_some_new_and_some_existing_entities()
{
KeyValueAttachTest((category, changeTracker) =>
{
var traversal = new List<string>();
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<string>
{
"<None> -----> Category:77",
"Category:77 ---Products--> Product:77",
"Category:77 ---Products--> Product:0",
"Category:77 ---Products--> Product:78"
},
traversal);
});
}

Expand Down

0 comments on commit b01b396

Please sign in to comment.