Zan*_*aes 5 entity-framework-core .net-core
关于在实体框架中播种多对多关系存在许多 问题。然而,它们中的大多数都非常古老,并且多对多行为在 EFCore5 中发生了显着变化。官方文档建议重写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; }
}
Run Code Online (Sandbox Code Playgroud)
然后在种子时间:
Book book = new Book() { BookId = 1, Title = "Brave New World" }
Category category = new Category() { CategoryId = 1, CategoryName = "Dystopian" }
category.Books = new List<Book>() { book };
book.Categories = new List<Category>() { category };
modelBuilder.Entity<Book>().HasData(book);
modelBuilder.Entity<Category>().HasData(category);
Run Code Online (Sandbox Code Playgroud)
BookCategories...但在生成的迁移中没有创建任何条目。这在某种程度上是预料之中的,因为本文建议必须明确为中间表设定种子。我想要的是这样的:
modelBuilder.Entity<BookCategory>().HasData(
new BookCategory() { BookId = 1, CategoryId = 1 }
);
Run Code Online (Sandbox Code Playgroud)
然而,同样,由于 EFCore5 中没有具体的类来描述BookCategories,我能想到的播种表的唯一方法是使用附加MigrationBuilder.InsertData 命令手动编辑迁移,这违背了通过应用程序代码播种数据的目的。
BookCategories然而,同样,由于EFCore5 中没有具体的类来描述
实际上,正如“新增功能”链接中所解释的,EF Core 5 允许您拥有显式联接实体
public class BookCategory
{
public int BookId { get; set; }
public EBook Book { get; set; }
public int CategoryId { get; set; }
public Category Category { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
并配置多对多关系来使用它
modelBuilder.Entity<Book>()
.HasMany(left => left.Categories)
.WithMany(right => right.Books)
.UsingEntity<BookCategory>(
right => right.HasOne(e => e.Category).WithMany(),
left => left.HasOne(e => e.Book).WithMany().HasForeignKey(e => e.BookId),
join => join.ToTable("BookCategories")
);
Run Code Online (Sandbox Code Playgroud)
这样您就可以使用所有正常的实体操作(查询、更改跟踪、数据模型播种等)
modelBuilder.Entity<BookCategory>().HasData(
new BookCategory() { BookId = 1, CategoryId = 1 }
);
Run Code Online (Sandbox Code Playgroud)
仍然具有新的多对多跳过导航映射。
这可能是最简单也是类型安全的方法。
如果您觉得太多,也可以使用传统的连接实体,但您需要知道共享字典实体类型名称以及两个影子属性名称。按照惯例,您将看到的可能不是您所期望的。
因此,按照惯例,连接实体(和表)名称是
{LeftEntityName}{RightEntityName}
影子属性(和列)名称是
{LeftEntityNavigationPropertyName}{RightEntityKeyName}{RightEntityNavigationPropertyName}{LeftEntityKeyName}第一个问题是 - 哪个是左/右实体?答案是(尚未记录) - 按照惯例,左侧实体是按字母顺序排列名称较小的实体。因此,您的示例Book是左,Category是右,因此连接实体和表名称将为BookCategory。
可以更改添加显式
modelBuilder.Entity<Category>()
.HasMany(left => left.Books)
.WithMany(right => right.Categories);
Run Code Online (Sandbox Code Playgroud)
现在就是了CategoryBook。
在这两种情况下,影子属性(和列)名称都是
CategoriesCategoryIdBooksBookId因此,表名和属性/列名都不是您通常所做的。
除了数据库表/列名称之外,实体和属性名称也很重要,因为您需要它们来进行实体操作,包括相关的数据播种。
话虽这么说,即使您不创建显式联接实体,最好流畅地配置 EF Core 约定自动创建的实体:
modelBuilder.Entity<Book>()
.HasMany(left => left.Categories)
.WithMany(right => right.Books)
.UsingEntity("BookCategory", typeof(Dictionary<string, object>),
right => right.HasOne(typeof(Category)).WithMany().HasForeignKey("CategoryId"),
left => left.HasOne(typeof(Book)).WithMany().HasForeignKey("BookId"),
join => join.ToTable("BookCategories")
);
Run Code Online (Sandbox Code Playgroud)
现在您可以使用实体名称来访问EntityTypeBuilder
modelBuilder.Entity("BookCategories")
Run Code Online (Sandbox Code Playgroud)
您可以像具有匿名类型的影子 FK 属性的普通实体一样为其播种
modelBuilder.Entity("BookCategories")
Run Code Online (Sandbox Code Playgroud)
或者对于这个特定的属性包类型实体,也带有Dictionary<string, object>实例
modelBuilder.Entity("BookCategory").HasData(
new { BookId = 1, CategoryId = 1 }
);
Run Code Online (Sandbox Code Playgroud)
更新:
人们似乎误解了上述“额外”步骤,并发现它们是多余的、“太多”、不需要的。
我从未说过它们是强制性的。如果您知道常规连接实体和属性名称,请直接进入最后一步并使用匿名类型或Dictionary<string, object>.
我已经解释了采用该路线的缺点 - 失去 C# 类型安全性并使用不受控制的“魔法”字符串。您必须足够聪明才能了解确切的 EF Core 命名约定,并意识到如果将类重命名Book为EBook新的联接实体/表名称将从“BookCategory”更改为“CategoryEBook”以及 PK 属性/的顺序列、关联索引等
关于数据播种的具体问题。如果您真的想概括它(OP 在自己的答案中尝试),至少通过使用 EF Core 元数据系统而不是反射和假设来正确实现它。例如,以下代码将从 EF Core 元数据中提取这些名称:
modelBuilder.Entity("BookCategory").HasData(
new Dictionary<string, object> { ["BookId"] = 1, ["CategoryId"] = 1 }
);
Run Code Online (Sandbox Code Playgroud)
另外,在这里您不需要知道哪种类型是“左”,哪种类型是“右”,也不需要特殊的基类或接口。只需传递实体对的序列,它就会正确地播种传统的连接实体,例如使用 OP 示例,两者
public static void HasJoinData<TFirst, TSecond>(
this ModelBuilder modelBuilder,
params (TFirst First, TSecond Second)[] data)
where TFirst : class where TSecond : class
=> modelBuilder.HasJoinData(data.AsEnumerable());
public static void HasJoinData<TFirst, TSecond>(
this ModelBuilder modelBuilder,
IEnumerable<(TFirst First, TSecond Second)> data)
where TFirst : class where TSecond : class
{
var firstEntityType = modelBuilder.Model.FindEntityType(typeof(TFirst));
var secondEntityType = modelBuilder.Model.FindEntityType(typeof(TSecond));
var firstToSecond = firstEntityType.GetSkipNavigations()
.Single(n => n.TargetEntityType == secondEntityType);
var joinEntityType = firstToSecond.JoinEntityType;
var firstProperty = firstToSecond.ForeignKey.Properties.Single();
var secondProperty = firstToSecond.Inverse.ForeignKey.Properties.Single();
var firstValueGetter = firstToSecond.ForeignKey.PrincipalKey.Properties.Single().GetGetter();
var secondValueGetter = firstToSecond.Inverse.ForeignKey.PrincipalKey.Properties.Single().GetGetter();
var seedData = data.Select(e => (object)new Dictionary<string, object>
{
[firstProperty.Name] = firstValueGetter.GetClrValue(e.First),
[secondProperty.Name] = secondValueGetter.GetClrValue(e.Second),
});
modelBuilder.Entity(joinEntityType.Name).HasData(seedData);
}
Run Code Online (Sandbox Code Playgroud)
和
modelBuilder.HasJoinData((book, category));
Run Code Online (Sandbox Code Playgroud)
会做。
| 归档时间: |
|
| 查看次数: |
3016 次 |
| 最近记录: |