Entity Framework Many to Many Relationship
Posted on: 2015-11-23
The theory for many to many relationship is simple: you map two entities with key that you define on each side and Entity Framework generate the table that you have assigned the name with. Something like the code below works perfectly. The problem occurs when you want to save entities later.
this.HasMany(d => d.Moderators).WithMany(d => d.ContestsUserModerate).Map(ul=> { ul.MapRightKey("ApplicationUserModeratorId"); ul.MapLeftKey("ContestId"); ul.ToTable("ContestModerators", Constants.SchemaNames.User); });
The problem is is you save the entity on which you configured the many to many relationship than you might get problem with Entity Framework trying to insert the two entities you are trying to maps. The problem is that if you have entity A that try to have B than A.B is having the entity and not a foreign key attribute. The same is true for B.A which is having a collection of A. In both case, it's a collection of entities. The problem is bigger than just trying to insert those entity cause you could just write in your save method a code to mark those collection's entities the status to UnChanged. But, the problem is that if you have rich entities than this one will contains other entities. So you have to go through all of its entities and handle all states of each properties or nullify all properties and use the foreign key properties. This is simply not viable for big classes.
The solution is to forget about the Map method of Entity Framework and to create your own association entity.
For example, the previous code we were having an Entity called Contest that try to get a many to many to ApplicationUser. So, you create a ContestApplicationUser class. Than, you have in both entities (Contest and ApplicationUser) a collection of the new class you just created : ContestApplicationUser. You need to create a configuration for ContestApplicationUser which will define the primary key which should be the primary key of both entities. This can be done by specifying in an anonymous class both primary key of your classes. Finally, you need to specify that both properties are required in the class and that this one has a relationship to the specific entity they both represent.
public class ContestApplicationUserConfiguration : AssociationConfiguration<ContestApplicationUser> { public ContestApplicationUserConfiguration() : base(Constants.SchemaNames.Contest) { this.HasKey(c => new { UserId = c.UserId, ContestId = c.ContestId }); this.HasRequired(d => d.Contest).WithMany(d => d.Users).HasForeignKey(d => d.ContestId); this.HasRequired(d => d.User).WithMany(d => d.Contests).HasForeignKey(d => d.UserId); } }
The AssociationConfiguration class is a class I created that simply an helper that create the table name with the type of the entity.
public abstract class AssociationConfiguration<T> : EntityTypeConfiguration<T> where T : class { protected AssociationConfiguration(string schemaName) { ToTable(typeof(T).Name, schemaName); } }
From here, it's very simple to save any new associations. You just need to instantiate the class, ContestApplicationUser and you just fill up the two properties for the foreign keys. On the other way around, when you load the entities with the collection you will include the collection and the other side.
.Include(d => d.Users) .Include(d => d.Users.Select(f=>f.User))
No more hassle when saving and still the full ledge entity when loading. One caveat of this solution is that you cannot insert new association before having both of the entity already inserted.