EF Core – Many to Many
前言
Many to many 是 EF Core 5.0 才开始有的, 以前都用 2 个 1-n 来实现的.
由于它比 1-n 复杂, 所以有必要写一遍来记入一下.
参考:
Without Fluent API
public class Post { public int PostId { get; set; } public string Title { get; set; } public string Content { get; set; } public ICollection<Tag> Tags { get; set; } } public class Tag { public string TagId { get; set; } public ICollection<Post> Posts { get; set; } }
在完全没有 Fluent API 配置下, 只要 2 个 class 有关系的 collection 就可以表达 n-n 了.
它会自动创建一个 Join entity type configuration, 就可以生产 migration 了.
为什么是 CategoryProduct 而不是 ProductCategory 呢?
它是按 class name A-Z 排序的. C 比 P 前面, 所以就是 CategoryProduct.
Joining relationships configuration (Dictionary)
想自己配置 Join entity type configuration 也是 ok 的.
modelBuilder.Entity<Category>() .HasMany(e => e.Products) .WithMany(e => e.Categories) .UsingEntity<Dictionary<string, object>>( j => j .HasOne<Product>() .WithMany() .HasForeignKey("ProductId") .OnDelete(DeleteBehavior.Cascade), j => j .HasOne<Category>() .WithMany() .HasForeignKey("CategoryId") .OnDelete(DeleteBehavior.Cascade), j => { j.ToTable("CategoryProduct"); j.Property<int>("Id").HasColumnName("CategoryProductId"); j.HasKey("Id").IsClustered(false); j.HasIndex("ProductId", "CategoryId").IsUnique().IsClustered(true); j.HasAnnotation("AuditTrail", null); j.Property<string>("CreatedBy"); j.Property<DateTimeOffset>("DateCreated"); } );
第 3 个 parameter 是配置 Join Entity. 注意这里用的是 Dictionary 来表示 Join Entity 而不是 class, 这个做法比较适合那种在 application level 不会用到这个 join entity 的情况.
如果需要用到的话, 那么应该用下面这个方式.
Joining relationships configuration (Class)
public class CategoryProduct { public int Id { get; set; } public int ProductId { get; set; } public Product Product { get; set; } = null!; public int CategoryId { get; set; } public Category Category { get; set; } = null!; public string CreatedBy { get; set; } = ""; public DateTimeOffset DateCreated { get; set; } }
没有太大的区别, 就是定义多一个 class 就 ok 了.
modelBuilder.Entity<Category>() .HasMany(e => e.Products) .WithMany(e => e.Categories) .UsingEntity<CategoryProduct>( j => j.HasOne(e => e.Product) .WithMany() .HasForeignKey(e => e.ProductId) .OnDelete(DeleteBehavior.Cascade), j => j.HasOne(e => e.Category) .WithMany() .HasForeignKey(e => e.CategoryId) .OnDelete(DeleteBehavior.Cascade), j => { j.ToTable("CategoryProduct"); j.HasKey(e => e.Id).IsClustered(false); j.HasIndex(e => new { e.CategoryId, e.ProductId }).IsUnique().IsClustered(true); } );