zzzprojects / GraphDiff

GraphDiff is a library that allows the automatic update of a detached graph using Entity Framework code first.
https://entityframework-graphdiff.net/overview
MIT License
333 stars 101 forks source link

Question / Order of UpdateGraph Calls... #110

Closed jrichmeier closed 10 years ago

jrichmeier commented 10 years ago

In my test application, I have Book and Publisher objects. There is a 0-to-many relationship between Book objects and Publisher objects.

Using the information that you provided me under issue #88, I am interested in updating only the relationship between a Book and Publisher object. Further, based on said information, I should use the AssociatedEntity mapping when call the UpdateGraph method.

If I execute the following code...

Guid publisher1Id;

publisher1Id = Guid.Parse("6C0DA83A-4AEC-4A35-92B2-84FEB911C190");

Publisher publisher1 = null;

publisher1 = context.Publishers.AsNoTracking().Where(e => e.Id == publisher1Id).FirstOrDefault();

Guid book1Id;

book1Id = Guid.Parse("D6E8B0C2-88B0-44D1-8EB9-FD2D7E346957");

Book book1 = null;

book1 = context.Books.AsNoTracking().Where(e => e.Id == book1Id).FirstOrDefault(); book1.PublisherId = publisher1.Id;

context.UpdateGraph(publisher1); context.UpdateGraph(book1, m => m.AssociatedEntity(e => e.Publisher));

context.SaveChanges();

...the expectation is that the publisher id column in the books table in the database will be updated with the id of the publisher, thereby making the association. However, nothing appears to happen. If I reverse the two calls to the UpdateGraph method, then the update occurs as expected. As a side note, I am making the first call to the UpdateGraph method as I do not know whether or not the Publisher object has been previously persisted (which is one thing I took away in the information provided with regards to issue #88). In this very specific example, I know that the publisher has already been persisted but I may not know as I expand this code out.

Can you explain what is happening here?

jrichmeier commented 10 years ago

I have made the following observation...

If I change the line listed in the code above...

book1.PublisherId = publisher1.Id;

...to...

book1.Publisher = publisher1

...then the code listed above works regardless of whether or not publisher1 is new (not previously persisted) or existing (previously persisted).

All that said, I can make things work but I would still like an explanation if there is one to be had. It looks like I might be stuck in the foreign key property mode. I guess if I update the foreign key property of the Book object then there is no need to worry about defining the AssociatedEntity.

ghost commented 10 years ago

Hi,

your problem is related to issue #43, except you're case is a single entity while issue #43 is asking to support this for a collection. Just setting the foreign key properties (ignoring the state of the navigation properties) currently doesn't trigger GraphDiff to do anything. For the mapping to work you have to set the navigation properties, as you already found out. If the navigation property is null GraphDiff currently assumes that you've removed the relation and persists this to the database.

The explanation for the different behavior of the two orders of your UpdateGraph calls is a kind of "magic" the Entity Framework DbContext performs for you when attaching entities to the context: if it knows about a relation (in your case if the foreign key id is set) and both ends of the association are attached, the context populates the navigation properties between them for you.

So if you attach the publisher while the book (with the foreign key id set) is already attached, the context recognizes their relation and populates the navigation properties. As you haven't mapped the relation in the second call, GraphDiff never touches it, leaving the navigation property and foreign key id intact.

But if you do it the other way around, GraphDiff will see that the navigation property on the book is set to null, which triggers GraphDiff to also remove the relation from the attached objects, leaving you with navigation properties and foreign key ids nulled.

I hope my description of the sequence of events is understandable. If not: it might become clear if you inspect the navigation and foreign key properties on all objects attached to the context (e.g. context.Books.Local[0], context.Publishers.Local[0] etc.) while stepping through both call sequences in the debugger.

We're thinking about supporting the "foreign key id only" scenario, but nothing has been implemented yet and I'm not entirely convinced it will be possible to do so (at least not for all cases).

jrichmeier commented 10 years ago

Thank you for the explanation.

Overall, what you described makes sense although it might take a few extra cycles for everything to sink in.

Early on in my Entity Framework adventures I was not a fan of foreign key properties as I thought it indicated a leakage of the data model into the object model. I tried for quite some time to get around not using them but eventually succumbed to the recommendations because they just worked. This is probably why I was a bit confused why things were not working at first.

Again, thank you for the feedback.