.net core 使用ModelBuilder在EFCore5中植入多对多数据库?

.net core 使用ModelBuilder在EFCore5中植入多对多数据库?,.net-core,entity-framework-core,.net Core,Entity Framework Core,在实体框架中有很多种关系。然而,他们中的大多数都非常老,而且多对多的行为已经发生。重写OnModelCreating以实现ModelBuilder.Entity.HasData() 但是,使用新的多对多行为(没有显式映射),我无法找到为中间表种子的清晰路径。例如,BookCategories类现在是隐式的。因此,在种子设定时没有显式声明中间表值的路径 我还尝试简单地分配数组,例如: public class Book { public int BookId { get; set; }

在实体框架中有很多种关系。然而,他们中的大多数都非常老,而且多对多的行为已经发生。重写
OnModelCreating
以实现
ModelBuilder.Entity.HasData()

但是,使用新的多对多行为(没有显式映射),我无法找到为中间表种子的清晰路径。例如,
BookCategories
类现在是隐式的。因此,在种子设定时没有显式声明中间表值的路径

我还尝试简单地分配数组,例如:

public class Book
{
    public int BookId { get; set; }
    public string Title { get; set; }
    public ICollection<Category> Categories { get; set; }
}  
public class Category
{
    public int CategoryId { get; set; }
    public string CategoryName { get; set; }
    public ICollection<Book> Books { get; set; }
}  
但是,同样,由于在EFCore5中没有具体的类来描述
BookCategories
,因此我能想到的为表种子设定种子的唯一方法是使用额外的
MigrationBuilder.InsertData
命令手动编辑迁移,这与通过应用程序代码设定数据种子的目的背道而驰

然而,同样,由于在EFCore5中没有具体的类来描述
图书类别

实际上,正如在中所解释的,EF Core 5允许您拥有显式连接实体

公共类图书类别
{
public int BookId{get;set;}
公共电子书{get;set;}
public int CategoryId{get;set;}
公共类别{get;set;}
}
并配置多对多关系以使用它

modelBuilder.Entity()
.HasMany(左=>左.Categories)
.WithMany(右=>right.Books)
.商业实体(
right=>right.HasOne(e=>e.Category).WithMany(),
left=>left.HasOne(e=>e.Book).WithMany().HasForeignKey(e=>e.BookId),
join=>join.ToTable(“图书类别”)
);
通过这种方式,您可以使用所有常规实体操作(查询、更改跟踪、数据模型种子设定等)

modelBuilder.Entity().HasData(
new BookCategory(){BookId=1,CategoryId=1}
);
仍然有新的多对多跳过导航映射

这可能是最简单的类型安全方法

如果你觉得太多了,也可以使用传统的连接实体,但是你需要知道名称以及两个名称。按照惯例,这可能不是你所期望的

因此,按照惯例,连接实体(和表)的名称是

{LeftEntityName}{righEntityName}

阴影属性(和列)名称为

  • {LeftEntityNavigationPropertyName}{RightEntityKeyName}
  • {RightEntityNavigationPropertyName}{LeftEntityKeyName}
第一个问题是-哪个是左/右实体?答案是(尚未记录)-按照惯例,左边的实体是名称按字母顺序排列较少的实体。因此,在您的示例中,
Book
位于左侧,
Category
位于右侧,因此连接实体和表名将为
BookCategory

它可以通过添加显式

modelBuilder.Entity()
.HasMany(左=>左.Books)
.WithMany(right=>right.Categories);
现在它将是
分类书籍

在这两种情况下,阴影属性(和列)名称都是

  • CategoriesCategoryId
  • BooksBookId
因此,表名和属性/列名都不是您通常使用的名称

除了数据库表/列名之外,实体和属性名也很重要,因为实体操作需要它们,包括相关的数据种子设定

话虽如此,即使您不创建显式连接实体,也最好流畅地配置由EF Core约定自动创建的连接实体:

modelBuilder.Entity()
.HasMany(左=>左.Categories)
.WithMany(右=>right.Books)
.UsingEntity(“图书类别”)、类型(字典),
right=>right.HasOne(typeof(Category)).WithMany().HasForeignKey(“CategoryId”),
left=>left.HasOne(typeof(Book)).WithMany().HasForeignKey(“BookId”),
join=>join.ToTable(“图书类别”)
);
现在,您可以使用实体名称访问
EntityTypeBuilder

