45

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?

Yurii N.
  • 5,455
  • 12
  • 42
  • 66

1 Answers1

79

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();
}
Community
  • 1
  • 1
Bassam Alugili
  • 16,345
  • 7
  • 52
  • 70
  • 1
    Thanks for the asnwer! But I have question. to clarify some things. So, as you say, if order isn't important, if I have `var library2Books = new List()`, I can add it to database like this: `myDb.Libraries.Add(library2Books.Select(x => x.Library))`, then `myDb.Books.Add(library2Books.Select(x => x.Book))`, finally `myDb.Library2Books.Add(library2Books)`? – Yurii N. Aug 12 '16 at 11:47
  • @YuriyN. No this will not work you will get an converting you need for that at least AddRange and not Add – Bassam Alugili Aug 12 '16 at 13:22
  • Sure, mistake, we need to use `AddRange`, but I can't understand exactly, why it doesn't work. – Yurii N. Aug 12 '16 at 13:34
  • @YuriyN. I think the problem that the library2Books.It is not correctly linked or mapped to the related entities.That is exatcly what I'm trying to tell you with my answer. Can you please dump the object tree of your entities added/modified entities. This is a good way which allow me to see what is missing or what is exactly the problem in between your entities. – Bassam Alugili Aug 12 '16 at 13:40
  • But the `library2Books` already contains all, what we need to insert in database. Anyway, I've found that the problem appears, when I was trying to insert a lot of books and libraries, about 300, inserting falls down with an error _same key has been already added_, so the problem is in bulk insert with huge amount of entites. – Yurii N. Aug 12 '16 at 13:54
  • @YuriyN. I have now a problem by loading them? can you load a libaray.Library2Books ? with it related books? if yes how did you that? – Bassam Alugili Aug 12 '16 at 14:24
  • @YuriyN. I have accepted your changes, just update your changes and i will approve it. I had the same problem with key constraint and when i have linked the object corretly everything has been worked fine! I have open an issues for this qeustion in GitHub because I think the loading is not working correctly this is a bug in EF Core https://github.com/aspnet/EntityFramework/issues/6309 – Bassam Alugili Aug 12 '16 at 14:44
  • Ok, we will see how it would considered. Hope, they'll solve this problem! – Yurii N. Aug 12 '16 at 14:46
  • 1
    Looks like the issue might be a problem with trying to save asynchronously, or the fact that you're using some sort of sequential key, if you use GUID you might get away with being able to save asynchronously _AND_ not get the duplicate key error – gimiarn1801 Nov 12 '17 at 15:06
  • I'm experiencing this same issue, and dropping my batch size down to 100 doesn't seem to resolve it. I am, however, wrapping all of my SaveChangesAsync() in a manually controlled transaction, which may be contributing. https://github.com/collinbarrett/FilterLists/issues/343 – Collin Barrett Aug 16 '18 at 12:53
  • EF Core 5 has now the possibility of Many-to-Many relationship without explicitly mapping the join table. – Mohammad Farahi May 16 '21 at 14:56