Many to Many Relationship in EF Core

By: Chris Dunn

A many-to-many relationship is one where you have two entities and entity x can have one or more of entity y, and entity y can have one or more of entity x.  Examples of this relationship are Products <-> Orders, BlogPosts <-> Tags, Albums <-> Artists, and in this post Courses <-> Students.

In our example let's pretend we're writing course scheduling software for a University.  In that scenario, we might have two models for our Entities, Course and Student declared as follows.

   public class Course
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public string Description { get; set; }
    }
	
	
    public class Student
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }

    }

Now that we have our Entities defined, we need to have a way to relate them to each other. Unfortunately we can't just define a navigation property on each entity, declared as an ICollection pointing to the other entity. I know we are able to follow that pattern using other ORMs and even other versions of Entity Framework.  Currently, as of this writing, that is not the case with EF Core.

We have to remember that under the hood, Entity Framework is doing a lot of not only functional work, but structural work on the data model. After all, the POCO models still need to map to database tables. In the cases where we simply define navigation properties, Entity Framework is still creating shadow models to represent join tables.

Since Entity Framework Core has not yet included this nicety for Many-to-Many, we need to create the Join Entity ourselves, so the underlying data table is created. Fortunately, that's not too difficult to do.

Here is our Join Entity class called CourseStudent which provides the navigation properties for each Entity we're going to relate in the many-to-many relationship.

	public class CourseStudent
    {
        public int CourseId { get; set; }
        public Course Course { get; set; }
        public int StudentId { get; set; }
        public Student Student { get; set; }

    }

We then need to go back and add a navigation property to our Course and Student Entities to reference the CourseStudent join Entity (public virtual ICollection CourseStudents { get; set; }).

    public class Course
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public string Description { get; set; }
        public virtual ICollection CourseStudents { get; set; }
    }
	
	
    public class Student
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public virtual ICollection CourseStudents { get; set; }

    }

After our entities are defined (all three), we need to describe the relationships to each other so that Entity Framework knows how to join the entities. We do this in our DbContext class, specifically the OnModelCreating method using the FLUENT API. I've included the entire DbContext class for reference.

public class DataContext:DbContext
    {
        public DataContext(DbContextOptions options): base(options){}

        public DbSet Students { get; set; }
        public DbSet Courses { get; set; }
        public DbSet CourseStudents { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);

            modelBuilder.Entity<CourseStudent>
                    .HasKey(p => new { p.CourseId, p.StudentId });

            modelBuilder.Entity<CourseStudent>
                .HasOne(p => p.Course)
                .WithMany(p => p.CourseStudents)
                .HasForeignKey(p => p.CourseId);

            modelBuilder.Entity<CourseStudent>
                .HasOne(p => p.Student)
                .WithMany(p => p.CourseStudents)
                .HasForeignKey(p => p.StudentId);
            

        }


    }

In the code above we're first defining a composite key for CourseStudent entity using the course id and student id. Second, we are defining that CourseStudent has one Course which has many Students based on the key of courseid. Lastly, we are defining that CourseStudent has one Student with many Courses based on the key studentid.

I imagine at some point the Entity Framework Core team will automatically create the join tables/entities for us, but until that time the following will get you the same or close enough functionality.

Tags: c# entity framework

Copyright 2023 Cidean, LLC. All rights reserved.

Proudly running Umbraco 7. This site is responsive with the help of Foundation 5.