.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
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核心命名约定,并意识到如果将classBook
重命名为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 });