Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using InboundNavigation in TrackGraph #7308

Closed
tonysneed opened this issue Dec 23, 2016 · 5 comments
Closed

Using InboundNavigation in TrackGraph #7308

tonysneed opened this issue Dec 23, 2016 · 5 comments
Labels
closed-no-further-action The issue is closed and no further action is planned.

Comments

@tonysneed
Copy link

Using TrackGraph, I would like to be able to set the state of an entity to Added if it is a child in a one-to-many relationship and the parent entity state is Added. For example, if an Order is in an Added state, then I would like to be able to set the OrderDetail to Added as well.

I notice that EntityEntryGraphNode has an InboundNavigation property. Is it possible to use it to find the relationship type and a pointer to the entity on the other side of the relationship?

@tonysneed
Copy link
Author

tonysneed commented Dec 24, 2016

I thought I found the answer to the first part of my question, which is how to determine if the current node is on the many side of a one-to-many relationship:

context.ChangeTracker.TrackGraph(item, e =>
{
    var isOneToMany = e.InboundNavigation?.ForeignKey.PrincipalToDependent?.IsCollection();
});

But this returns true for ManyToOne relationships as well, for example, Order to Customer.

And I'm still at a loss as to how I could get a reference to the parent on the other side of InboundNavigation. There is a GetPrincipal method that looks interesting:

var stateManager = e.GetInfrastructure().StateManager;
var internalEntry = stateManager.TryGetEntry(e.Entry.Entity);
var parent = stateManager.GetPrincipal(internalEntry, e.InboundNavigation?.ForeignKey)?.Entity;

But GetPrincipal is for internal use, so I'm wondering if there is a better way to get a reference to a related entity via ForeignKey?

@divega divega added this to the 2.0.0 milestone Dec 28, 2016
@ajcvickers
Copy link
Member

@tonysneed Inbound navigation is the navigation that was followed to discover this entity in the graph. It does not necessarily point to the principal of the entity. For example, consider the following model:

public class Blog
{
    public int Id { get; set; }

    public ICollection<Post> Posts { get; set; }
}

public class Post
{
    public int Id { get; set; }

    public Blog Blog { get; set; }
}

Now let's track this graph:

var post = new Post();
var blog = new Blog { Posts = new List<Post> { post } };
post.Blog = blog;

If we call TrackGraph passing in the blog object, then there will be two callbacks:

  • First for blog, with InboundNavigation set to null
  • Second for post, with InboundNavigation set to Blog.Post

But if we call TrackGraph passing in the post, then we get:

  • First for post, with InboundNavigation set to null
  • Second for blog, with InboundNavigation set to Blog.Posts

Note that in the first case when we get the callback for post (the dependent), the blog (the principal) has already been visited and is therefore being tracked. However, in the second case, the callback for post happens before the blog has been discovered. This indicates a problem with what you are trying to do--depending on how entities are discovered, the principal for a relationship may not yet be tracked, and so code to check if it is Added will likely not work.

If we assume that you have constrained uses of TrackGraph so that the principals are always visited first--for example, a single relationship with TrackGraph always called on the principal, then you could do something like this:

context.ChangeTracker.TrackGraph(blog, e =>
{
    if (e.InboundNavigation != null)
    {
        var referenceToPrincipal = e.InboundNavigation.FindInverse();

        Debug.Assert(referenceToPrincipal.IsDependentToPrincipal());

        var principalEntry = e.Entry.Context.Entry(e.Entry.Reference(referenceToPrincipal.Name).CurrentValue);

        // Handle dependent
        e.Entry.State = principalEntry.State == EntityState.Added ? EntityState.Added : EntityState.Unchanged;
    }
    else
    {
        // Handle principal
        e.Entry.State = e.Entry.IsKeySet ? EntityState.Unchanged : EntityState.Added;
    }
});

@ajcvickers ajcvickers added closed-no-further-action The issue is closed and no further action is planned. and removed type-investigation labels Jan 3, 2017
@tonysneed
Copy link
Author

@ajcvickers Thanks, this helps a lot. With e.InboundNavigation.FindInverse() and referenceToPrincipal.IsDependentToPrincipal() I'm able to determine if the node is on the child-side of a one-to-many relationship.

I am also able to read the reference property of the node with e.Entry.Reference(referenceToPrincipal.Name).CurrentValue. However, it is possible that the reference property is null, because the relationship is defined with foreign key values, for example, if Post has a BlogId foreign key property. In this case, how would it be possible for me to obtain the value of Post.BlogId, then use it to obtain a reference to the related Blog entity that is being tracked?

@ajcvickers
Copy link
Member

@tonysneed Something like this:

context.ChangeTracker.TrackGraph(blog, e =>
{
    if (e.InboundNavigation != null)
    {
        Debug.Assert(!e.InboundNavigation.IsDependentToPrincipal());

        var dependentEntry = e.Entry;
        var foreignKey = e.InboundNavigation.ForeignKey;

        var keyValues = foreignKey.Properties
            .Select(p => dependentEntry.Property(p.Name).CurrentValue)
            .ToArray();

        var principalEntry =
            dependentEntry.Context.Entry(
                dependentEntry.Context.Find(foreignKey.PrincipalEntityType.ClrType, keyValues));

        // Handle dependent
        dependentEntry.State = principalEntry.State == EntityState.Added
            ? EntityState.Added
            : EntityState.Unchanged;
    }
    else
    {
        // Handle principal
        e.Entry.State = e.Entry.IsKeySet ? EntityState.Unchanged : EntityState.Added;
    }
});

But that only works if the FK is mapped and an the property values are set before calling Add. See #7389.

@tonysneed
Copy link
Author

@ajcvickers Thanks, this is helpful. However, I will need access to the referencing entity, as proposed in #7389, because the FK and parent entity may not have the key set because it is an auto-generated identity column.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
closed-no-further-action The issue is closed and no further action is planned.
Projects
None yet
Development

No branches or pull requests

3 participants