Yuriy N. Yuriy N. - 3 months ago 26
C# Question

Saving many-to-many relationship in Entity Framework Core

For example, I have 3 classes, which I used for many-to-many relationship:

public class Library
{
[Key]
public string LibraryId { get; set; }
public List<Library2Book> Library2Books { get; set; }
}

public class Book
{
[Key]
public string BookId { get; set; }
public List<Library2Book> Library2Books { get; set; }
}

public class Library2Book
{
public string BookId { get; set; }
public Book Book { get; set; }

public string LibraryId { get; set; }
public Library Library { get; set; }
}


They're configured in
ApplicationDbContext
:

protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<CodeableConcept2Coding>().HasKey(k => new { k.LibraryId, k.BookId});
builder.Entity<Library2Book>()
.HasOne(x => x.Library)
.WithMany(x => x.Library2Book)
.HasForeignKey(x => x.LibraryId);
builder.Entity<Library2Book>()
.HasOne(x => x.Book)
.WithMany(x => x.Library2Book)
.HasForeignKey(x => x.BookId);
}


So, I want to add to database some list of
Library2Books
:

var library2Books = new List<Library2Books>(/*some sort of initialization*/);


What entity should I add first?
Books
or maybe
Library
? How can I do this saving?

Answer

This is a simple and a very fundamental question to EF Core many-to-many relationship; I do not know why no one has write a complete example for n..m in EF Core. I have a litte bite modified your code (primary key as Int), I do not like string in primary key. You have just to copy/paste the code and every should working fine.

 > What entity should I add first? Books or maybe Library? How can I do this saving?

The order is not important the important thing here is the data linking. The data must be correctly linked, see the comments between my code lines.

Notes:

  • Many-to-many relationships without an entity class to represent the join table are not yet supported! You must have a join table.

  • Many-to-many relationships is consists of 2 separate one-to-many relationships. = 2x 1:N

    class Program
    {
       public class Library
       {
         [Key]
         public int LibraryId { get; set; }
         public List<Library2Book> Library2Books { get; set; } = new    List<Library2Book>();
       }
    
       public class Book
       {
         [Key]
         public int BookId { get; set; }
         public List<Library2Book> Library2Books { get; set; } = new List<Library2Book>();
       }
    
       public class Library2Book
       {
         [Key]
         public int BookId { get; set; }
         public Book Book { get; set; }
    
         [Key]
         public int LibraryId { get; set; }
         public Library Library { get; set; }
       }
    
       public class MyDbContext : DbContext
       {
         public DbSet<Book> Books { get; set; }
    
         public DbSet<Library> Libraries { get; set; }
    
         protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
         {
           optionsBuilder.UseSqlServer(@"Server=.\;Database=EFTutorial;integrated security=True;");
           base.OnConfiguring(optionsBuilder);
        }
    
         protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
           modelBuilder.Entity<Library2Book>().HasKey(k => new { k.LibraryId, k.BookId });
    
           modelBuilder.Entity<Library2Book>()
               .HasOne(x => x.Book)
               .WithMany(x => x.Library2Books)
               .HasForeignKey(x => x.BookId);
    
           modelBuilder.Entity<Library2Book>()
              .HasOne(x => x.Library)
              .WithMany(x => x.Library2Books)
              .HasForeignKey(x => x.LibraryId);
    
           base.OnModelCreating(modelBuilder);
         }
       }
    
       static void Main(string[] args)
       {
         using (var myDb = new MyDbContext())
        {
          // Create Db
           myDb.Database.EnsureCreated();
    
           // I will add two books to one library
           var book1 = new Book();
           var book2 = new Book();
    
           // I create the library 
           var lib = new Library();
    
           // I create two Library2Book which I need them 
           // To map between the books and the library
           var b2lib1 = new Library2Book();
           var b2lib2 = new Library2Book();
    
           // Mapping the first book to the library.
           b2lib1.Book = book1;
           b2lib2.Library = lib;
    
           // I map the second book to the library.
           b2lib2.Book = book2;
           b2lib2.Library = lib;
    
           // Linking the books (Library2Book table) to the library
           lib.Library2Books.Add(b2lib1);
           lib.Library2Books.Add(b2lib2);
    
           // Adding the data to the DbContext.
           myDb.Libraries.Add(lib);
    
           myDb.Books.Add(book1);
           myDb.Books.Add(book2);
    
           // Save the changes and everything should be working!
           myDb.SaveChanges();
         }
       }
    }
    

Results

Tables:   Books    |   Libraries      |    Library2Book  |
           1       |      1           |      1   |   1   |
           2       |      -           |      1   |   2   |

Edit from author of the question

When you're trying to insert a lot of entities (I've tried approximately 300, it was enough), you'll have the same key has been already added error. You should split your inserting collection into small parts, e.g. 100 entities will be enough.

public async Task SaveEntities(IEnumerable<Library2Book> library2Books)
        {
                int i = 0;
                foreach (var library2Book in library2Books)
                {
                    _dbContext.Set<Library>().Add(codConc2Coding.Library);
                    _dbContext.Set<Book>().Add(codConc2Coding.Book);
                    _dbContext.Set<Library2Book>().Add(library2Book);
                    i++;
                    if (i == 99)
                    {
                        await _dbContext.SaveChangesAsync();
                        i = 0;
                    }
                }
                await _dbContext.SaveChangesAsync();
}