Home > Software engineering >  How to handle a Many to Many Relationship in EFCore 3
How to handle a Many to Many Relationship in EFCore 3

Time:12-12

I have moved my database model from an existing .net Framework solution to a Core 3.1 solution. The previous framework used the Database First Approach with the EDMX file for building its EF. EFCore does not have the Database First Approach as an option but their is a scaffolding tool to do the initial build to start. I have done scaffolding but I am getting an error on my many to many relationship. The names of the fields have been changed and properties that do not matter to the issue at hand have been removed. For the sake of this problem a CollegeCourse can have many students and students can have many collegecourses.

Error: Unable to determine the relationship represented by navigation property 'CollegeClass.Students' of type 'ICollection Student'. Either manually configure the relationship, or ignore this property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.


public class CollegeClass
{
       [Key]
       public int Id{get;set;}
       public string CourseName {get;set;}
       [InverseProperty(nameof(Student.CollegeCourses))]
       public virtual ICollection<Student> Students{ get; set; } 
       [InverseProperty(nameof(CollegeCoursesToStudents.CollegeCourse))]
       public virtual ICollection<CollegeCoursesToStudents> CollegeCoursesToStudentss{ get; set; }
}
public class Student
{
        [Key]
        public int Id{get;set;}
        public string StudentName{get;set;}
        [InverseProperty(nameof(CollegeCourse.Students))]
        public virtual ICollection<CollegeCourse> CollegeCourses{ get; set; }
        [InverseProperty(nameof(CollegeCoursesToStudents.Student))]
        public virtual ICollection<CollegeCoursesToStudents> CollegeCoursesToStudentss{ get; set; }
}
public class CollegeCoursesToStudents
{
        [Key]
        public int CollegeCourseId{ get; set; }
        [Key]
        public int StudentId { get; set; }

        [ForeignKey(nameof(CollegeCourseId))]
        [InverseProperty("CollegeCoursesToStudentss")]
        public virtual CollegeCourse CollegeCourse{ get; set; }
        [ForeignKey(nameof(StudentId))]
        [InverseProperty("CollegeCoursesToStudentss")]
        public virtual Student Student { get; set; }
}

In my model building class I have this for the CollegeCoursesToStudents entity

           modelBuilder.Entity<RoutingRuleIdToOrderId>(entity =>
            {
                entity.HasKey(e => new { e.CollegeCourseId, e.StudentId });

                entity.HasOne(d => d.CollegeCourse)
                    .WithMany(p => p.CollegeCoursesToStudents)
                    .HasForeignKey(d => d.CollegeCourseId)
                    .OnDelete(DeleteBehavior.ClientSetNull)
                    .HasConstraintName("FK_CollegeCoursesToStudents_MerchantRoutingRules");

                entity.HasOne(d => d.Student)
                    .WithMany(p => p.CollegeCoursesToStudents)
                    .HasForeignKey(d => d.StudentId)
                    .HasConstraintName("FK_CollegeCoursesToStudents_Students");
            });

I have checked out various links on doing a many to many and the relevant error message. For instance this link outlines a similar problem and solution but does not seem to be working my problem.

https://www.learnentityframeworkcore.com/configuration/many-to-many-relationship-configuration

CodePudding user response:

you have already many-many to many table, so remove this lines from your code. It would be working in net 5 , but you are using 3.1 only.

  [InverseProperty(nameof(Student.CollegeCourses))]
   public virtual ICollection<Student> Students{ get; set; } 

 [InverseProperty(nameof(CollegeCourse.Students))]
        public virtual ICollection<CollegeCourse> CollegeCourses{ get; set; }

and since you are using Data Annotations attributes, you don't need fluent apis. Try to remove them from your db context too

CodePudding user response:

My example. I prefer fluent api instead of annotations.

Step 1. The code

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;

namespace College.Model
{
    public class CollegeClass
    {
        public int Id { get; set; }
        public string Name { get; set; } 

        //Navigation
        public ICollection<Enrollment> Enrollments { get; set; } = null!;
    }
    public class Student
    {
        public int Id { get; set; }
        public string Name { get; set; } 

        //Navigation
        public ICollection<Enrollment> Enrollments { get; set; } 
    }
    public class Enrollment
    {
        public int Id { get; set; }
        public int CollegeClassId { get; set; }
        public int StudentId { get; set; }

