Many to Many Relationship Entity Framework Core
Working with a many to many relationship in Entity Framework Core has always been one of the harder things for developers new to Entity Framework. This is going to be a short post detailing how to create them and configure the relationship. We’ll also discuss common scenarios when working with them.
Create the Entities
Start off by creating the entities that will have a many to many relationship. In our case we’re going to create a Teacher and a Classroom.
public class Teacher { public int Id { get; set; } public string Email { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public virtual ICollection<TeacherClassroom> TeacherClassrooms { get; set; } } public class Classroom { public int Id { get; set; } public string Name { get; set; } public virtual ICollection<TeacherClassroom> TeacherClassrooms { get; set; } }
You’ll notice in these two classes that I have created a list of TeacherClassroom. This is the navigation property to our linking entity. The TeacherClassroom is fairly simple and just contains the Ids of Teacher and Classroom.
public class TeacherClassroom { public int ClassroomId { get; set; } public int TeacherId { get; set; } public virtual Classroom Classroom { get; set; } public virtual Teacher Teacher { get; set; } }
So, we have a Teacher, a Classroom, and a TeacherClassroom that links the two. So far so good.
Configuring the Relationships
The next thing we need to do is to configure the relationships. One of the most common issues you will run into when trying to save a many to many relationship is an FK constraint error. If you see this in your logs it is more than likely due to an improperly configured relationship.
builder.Entity()<Teacher>.HasMany(m => m.TeacherClassrooms) .WithOne(m => m.Teacher).HasForeignKey(k => k.TeacherId); builder.Entity()<Classroom>.HasMany(m => m.TeacherClassrooms) .WithOne(m => m.Classroom).HasForeignKey(k => k.ClassroomId); builder.Entity()<TeacherClassroom>.HasOne(m => m.Classroom) .WithMany(m => m.TeacherClassrooms).HasForeignKey(k => k.ClassroomId); builder.Entity()<TeacherClassroom>.HasOne(m => m.Teacher) .WithMany(m => m.TeacherClassrooms).HasForeignKey(k => k.TeacherId);
In the above code we are starting with the Teacher entity and configuring the relationship using the navigation properties. A Teacher and Classroom have many TeacherClassroom and the linking entity in turn has one of each. The Foreign keys for this relationship reside in the linking entity so when we call HasForeignKey that is the entity we are referencing. Finally, we must do the same thing for the TeacherClassroom and its navigation properties to complete the configuration.
Working with Many to Many Relationships
Now, once everything is configured and set up we can query and save our entities in this relationship. There are a number of different ways you can interact with them. To see a more detailed analysis on performance tuning Entity Framework Core see my post Maximizing Entity Framework Core Performance.
If you want to work directly with the TeacherClassroom linking entity you can do the following:
await dbContext.TeacherClassroom.Include(m => m.Teacher) .Include(m => m.Classroom).ToListAsync(); dbContext.TeacherClassroom.Add(new TeacherClassroom { TeacherId = teacher.Id, ClassroomId = classroom.Id }); await dbContext.SaveChangesAsync();
The first part will pull back the TeacherClassroom and its linked entities in the many to many relationship while the second part will directly save a new linking entity using the keys of an existing Teacher and Classroom. You may also create a new Teacher, Classroom, and linking entity all at once.
var teacher = new Teacher { FirstName = "Test", TeacherClassrooms = new List { new TeacherClassroom { Classroom = new Classroom { Name = "Classroom Name" } } } }; dbContext.Teacher.Add(teacher); dbContext.SaveChanges();
Notice that we didn’t have to set the Ids of anything. Because we have created everything as a child of Teacher using its navigation properties, Entity Framework will automatically create our entities with the proper relationships. Not only that, it will do it as all part of one transaction for us. A common mistake is to also try to set the Teacher property within TeacherClassroom which will cause you problems. If you want to save a new Classroom with a new Teacher, you can do the reverse of this and you would instead create the Teacher in TeacherClassroom instead of Classroom. Once again, if you get FK constraint errors then take a look at your configuration and make sure you configured both sides of the relationship properly.
David
February 10, 2020Thank you! It helped me a lot!
And as a note to the readers, In my case the ‘magic’ happened in the last code block the one where you create Teacher, Classroom, and linking entity all at once. This solved all the issues on insert. You actually don’t need to fully configure the relationship in OnModelCreating, nor you need to create list navigation properties. As long as you use the EF Core naming conventions in the glue table you are good to go, like this:
public class Entity1Entity2 {
public Entity1 Entity1 { get; set; }
public int Entity1Id { get; set; }
public Entity2 Entity2{ get; set; }
public int Entity2Id { get; set; }
}