資料植入
資料植入是使用初始資料集來填入資料庫的程式。
EF Core 有數種方式可以達成此目的:
組態選項 UseSeeding
和 UseAsyncSeeding
方法
EF 9 引進 UseSeeding
和 UseAsyncSeeding
方法,可提供使用初始數據植入資料庫的便利方式。 這些方法旨在改善使用自定義初始化邏輯的體驗(如下所述)。 它們提供一個清楚的位置,其中可以放置所有數據植入程序代碼。 此外,和 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);
}
});
注意
UseSeeding
從 EnsureCreated
方法呼叫,並從 UseAsyncSeeding
方法呼叫 EnsureCreatedAsync
。 使用這項功能時,即使使用 EF 的程式代碼是異步的,還是建議使用類似的邏輯來實 UseSeeding
作 和 UseAsyncSeeding
方法。 EF Core 工具目前依賴方法的同步版本,如果 UseSeeding
方法未實作,將不會正確植入資料庫。
自訂初始化邏輯
執行數據植入的簡單且強大方式,是在主要應用程式邏輯開始執行之前使用 DbContext.SaveChanges()
。 建議您針對該目的使用 UseSeeding
和 UseAsyncSeeding
,但有時候使用這些方法並不是一個很好的解決方案。 例如,植入需要在一筆交易中使用兩個不同的內容時。 以下是直接在應用程式中執行自訂初始化的程式代碼範例:
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 移 轉可以自動計算將資料庫升級至新版本模型時,需要套用哪些插入、更新或刪除作業。
警告
移轉只會考慮模型變更,以判斷應該執行的作業,以讓受控數據進入所需的狀態。 因此,對移轉外部執行的數據所做的任何變更可能會遺失或造成錯誤。
例如,這會在 中設定 Country
的 OnModelCreating
Managed數據:
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
用來稱為「數據植入」的方法填入資料庫。 此命名會設定不正確的預期,因為此功能有一些限制,而且僅適用於特定數據類型。 這就是為什麼我們決定將它重新命名為「模型受控數據」的原因。 UseSeeding
和 UseAsyncSeeding
方法應該用於一般用途的數據植入。
模型受控數據的限制
這種類型的數據是由移轉所管理,而且腳本會更新資料庫中已存在的數據,而不需要連線到資料庫。 這會施加一些限制:
- 即使通常由資料庫產生主鍵值,也必須指定主鍵值。 其將用來偵測移轉之間的數據變更。
- 如果主鍵以任何方式變更,則會移除先前插入的數據。
因此,這項功能最適用於預期不會在移轉之外變更的靜態數據,且不相依於資料庫中的任何其他專案,例如郵遞區號。
如果您的案例包含下列任一項,建議您使用 UseSeeding
和 UseAsyncSeeding
第一節中所述的方法:
- 用於測試的暫存數據
- 相依於資料庫狀態的數據
- 大型數據(植入數據會在移轉快照集中擷取,而大型數據可以快速導致大量檔案和效能降低)。
- 需要資料庫產生索引鍵值的數據,包括使用替代索引鍵作為身分識別的實體
- 需要自定義轉換的數據(不是由 值轉換處理),例如某些密碼哈希
- 需要呼叫外部 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 }
});