共用方式為


資料植入

資料植入是使用初始資料集來填入資料庫的程式。

EF Core 有數種方式可以達成此目的:

組態選項 UseSeedingUseAsyncSeeding 方法

EF 9 引進 UseSeedingUseAsyncSeeding 方法,可提供使用初始數據植入資料庫的便利方式。 這些方法旨在改善使用自定義初始化邏輯的體驗(如下所述)。 它們提供一個清楚的位置,其中可以放置所有數據植入程序代碼。 此外,和 UseAsyncSeeding 方法內的UseSeeding程式代碼會受到移轉鎖定機制的保護,以防止並行問題。

新的植入方法會在作業和dotnet ef database update命令中EnsureCreated呼叫,Migrate即使沒有模型變更,也不會套用任何移轉。

提示

使用 和 UseSeeding UseAsyncSeeding 是使用 EF Core 時,使用初始數據植入資料庫的建議方式。

這些方法可以在選項組態步驟設定。 以下是範例:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=EFDataSeeding;Trusted_Connection=True;ConnectRetryCount=0")
        .UseSeeding((context, _) =>
        {
            var testBlog = context.Set<Blog>().FirstOrDefault(b => b.Url == "http://test.com");
            if (testBlog == null)
            {
                context.Set<Blog>().Add(new Blog { Url = "http://test.com" });
                context.SaveChanges();
            }
        })
        .UseAsyncSeeding(async (context, _, cancellationToken) =>
        {
            var testBlog = await context.Set<Blog>().FirstOrDefaultAsync(b => b.Url == "http://test.com", cancellationToken);
            if (testBlog == null)
            {
                context.Set<Blog>().Add(new Blog { Url = "http://test.com" });
                await context.SaveChangesAsync(cancellationToken);
            }
        });

注意

UseSeedingEnsureCreated 方法呼叫,並從 UseAsyncSeeding 方法呼叫 EnsureCreatedAsync 。 使用這項功能時,即使使用 EF 的程式代碼是異步的,還是建議使用類似的邏輯來實 UseSeeding 作 和 UseAsyncSeeding 方法。 EF Core 工具目前依賴方法的同步版本,如果 UseSeeding 方法未實作,將不會正確植入資料庫。

自訂初始化邏輯

執行數據植入的簡單且強大方式,是在主要應用程式邏輯開始執行之前使用 DbContext.SaveChanges() 。 建議您針對該目的使用 UseSeedingUseAsyncSeeding ,但有時候使用這些方法並不是一個很好的解決方案。 例如,植入需要在一筆交易中使用兩個不同的內容時。 以下是直接在應用程式中執行自訂初始化的程式代碼範例:

using (var context = new DataSeedingContext())
{
    context.Database.EnsureCreated();

    var testBlog = context.Blogs.FirstOrDefault(b => b.Url == "http://test.com");
    if (testBlog == null)
    {
        context.Blogs.Add(new Blog { Url = "http://test.com" });
        context.SaveChanges();
    }
}

警告

植入程式代碼不應該是一般應用程式執行的一部分,因為當多個實例執行時,這可能會導致並行問題,而且也需要應用程式具有修改資料庫架構的許可權。

根據部署的條件約束,初始化程式代碼可以透過不同的方式執行:

  • 在本機執行初始化應用程式
  • 使用主要應用程式部署初始化應用程式、叫用初始化例程,以及停用或移除初始化應用程式。

這通常可以使用發行配置檔來自動化

模型受控數據

數據也可以與實體類型相關聯,做為模型組態的一部分。 然後,EF Core 轉可以自動計算將資料庫升級至新版本模型時,需要套用哪些插入、更新或刪除作業。

警告

移轉只會考慮模型變更,以判斷應該執行的作業,以讓受控數據進入所需的狀態。 因此,對移轉外部執行的數據所做的任何變更可能會遺失或造成錯誤。

例如,這會在 中設定 CountryOnModelCreatingManaged數據:

modelBuilder.Entity<Country>(b =>
{
    b.Property(x => x.Name).IsRequired();
    b.HasData(
        new Country { CountryId = 1, Name = "USA" },
        new Country { CountryId = 2, Name = "Canada" },
        new Country { CountryId = 3, Name = "Mexico" });
});

若要新增具有關聯性的實體,必須指定外鍵值:

modelBuilder.Entity<City>().HasData(
    new City { Id = 1, Name = "Seattle", LocatedInId = 1 },
    new City { Id = 2, Name = "Vancouver", LocatedInId = 2 },
    new City { Id = 3, Name = "Mexico City", LocatedInId = 3 },
    new City { Id = 4, Name = "Puebla", LocatedInId = 3 });

管理多對多導覽的數據時,必須明確設定聯結實體。 如果實體類型具有陰影狀態的任何屬性(例如 LanguageCountry 下面的聯結實體),則可以使用匿名類別來提供值:

