前言
將 ABP v4.x 版本的方案升級到 ABP v5.x 後,
再執行 DbMigrator 專案後,居然發生 EntityFrameworkCore.DbUpdateException 的錯誤 (Cannot insert duplicate key row)
SqlException: Cannot insert duplicate key row in object ‘dbo.AbpPermissionGrants’ with unique index ‘IX_AbpPermissionGrants_TenantId_Name_ProviderName_ProviderKey’. The duplicate key value is (51d8723e-1858-cf8a-1a64-3a09b866138a, AbpIdentity.Roles.Update, R, admin).
The statement has been terminated.
解法
因為 ABP v5.x 的 Migration 中,會針對 AbpPermissionGrants 這個資料表,
以 "TenantId", "Name", "ProviderName", "ProviderKey"
這 4 個欄位建立 唯一值索引。
查詢更新前資料庫的 AbpPermissionGrants 資料,很多資料都是有重覆,
1 | select TenantId, ProviderKey, Name, Count(*) from [dbo].[AbpPermissionGrants] |
所以當升級後,自然就炸了。
回來頭來查看一下 DbMigrator 做那些事來追看看。
在 DbMigratorHostedService.cs
中的 StartAsync
就是呼叫這個系統 DbMigrationService
的 MigrateAsync
Method。
而在 MigrateAsync
Method 裡會進行 MigrateDatabaseSchema
(資料庫升級) 及 SeedDataAsync
(資料升級),
1 | private async Task MigrateDatabaseSchemaAsync(Tenant tenant = null) |
在 Debug 的過程中,發現專案中有 5 個 DataSeedContributor,但只有 2 個自已寫的 DataSeedContributor 會執行 2 次。
查看一下 Volo.Abp.Data
中 DataSeeder
的 SeedAsync
Method,
1 | public class DataSeeder : IDataSeeder, ITransientDependency |
看起來是呼叫 AbpDataSeedOptions.Contributors
中 Contributor
的 SeedAsync
Method。
於是在這個系統 DbMigrationService
類別中 注入 IOptions<AbpDataSeedOptions> options
,
然後查看 AbpDataSeedOptions.Contributors
裡面有那些 Contributor
,
結果發現,有 2 個 Name 重覆的 Contributor,
而這 2 個重覆的 Contributor ,正是被重覆執行的 Contributor,如下,
被重覆 Run 到的 Contributor ,分別是 IdentityDataSeedContributor
及 PermissionDataSeedContributor
,
而它們都分別繼承自 Volo.Abp 對應的 IdentityDataSeedContributor
及 PermissionDataSeedContributor
,
而透過 ServiceProvider.GetRequiredService
給 Volo.Abp.Identity.IdentityDataSeedContributor
取回來的服務則是我們系統的 LicenseManagement.Identity.IdentityDataSeedContributor
,
而當 ServiceProvider.GetRequiredService
給 LicenseManagement.Identity.IdentityDataSeedContributor
取回來的服務也是我們系統的 LicenseManagement.Identity.IdentityDataSeedContributor
,
所以這 2 個繼承自 Volo.Abp 的 IdentityDataSeedContributor
及 PermissionDataSeedContributor
,
就執行了 2 次 SeedAsync Method。
1 | [ ] |
知道發生的原因,因為 AbpDataSeedOptions.Contributors 多了重覆的項目,
最快的方式就是將重覆的 DataSeedContributor 從 AbpDataSeedOptions.Contributors 中將它們移除,
在這個系統 DbMigrationService
類別 MigrateAsync
Method 中,
在一開始就將重覆中的 Volo DataSeedContributor 移除,如下,
1 | public class LicenseManagementDbMigrationService : ITransientDependency |
再執行 DbMigrator 就不會發生重覆執行的狀況的。
而為什麼 AbpDataSeedOptions.Contributors 會有那些 DataSeedContributor ?
可以查看 AbpDataModule
的 AutoAddDataSeedContributors
Method,
1 | private static void AutoAddDataSeedContributors(IServiceCollection services) |
參考資源
Seeding initial user on Identity module without double-seeding