modelBuilder.Entity("BookCategories")
您可以使用类似于匿名类型的方法对其进行种子设定

modelBuilder.Entity(“BookCategory”).HasData(
新{BookId=1,CategoryId=1}
);
或者对于此特定属性包类型实体,也可以使用
字典
实例

modelBuilder.Entity(“BookCategory”).HasData(
新词典{[“BookId”]=1,[“CategoryId”]=1}
);

更新:

人们似乎误解了前面提到的“额外”步骤,认为它们是多余的,“太多了”,不需要

我从来没有说过它们是强制性的。如果您知道传统的联接实体和属性名称,请直接转到最后一步,并使用匿名类型或
字典

我已经解释了采用这种方法的缺点——失去C#type安全性和使用“魔法”字符串。您必须足够聪明,了解确切的EF核心命名约定,并意识到如果将class
Book
重命名为
EBook
,新的连接实体/表名将从“BookCategory”更改为“categoryBook”,以及PK属性/列、关联索引等的顺序

关于数据种子的具体问题。如果你真的想推广它(在他们自己的答案中尝试),至少通过使用EF核心元数据系统而不是反射和假设来正确地进行。例如,以下内容将从EF核心元数据中提取这些名称:

公共静态数据(
这是ModelBuilder ModelBuilder,
参数(TFirst First,TSecond Second)[数据)
where tffirst:class where TSecond:class
=>modelBuilder.HasJoinData(data.AsE
modelBuilder.Entity<BookCategory>().HasData(
  new BookCategory() { BookId = 1, CategoryId = 1 }
);
modelBuilder.Entity("BookCategories")
// Add book1 and book2 to category1:
modelBuilder.HasM2MData(new [] { book1, book2 }, new [] { category1 });
public static void HasM2MData<T1, T2>
  (this ModelBuilder mb, T1[] t1s, T2[] t2s)
  where T1 : ModelBase where T2 : ModelBase
{
  string table = $"{typeof(T1).Name}{typeof(T2).Name}";
  PropertyInfo t1Prop = GetM2MProperty<T1, T2>();
  PropertyInfo t2Prop = GetM2MProperty<T2, T1>();
  string t1Key = $"{t1Prop.Name}Id";
  string t2Key = $"{t2Prop.Name}Id";
  foreach (T1 t1 in t1s) {
    foreach (T2 t2 in t2s) {
      mb.Entity(table).HasData(new Dictionary<string, object>() { [t2Key] = t1.Id, [t1Key] = t2.Id });
    }
  }
}

// Get a property on T1 which is assignable to type ICollection<T2>, representing the m2m relationship
private static PropertyInfo GetM2MProperty<T1, T2>() {
  Type assignableType = typeof(ICollection<T2>);
  List<PropertyInfo> props = typeof(T1).GetProperties()
                                       .Where(pi => pi.PropertyType.IsAssignableTo(assignableType))
                                       .ToList();
  if (props.Count() != 1) {
    throw new SystemException(
      $"Expected {typeof(T1)} to have exactly one column of type {assignableType}; got: {props.Count()}");
  }
  return props.First();
}
migrationBuilder.InsertData(
table: "BookCategory",
columns: new[] { "BooksId", "CategoriesId" },
values: new object[,]
{
    { "book1", "category1" },
    { "book2", "category1" }
});
    builder.Entity("ContractDeclarationType").HasData(
        new { ContractsId = 1L, DeclarationTypesId = 1L },
        new { ContractsId = 1L, DeclarationTypesId = 2L },
        new { ContractsId = 1L, DeclarationTypesId = 3L });