简体   繁体   中英

What is the correct way to do many to many entity relation update in Entity framework core 6?

 public class Book
 {
     public int Id { get; set; }
     public string Name{ get; set; }
     public string ISBN { get; set; }
    
     public ICollection<Category> Categories { get; set; }
 }
    
 public class Category
 {
     public int Id { get; set; }
     public string Name{ get; set; }
     public ICollection<Book> Books { get; set; }
 }

I a using Entity Framework core 6 with .NET 6. I am trying to update the Categories of a specific Book.

For example, If one book has categories like .NET , C# then I want to update categories into .NET , EF Core , SqlServer , I think you get it now.

Do I need to add a Join entity for only the Update operation? As you can see I have not created any Join entity like BookCategories though I managed to Insert categories while creating Book for the first time. But when trying to update the book with new categories I am getting two issues.

  1. The old category is not deleted.
  2. And getting Duplicate Error Key while trying to update with existing category, in this case, .NET .

Please kindly show the proper way of updating related entities in Entity Framework Core 6 in .NET6.

Many-to-Many relationships need a bit of configuration depending on what you want out of the relationship. If you just want the linking table to manage the link and nothing else:

[BookCategories]
BookId (PK, FK)
CategoryId (PK, FK)

Then you can set up the relationship to either use an entity definition or a shadow entity. In both cases this is typically preferable since your Book can have a collection of Categories, and the Category can have a collection of books. With Code-First and Migrations I believe EF can and will set up this linking table automatically. Otherwise you can use OnModelCreating or an EntityTypeConfiguration to configure what Table and Columns to use for the relationship.

This can be done either with an Entity declared for BookCategory, or without one:

With entity:

modelBuilder.Entity<Book>()
   .HasMany(x => x.Categories)
   .WithMany(x => Books);
   .UsingEntity<BookCategory>(
       l => l.HasOne<Book>().WithMany().HasForeignKey(x => x.BookId),
       r => r.HasOne<Category>().WithMany().HasForeignKey(x => x.CategoryId),
       j =>
       {
           j.HasKey("BookId", "CategoryId");
           j.ToTable("BookCategories"); 
       });

Without entity: (See Scaffolding many-to-many relationships - https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-6.0/whatsnew )

modelBuilder.Entity<Book>()
   .HasMany(x => x.Categories)
   .WithMany(x => Books);
   .UsingEntity<Dictionary<string, object>>(
       "BookCategories",
       l => l.HasOne<Book>().WithMany().HasForeignKey("BookId"),
       r => r.HasOne<Category>().WithMany().HasForeignKey("CategoryId"),
       j =>
       {
           j.HasKey("BookId", "CategoryId");
           j.ToTable("BookCategories"); 
       });

Alternatively, if the joining table needs to contain additional relevant details, for example if you are using a soft-delete system and want to mark deleted relationships as inactive rather than deleting those rows, then you have to adopt an indirect relationship using a BookCategory entity where Book has a collection of BookCategories, as does Category. (See Join entity type configuration - https://docs.microsoft.com/en-us/ef/core/modeling/relationships?tabs=fluent-api%2Cfluent-api-simple-key%2Csimple-key )

Once you have your relationships set up, it is important to treat these relationships as associations, not copies of data. This means you should ensure that your collections are initialized on construction, and never reset. You can add items to the collection or remove items from the collection, but you should never have code that resets the collection. (Ie no code that does stuff like book.Categories = new List<Category>() or book.Categories = myUpdatedCategories etc.) While EF is tracking entities, it is relying on proxies to help with change tracking to know when data needs to be added, removed, or updated. This also means if you want to "change" a book's category, this is a remove and add, not an update.

For instance to change a book's category from "Java" to ".Net", you don't want to do something like:

var book = context.Books.Include(x => x.Categories).Single(x => x.BookId == bookId);
var category = book.Categories.SingleOrDefault(x => x.CategoryName == "Java");
if (category != null)
    category.CategoryName = ".Net"; // or category.CategoryId = dotNetCategoryId;

This would attempt to modify the Category record to change it's Name (likely not intended) or attempt to change it's PK. (illegal)

Instead, you want to change the association:

var dotNetCategory = context.Categories.Single(x => x.CategoryId == dotNetCategoryId);
var book = context.Books.Include(x => x.Categories).Single(x => x.BookId == bookId);
var category = book.Categories.SingleOrDefault(x => x.CategoryName == "Java");
if (category != null)
{
    book.Categories.Remove(category);
    book.Categories.Add(dotNetCategory);
}

Behind the scenes, EF will delete the BookCategory linking the book to Java category, and insert a BookCategory with the new.Net association. If you have a joining entity then you will just need to remove, add, or update the BookCategory entity specifically based on the relationship changes you want to make.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM