I’m using EF Code First with the following configuration
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<UserProfile>()
.HasMany(x => x.TeamLeaders)
.WithMany()
.Map(m => m.MapLeftKey("UserId")
.MapRightKey("TeamLeaderId")
.ToTable("UserTeamLeaders"));
}
TeamLeaders is an ICollection of Users i.e. it is a self referencing many-many relationship.
In plain English, a user can have more than one team leader. This configuration appears to be correct, as it creates the duel FK/PK link table as I would expect.
I have an MVC4 application that allows editing, adding and removal of team leaders from the collection.
In my controller, I originally had the following:
var original = context.UserProfiles
.Include("TeamLeaders")
.Single(x => x.UserId == model.UserId);
context.Entry(original).CurrentValues.SetValues(model);
However, that last line was failing to mark the TeamLeaders collection as updated, and when I called SaveChanges(), no changes were recorded.
Instead I wrote a simple CopyProperties method on my User class, which uses reflection to manually copy the properties across, so in my controller I now have:
var original = context.UserProfiles
.Include("TeamLeaders")
.Single(x => x.UserId == model.UserId);
//context.Entry(original).CurrentValues.SetValues(model);
original.CopyProperties(model);
However, this goes too far, and SaveChanges attempts to add a new user to the system matching the profile of the selected team leader.
Can anyone advise as to which part of this I am doing wrong? I am not sure whether I need to update the mappings, or change the way I copy properties from the view model to the model
You must modify the loaded
TeamLeaderscollection in theoriginaluser according to your changes so that change detection can recognize which leaders have been removed and which have been added. When you callSaveChangesthen EF will write the appropriate DELETE and INSERT statements for the join table based on the detected changes. The simplest way would look like this:Findwill fetch the team leaders from the context if they are already loaded. If they aren’t loaded it will query the database.If you want to avoid additional queries you can attach the team leaders manually to the context:
Except the first query to load the
originalhere is no further database query involved.BTW: You should be able to use the strongly typed
Includeversion with EF Code First:You just need
using System.Data.Entity;in your code file to get access to this version.