modelBuilder.Entity<Language>(b =>
{
    b.HasData(
        new Language { Id = 1, Name = "English" },
        new Language { Id = 2, Name = "French" },
        new Language { Id = 3, Name = "Spanish" });

    b.HasMany(x => x.UsedIn)
        .WithMany(x => x.OfficialLanguages)
        .UsingEntity(
            "LanguageCountry",
            r => r.HasOne(typeof(Country)).WithMany().HasForeignKey("CountryId").HasPrincipalKey(nameof(Country.CountryId)),
            l => l.HasOne(typeof(Language)).WithMany().HasForeignKey("LanguageId").HasPrincipalKey(nameof(Language.Id)),
            je =>
            {
                je.HasKey("LanguageId", "CountryId");
                je.HasData(
                    new { LanguageId = 1, CountryId = 2 },
                    new { LanguageId = 2, CountryId = 2 },
                    new { LanguageId = 3, CountryId = 3 });
            });
});

擁有的實體類型可以以類似的方式進行設定:

modelBuilder.Entity<Language>().OwnsOne(p => p.Details).HasData(
    new { LanguageId = 1, Phonetic = false, Tonal = false, PhonemesCount = 44 },
    new { LanguageId = 2, Phonetic = false, Tonal = false, PhonemesCount = 36 },
    new { LanguageId = 3, Phonetic = true, Tonal = false, PhonemesCount = 24 });

如需更多內容, 請參閱完整的範例專案

將數據新增至模型之後, 應該使用移 轉來套用變更。

提示

如果您需要將移轉套用為自動化部署的一部分,您可以 建立可在執行前預覽的 SQL 腳本

或者,您可以使用 context.Database.EnsureCreated() 來建立包含 Managed 數據的新資料庫,例如測試資料庫,或使用記憶體內部提供者或任何非關係資料庫時。 請注意,如果資料庫已經存在, EnsureCreated() 將不會更新資料庫中的架構或受控數據。 如果您打算使用移轉,就不應該呼叫 EnsureCreated() 關係資料庫。

注意

使用 HasData 用來稱為「數據植入」的方法填入資料庫。 此命名會設定不正確的預期,因為此功能有一些限制,而且僅適用於特定數據類型。 這就是為什麼我們決定將它重新命名為「模型受控數據」的原因。 UseSeedingUseAsyncSeeding 方法應該用於一般用途的數據植入。

模型受控數據的限制

這種類型的數據是由移轉所管理,而且腳本會更新資料庫中已存在的數據,而不需要連線到資料庫。 這會施加一些限制:

  • 即使通常由資料庫產生主鍵值,也必須指定主鍵值。 其將用來偵測移轉之間的數據變更。
  • 如果主鍵以任何方式變更,則會移除先前插入的數據。

因此,這項功能最適用於預期不會在移轉之外變更的靜態數據,且不相依於資料庫中的任何其他專案,例如郵遞區號。

如果您的案例包含下列任一項,建議您使用 UseSeedingUseAsyncSeeding 第一節中所述的方法:

  • 用於測試的暫存數據
  • 相依於資料庫狀態的數據
  • 大型數據(植入數據會在移轉快照集中擷取,而大型數據可以快速導致大量檔案和效能降低)。
  • 需要資料庫產生索引鍵值的數據,包括使用替代索引鍵作為身分識別的實體
  • 需要自定義轉換的數據(不是由 值轉換處理),例如某些密碼哈希
  • 需要呼叫外部 API 的數據,例如 ASP.NET 核心身分識別角色和使用者建立
  • 未固定且具決定性的數據,例如植入 至 DateTime.Now

手動移轉自定義

將移轉新增至指定HasData之資料的變更會轉換成對、 UpdateData()DeleteData()InsertData()呼叫。 解決某些限制 HasData 的其中一種方法是手動將這些呼叫或 自定義作業 新增至移轉。

migrationBuilder.InsertData(
    table: "Countries",
    columns: new[] { "CountryId", "Name" },
    values: new object[,]
    {
        { 1, "USA" },
        { 2, "Canada" },
        { 3, "Mexico" }
    });

migrationBuilder.InsertData(
    table: "Languages",
    columns: new[] { "Id", "Name", "Details_PhonemesCount", "Details_Phonetic", "Details_Tonal" },
    values: new object[,]
    {
        { 1, "English", 44, false, false },
        { 2, "French", 36, false, false },
        { 3, "Spanish", 24, true, false }
    });

migrationBuilder.InsertData(
    table: "Cites",
    columns: new[] { "Id", "LocatedInId", "Name" },
    values: new object[,]
    {
        { 1, 1, "Seattle" },
        { 2, 2, "Vancouver" },
        { 3, 3, "Mexico City" },
        { 4, 3, "Puebla" }
    });

migrationBuilder.InsertData(
    table: "LanguageCountry",
    columns: new[] { "CountryId", "LanguageId" },
    values: new object[,]
    {
        { 2, 1 },
        { 2, 2 },
        { 3, 3 }
    });