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 102 forks source link

Updating a many-to-many relation using GraphDiff cause an error #96

Closed msanaei closed 9 years ago

msanaei commented 9 years ago

I'm using EF code first 6.1 with .NET 4 in my application and have following classes in my model(I cut other unrelated parts of diagram. e.g Permission has other navigations): enter image description here

My business logic works with detached entities so I'm using RefactorThis.GraphDiff 2.0.1.0 to perform updates. I want to update an applicationUserInApplication object, so I get an existing applicationUserInApplication with its SecurityRoles from database and return it as a View-Model then update it and map back it to applicationUserInApplication using Automapper (in update operation, I only change SecurityRoles collection of an applicationUserInApplication , these SecurityRoles saved before and I only select them), so I defined following configuration:

_dbContext.UpdateGraph(appUserInApplication, map => map
                .OwnedCollection(t => t.SecurityRoles, with=>
                                 with.AssociatedCollection(t=>t.Permissions)
                                     .AssociatedEntity(t => t.ApplicationDescriptor))
                .AssociatedEntity(t=>t.ApplicationDescriptor)
                .AssociatedEntity(t=>t.AppUser)
                .AssociatedEntity(t=>t.UserProfile));

and defined following mapping for AppUserInApplication in AppUserInApplication_Mapping class:

this.HasRequired(t => t.AppUser).WithMany(t => t.AppUserInApplications)
                                                .HasForeignKey(d => d.AppUserId);
this.HasRequired(t => t.Applicationdescriptor).WithMany(t => t.AppUserInApplications)
                                              .HasForeignKey(d => d.ApplicationId);
this.HasMany(t => t.SecurityRoles).WithMany(t => t.AppUserInApplications)
            .Map(m =>
            {
                m.ToTable("AppUserInApplicationSecurityRole");
                m.MapLeftKey("AppUserInApplications_Id");
                m.MapRightKey("SecurityRoles_Id");
            });
this.HasRequired(t => t.UserProfile).WithMany().HasForeignKey(t=>t.UserProfileId);

After calling above UpdateGraph(), when I call _dbContext.SaveChange(); I get following error:

An unhandled exception of type 'System.InvalidOperationException' occurred in EntityFramework.dll Additional information: The operation failed: The relationship could not be changed because one or more of the foreign-key properties is non-nullable. When a change is made to a relationship, the related foreign-key property is set to a null value. If the foreign-key does not support null values, a new relationship must be defined, the foreign-key property must be assigned another non-null value, or the unrelated object must be deleted.

[Updated]

I also, tried following mapping

_dbContext.UpdateGraph(appUserInApplication, map => map
           .AssociatedCollection(t => t.SecurityRoles)
           .AssociatedEntity(t => t.Application)
           .AssociatedEntity(t => t.UserProfile)
           .AssociatedEntity(t => t.AppUser);

But I get same error, yet.

Does anyone know where is the problem?

ghost commented 9 years ago

Hi,

I can't tell really tell what's wrong after having a cursory glance over your description. If I had to speculate I'd say that one of the associated entities has navigation properties that are null after calling UpdateGraph but the corresponding foreign key property is of a type that cannot be null (e.g. int). Could you please inspect the object that UpdateGraph returns in a debugger and check if that's the case?

var inspectMe = _dbContext.UpdateGraph(..)

If that's not the problem please provide a minimal reproduction of your problem so I can debug it (for example in a gist).

msanaei commented 9 years ago

Thank you @andypelzer , I uploaded a simplified version of my model, you can get it from https://www.dropbox.com/s/i9dvrb6ebd5wo7h/GraphdiffTest.rar?dl=0

ghost commented 9 years ago

Hi,

the "Sequence contains no element" exception in your sample is caused by mapping a collection as an associated entity:

image

After fixing that I also see the InvalidOperationException you originally reported. It simply means that Entity Framework is complaining about navigation properties you've mapped as required but not set (they're null). Have a look at the new AppUserInApplication attached to the context:

image

Entity Framework requires you to not only to set ids but also the navigation properties. If you change your code to this everything works as expected:

image

Hope that helps!

As far as the first exception is concerned I've opened a separate issue (#100) to improve the error message.

msanaei commented 9 years ago

Thank you @andypelzer, The error fixed but now when I run the program, it delete Role1 , Role2 and insert Role3: image

But I expect only Role3 add to other roles.

ghost commented 9 years ago

Hi,

yes, because that's what you're asking it to do. GraphDiff can't monitor what you do to the in-memory object graph, it can just compare the state of that graph (when calling UpdateGraph) to the database. So if you provide it with a list containing just a single element it has to assume that's what you want to see in your database and it tries to recreate that state there. If you want to assign all three security roles instead, you have to add all those roles to appuserinapp.SecurityRoles.

msanaei commented 9 years ago

Thank you @andypelzer again, I asked this question in StackOverflow with bounty worth +100, too. If you want, you can post answer to my question ;)

ghost commented 9 years ago

Thanks, I have.