39
votes

For example, I have 3 classes, which I'm using 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?

1

1 Answers

70
votes

This is a simple and a very fundamental question to EF Core many-to-many relationship; I do not know why no one has written a complete example for n..m in EF Core.

I have modified your code (primary key as int), I do not like string in primary key. Just copy/paste the code and every should work 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 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.
           // Changed b2lib2.Library to b2lib1.Library
           b2lib1.Book = book1;
           b2lib1.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), you'll have the same key has been already added error. You should split your inserting collection into small parts, e.g. 100 entities should 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();
}