        public CollegeClass CollegeClass { get; set; } 
        public Student Student { get; set; } 
    }

    public class CollegeClassConfiguration : IEntityTypeConfiguration<CollegeClass>
    {
        public void Configure(EntityTypeBuilder<CollegeClass> builder)
        {
            builder.Property(e => e.Id).ValueGeneratedOnAdd();
            builder.Property(e => e.Name).HasMaxLength(64).IsUnicode().IsRequired();

            builder.HasKey(e => e.Id);
            builder.HasIndex(e => e.Name).IsUnique();
        }
    }

    public class StudentConfiguration : IEntityTypeConfiguration<Student>
    {
        public void Configure(EntityTypeBuilder<Student> builder)
        {
            builder.Property(e => e.Id).ValueGeneratedOnAdd();
            builder.Property(e => e.Name).HasMaxLength(64).IsUnicode().IsRequired();

            builder.HasKey(e => e.Id);
            builder.HasIndex(e => e.Name).IsUnique();
        }
    }

    public class EnrollmentConfiguration : IEntityTypeConfiguration<Enrollment>
    {
        public void Configure(EntityTypeBuilder<Enrollment> builder)
        {
            builder.Property(e => e.Id).ValueGeneratedOnAdd();
            builder.Property(e => e.CollegeClassId).IsRequired();
            builder.Property(e => e.StudentId).IsRequired();

            builder.HasKey(e => e.Id);
            builder.HasIndex(e => new { e.StudentId, e.CollegeClassId }).IsUnique();

            builder.HasOne(e => e.CollegeClass).WithMany(e => e.Enrollments).HasForeignKey(e => e.CollegeClassId);
            builder.HasOne(e => e.Student).WithMany(e => e.Enrollments).HasForeignKey(e => e.StudentId);
        }
    }

    public class CollegeContext: DbContext
    {
        public DbSet<CollegeClass> CollegeClasses { get; set; } 
        public DbSet<Student> Students { get; set; }
        public DbSet<Enrollment> Enrollments { get; set; }

        public CollegeContext(DbContextOptions<CollegeContext> options) : base(options)
        {
        }

        protected override void OnModelCreating(ModelBuilder builder)
        {
            base.OnModelCreating(builder);
            builder.ApplyConfiguration(new CollegeClassConfiguration());
            builder.ApplyConfiguration(new StudentConfiguration());
            builder.ApplyConfiguration(new EnrollmentConfiguration());
        }
    }
}

Step 2. Migration
In the nuget console type Add-Migration MyMigrationIdentifier

Step 3. Update the database
In the nuget console type Update-Database

Step 4. Check the result in Sql
Using nuget console with command Script-DbContext

CREATE TABLE [CollegeClasses] (
    [Id] int NOT NULL IDENTITY,
    [Name] nvarchar(64) NOT NULL,
    CONSTRAINT [PK_CollegeClasses] PRIMARY KEY ([Id])
);
GO


CREATE TABLE [Students] (
    [Id] int NOT NULL IDENTITY,
    [Name] nvarchar(64) NOT NULL,
    CONSTRAINT [PK_Students] PRIMARY KEY ([Id])
);
GO


CREATE TABLE [Enrollments] (
    [Id] int NOT NULL IDENTITY,
    [CollegeClassId] int NOT NULL,
    [StudentId] int NOT NULL,
    CONSTRAINT [PK_Enrollments] PRIMARY KEY ([Id]),
    CONSTRAINT [FK_Enrollments_CollegeClasses_CollegeClassId] FOREIGN KEY ([CollegeClassId]) REFERENCES [CollegeClasses] ([Id]) ON DELETE CASCADE,
    CONSTRAINT [FK_Enrollments_Students_StudentId] FOREIGN KEY ([StudentId]) REFERENCES [Students] ([Id]) ON DELETE CASCADE
);
GO


CREATE UNIQUE INDEX [IX_CollegeClasses_Name] ON [CollegeClasses] ([Name]);
GO


CREATE INDEX [IX_Enrollments_CollegeClassId] ON [Enrollments] ([CollegeClassId]);
GO


CREATE UNIQUE INDEX [IX_Enrollments_StudentId_CollegeClassId] ON [Enrollments] ([StudentId], [CollegeClassId]);
GO


CREATE UNIQUE INDEX [IX_Students_Name] ON [Students] ([Name]);
GO

References

  • Related