Search code examples
c#entity-frameworkentity-framework-6graphdiff

The relationship could not be changed because one or more of the foreign-key properties is non-nullable on saving


I use GraphDiff to update detached object graphs, and I'm getting the above exception when saving a parent and its children.

The models and mapping are:

public class Group
{
    public int Id { get; set; }
    public virtual ICollection<GroupUser> Users{ get; set; }
}

public class GroupUser
{
    public int Id { get; set; }
    public int GroupId { get; set; }
    public int UserId { get; set; }
    public virtual User User { get; set; }
}

public class GroupMap : EntityTypeConfiguration<Group>
{
    public GroupMap()
    {
        this.ToTable("groups");
        this.HasKey(t => t.Id).Property(t => t.Id).HasColumnName("id").HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);

        this.HasMany(t => t.Users)
            .WithOptional()
            .HasForeignKey(d => d.GroupId)
            .WillCascadeOnDelete();
    }
}

public class GroupUserMap : EntityTypeConfiguration<GroupUser>
{
    public GroupUserMap ()
    {
        this.ToTable("groups_users");
        this.HasKey(t => t.Id).Property(t => t.Id).HasColumnName("id").HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
        this.Property(t => t.GroupId).HasColumnName("group_id").IsRequired();
        this.Property(t => t.UserId).HasColumnName("user_id").IsRequired();

        this.HasRequired(t => t.User)
            .WithMany()
            .HasForeignKey(d => d.UserId)
            .WillCascadeOnDelete(false);
    }
}

For inserts and updates I have this following method in my repository:

public override Group Update(Group entity)
{
    if (entity.Users != null)
    {
        entity.Users.ToList()
                    .ForEach(x =>
                    {
                        x.GroupId = x.Id == 0 ? 0 : entity.Id;
                        x.UserId = x.User.Id;
                    });
    }

    return dbContext.UpdateGraph<Group>
        (entity,
            map => map.OwnedCollection(x => x.Users, with => with.AssociatedEntity(c => c.Users))
        );
}

I do the following:

  1. Add a new group, save and reload
  2. Add a user to the group, save and reload
  3. Add a second user, save and reload
  4. Add a third user, try to save -> exception is thrown

Whilst debugging, the state of the user entities being added is always detached, and both GroupId and Id are set to 0 for new entities. The first 2 users added are saved successfully. However, on the third one, the exception is thrown.

If, I'm always using the same method for saving, why does it not always work? Is it an EF or GraphDiff issue and is there a way to solve this problem?

Most questions and related answers here on SO and elsewhere all deal with the situation where a child entity is being deleted. This is not the case in my particular scenario.


Solution

  • Probably is related to UserId not nullable. As you know, UserId is used by EF to write User. If User is null (it can) EF cannot write the entity. Also it could be related to User.GroupId that is used by EF to map the Group.Users collection.

    Also, I can't understand your model. A User have one and only one Group. A Group can have more then one User.

    If this is true, why don't you just simplify your model removing the association entity? EF does not need it...

    public class Group
    {
        public int Id { get; set; }
        public virtual ICollection<User> Users{ get; set; }
    }
    
    public class User
    {
        public int Id { get; set; }
        public virtual Group Group { get; set; }
    }
    

    In this case you need just UserMap.

    public class UserMap : EntityTypeConfiguration<User>
    {
        public UserMap()
        {
            // You don't need to set primary key. EF take Id and create an identity column
            //HasKey(t => t.Id); 
    
            // Table & Column Mappings
            ToTable("users");
            // No column mappings in this case.
    
            // Relationships
            HasRequired(t => t.Group)
                .WithMany(t => t.Users)
                .Map(d => d.MapKey("user_group"));
    
        }
    }
    

    If a User can have more than one Group you can just modify your user model in this way

    public class User
    {
        public int Id { get; set; }
        public virtual ICollection<Group> Groups { get; set; }
    }
    

    EF understands that is a many to many relationship, will create the association table (UsersGroups) but you don't need the association entity.

    EDIT

    Rarely you need to set db.Entry(myEntity).State = EntityState.Modified; with EF6. Proxies works quite good.