gpt4 book ai didi

.net-core - 使用 ModelBuilder 在 EFCore5 中播种多对多数据库?

转载 作者:行者123 更新时间:2023-12-04 02:30:46 28 4
gpt4 key购买 nike

many questions关于在 Entity Framework 中播种多对多关系。不过大部分都非常老了,多对多的行为有changed significantly in EFCore5 . official docs recommend覆盖 OnModelCreating实现ModelBuilder.Entity<>.HasData() .
但是,使用新的多对多行为(没有显式映射),我找不到为中间表播种的明确路径。以 this tutorial 为例, 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; }
}
然后在种子时间:
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);
...但没有为 BookCategories 创建条目在由此产生的迁移中。这有点出乎意料,因为 this article suggests必须明确地为中间表播种。我想要的是这样的:
modelBuilder.Entity<BookCategory>().HasData(
new BookCategory() { BookId = 1, CategoryId = 1 }
);
然而,再一次,因为没有具体的类来描述 BookCategories在 EFCore5 中,我能想到的唯一方法是使用额外的 MigrationBuilder.InsertData 手动编辑迁移命令,这反而违背了通过应用程序代码播种数据的目的。

最佳答案

However, again, since there is no concrete class to describe BookCategories in EFCore5


实际上,正如 What's new link 中所解释的那样, 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; }
}
并配置多对多关系来使用它
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")
);
通过这种方式,您可以使用所有正常的实体操作(查询、更改跟踪、数据模型播种等)
modelBuilder.Entity<BookCategory>().HasData(
new BookCategory() { BookId = 1, CategoryId = 1 }
);
仍然有新的多对多跳过导航映射。
这可能是最简单且类型安全的方法。
如果你觉得它太多了,也可以使用传统的连接实体,但你需要知道 shared dictionary entity type名称,以及两个 shadow property名字。正如您将按照惯例看到的那样,这可能不是您所期望的。
因此,按照惯例,连接实体(和表)名称是 {LeftEntityName}{RightEntityName}阴影属性(和列)名称是
  • {LeftEntityNavigationPropertyName}{RightEntityKeyName}
  • {RightEntityNavigationPropertyName}{LeftEntityKeyName}

  • 第一个问题是——哪个是左/右实体?答案是(尚未记录) - 按照惯例,左侧实体是名称按字母顺序排列的较少的实体。所以用你的例子 Book离开了, Category是正确的,所以连接实体和表名将是 BookCategory .
    可以更改添加显式
    modelBuilder.Entity<Category>()
    .HasMany(left => left.Books)
    .WithMany(right => right.Categories);
    现在是 CategoryBook .
    在这两种情况下,影子属性(和列)名称都是
  • CategoriesCategoryId
  • BooksBookId

  • 因此,表名和属性/列名都不是您通常会做的。
    除了数据库表/列名称之外,实体和属性名称也很重要,因为您需要它们来进行实体操作,包括有问题的数据播种。
    话虽如此,即使您不创建显式连接实体,最好流畅地配置由 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")
    );
    现在您可以使用实体名称访问 EntityTypeBuilder
    modelBuilder.Entity("BookCategories")
    您可以将其播种类似于 normal entities with shadow FK properties匿名类型
    modelBuilder.Entity("BookCategory").HasData(
    new { BookId = 1, CategoryId = 1 }
    );
    或者对于这个特定的属性包类型实体,也可以使用 Dictionary<string, object>实例
    modelBuilder.Entity("BookCategory").HasData(
    new Dictionary<string, object> { ["BookId"] = 1, ["CategoryId"] = 1 }
    );

    更新:
    人们似乎误解了前面提到的“额外”步骤,发现它们是多余的和“太多”,不需要。
    我从来没有说过它们是强制性的。如果您知道常规的连接实体和属性名称,请直接进入最后一步并使用匿名类型或 Dictionary<string, object> .
    我已经解释了走这条路的缺点——失去了 C# 类型的安全性并使用了你无法控制的“魔法”字符串。您必须足够聪明才能知道确切的 EF Core 命名约定,并意识到如果您重命名类 BookEBook新的连接实体/表名称将从“BookCategory”更改为“CategoryEBook”以及 PK 属性/列、关联索引等的顺序。
    关于数据播种的具体问题。如果你真的想概括它(OP 在他们自己的答案中尝试),至少通过使用 EF Core 元数据系统而不是反射和假设来正确地做到这一点。例如,以下将从 EF Core 元数据中提取这些名称:
    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);
    }
    同样在这里,您不需要知道哪种类型是“左”,哪种是“​​右”,也不需要特殊的基类或接口(interface)。只需传递实体对序列,它就会正确地为常规连接实体播种,例如以OP为例,两者
    modelBuilder.HasJoinData((book, category));

    modelBuilder.HasJoinData((category, book));
    会做。

    关于.net-core - 使用 ModelBuilder 在 EFCore5 中播种多对多数据库?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64345107/

    28 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com