Search code examples
c#entity-frameworkef-code-firstmany-to-manyone-to-many

Many-to-Many & One-to-Many (EF) - Code First


Creating a code-first database, for a school-type system.

The bit I'm struggling with is the courses, and users section.

User

  • Can take many courses
  • Can teach multiple courses

Course

  • Can have many users
  • Can have only 1 teacher

Below are the 2 models for my courses, and users

public class User
{
  public virtual ICollection<Course> CoursesRunning { get; set; }

  public virtual ICollection<Course> CoursesTaking { get; set; }

  [DataType(DataType.Date)]
  [DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:yyyy-MM-dd}")]
  [Required]
  public DateTime DoB { get; set; }

  [Required]
  public string EmailAddress { get; set; }

  [Required]
  public string FirstName { get; set; }

  [Required]
  public string LastName { get; set; }

  [Required]
  public string Password { get; set; }

  [Key]
  public int UserId { get; set; }

  public User()
  {
    CoursesRunning = new HashSet<Course>();
    CoursesTaking = new HashSet<Course>();
  }
}

public class Course
{
  [Key]
  public int CourseId { get; set; }

  /// <summary>
  /// The name of the course
  /// </summary>
  public string Name { get; set; }

  /// <summary>
  /// The owner of the course
  /// </summary>
  public virtual User User { get; set; }

  /// <summary>
  /// The users on the course
  /// </summary>
  public virtual ICollection<User> Users { get; set; }

  public Course()
  {
    Users = new HashSet<User>();
  }
}

As you can see I've got my 2 collections in my User class, and then my single and many in the Course class.

I think I have to set a foreign key attribute on my virtual properties, but I'm really unsure.

Tried searching for the last few hours, but can't see anything that fits this need.

Thank you


Solution

  • All you need is to map the corresponding navigation properties that define each relationship.

    If you prefer data annotations, you could use InverseProperty attribute like

    public class User
    {
        [InverseProperty(nameof(Course.User))]
        public virtual ICollection<Course> CoursesRunning { get; set; }
    
        [InverseProperty(nameof(Course.Users))]
        public virtual ICollection<Course> CoursesTaking { get; set; }
    }
    

    or

    public class Course
    {
        [InverseProperty(nameof(User.CoursesRunning))]
        public virtual User User { get; set; }
    
        [InverseProperty(nameof(User.CoursesTaking))]
        public virtual ICollection<User> Users { get; set; }
    }
    

    etc.

    Or you can use fluent configuration instead (my preferred):

    modelBuilder.Entity<User>()
        .HasMany(e => e.CoursesRunning)
        .WithRequired(e => e.User)
        .WillCascadeOnDelete(false);
    
    modelBuilder.Entity<User>()
        .HasMany(e => e.CoursesTaking)
        .WithMany(e => e.Users);
    

    Note: in the above samples, you might need to exchange the Running and Taking mappings if what I did is not what you meant.

    Update: I almost always forget that having more than one relationship between the two entities is causing the famous multiple cascade paths error. So you need to turn the cascade delete off, and it can be done only with fluent API, hence data annotations is not an option.