ABP Vnext 4.4:统一Ef Core的DbContext/移除EF Core Migrations项目
Abp vnext 4.4出現了一個比較重大的變更:在Startup template中移除了EF Core Migrations項目,本文翻譯自community.abp.io/articl
由于本文發布的時候Abp vnext的版本還沒有到4.4,所以本文演示了如何從4.4以前的版本移除EntityFrameworkCore.DbMigrations這個項目,并且使用唯一的一個DbContext來進行數據庫的映射和基于Code-First模式的遷移。
該項目的github地址如下:github.com/abpframework
動機/背景
如果你使用Ef core作為數據庫provider創建一個解決方案,那么會有兩個與ef core有關的項目:
EntityFrameworkCore這個項目包含了你的應用的真正的DbContext,它包含了所有的數據庫映射和你的Repository的實現。
另一方面,EntityFrameworkCore.DbMigrations?項目包含了另一個DbContext用來創建和施行數據庫遷移。它包含了你所使用的所有模塊的數據庫映射,所以你有一個單獨并統一的數據庫架構/方案。
當時這么做主要有兩個原因:
你真正的DbContext保持了簡單和集中(focused)。它只包含了你自己應用中的實體相關的內容并且不包含你所使用的關于其他模塊的內容。
你可以創建自己的類,映射到依賴模塊的表。例如,AppUser實體(包含在下載的解決方案中)映射到數據庫中的AbpUsers表,而AbpUsers表實際上映射到Identity Module的IdentityUser實體。這意味著它們共享相同的數據庫表。與IdentityServer相比,AppUser包含的屬性更少。您只添加您需要的屬性,而不是更多。這還允許您根據自定義需求向AppUser添加新的標準(類型安全)屬性,只要您仔細地管理數據庫映射。
對于這個方面的說明我們在官方的文檔中有詳細的說明。然而,當你重用那些你依賴的模塊的表時,會存在一些問題,那就是這樣的架構會導致你的數據庫映射變得復雜。許多開發者在做一些諸如映射這些類/實體的工作時,會變得迷茫和犯錯,特別是當他們想要將這些實體和其他實體聯系起來時。
所以,我們決定在4.4的版本中取消這種分離,刪除EntityFrameworkCore.DbMigrations這個項目。新版本的abp vnext中將只包含EntityFrameworkCore這個項目并且只擁有一個DbContext。
如果你今天就想嘗試這么干,請接著往下看。
警告
新的設計有一個缺點(軟件開發中的一切都是一種權衡)。我們需要刪除AppUser實體,因為EF Core不能在沒有繼承關系的情況下將兩個類映射到單個表。我將在本文后面介紹這一點,并提供處理它的建議。步驟
我們的目標是在EntityFrameworkCore項目中啟用數據庫遷移,移除EntityFrameworkCore.DbMigrations項目并根據該包重新訪問代碼。
第一步:為EntityFrameworkCore添加Microsoft.EntityFrameworkCore.Tools包
在EntityFrameworkCore.csproj文件中添加如下代碼:
<ItemGroup><PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="5.0.*"><IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets><PrivateAssets>compile; contentFiles; build; buildMultitargeting; buildTransitive; analyzers; native</PrivateAssets></PackageReference> </ItemGroup>第二步,創建design time DbContext factory
在EntityFrameworkCore項目中創建一個實現了IDesignTimeDbContextFactory<T>的類:
using System.IO; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Design; using Microsoft.Extensions.Configuration;namespace UnifiedContextsDemo.EntityFrameworkCore {public class UnifiedContextsDemoDbContextFactory : IDesignTimeDbContextFactory<UnifiedContextsDemoDbContext>{public UnifiedContextsDemoDbContext CreateDbContext(string[] args){UnifiedContextsDemoEfCoreEntityExtensionMappings.Configure();var configuration = BuildConfiguration();var builder = new DbContextOptionsBuilder<UnifiedContextsDemoDbContext>().UseSqlServer(configuration.GetConnectionString("Default"));return new UnifiedContextsDemoDbContext(builder.Options);}private static IConfigurationRoot BuildConfiguration(){var builder = new ConfigurationBuilder().SetBasePath(Path.Combine(Directory.GetCurrentDirectory(), "../UnifiedContextsDemo.DbMigrator/")).AddJsonFile("appsettings.json", optional: false);return builder.Build();}} }這些代碼基本上是從EntityFrameworkCore.DbMigrations這個項目中粘貼過來的,重命名了一下并且將里面的DbContext替換成了EntityFrameworkCore項目中的那個DbContext。
第三步,創建數據庫方案遷移類
將EntityFrameworkCore...DbSchemaMigrator(...代表了你項目的名字)類復制到EntityFrameworkCore項目下,并且將其中的DbContext替換成EntityFrameworkCore項目中的那個真正的DbContext,在我的示例項目中,代碼是這樣的:
using System; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using UnifiedContextsDemo.Data; using Volo.Abp.DependencyInjection;namespace UnifiedContextsDemo.EntityFrameworkCore {public class EntityFrameworkCoreUnifiedContextsDemoDbSchemaMigrator: IUnifiedContextsDemoDbSchemaMigrator, ITransientDependency{private readonly IServiceProvider _serviceProvider;public EntityFrameworkCoreUnifiedContextsDemoDbSchemaMigrator(IServiceProvider serviceProvider){_serviceProvider = serviceProvider;}public async Task MigrateAsync(){/* We intentionally resolving the UnifiedContextsDemoMigrationsDbContext* from IServiceProvider (instead of directly injecting it)* to properly get the connection string of the current tenant in the* current scope.*/await _serviceProvider.GetRequiredService<UnifiedContextsDemoDbContext>().Database.MigrateAsync();}} }第四步,轉移模塊的配置
遷移DbContext(在遷移項目中定義的那個DbContext)通常包含你使用的每個模塊的builder.ConfigureXXX()這樣的代碼行。我們可以將這些行移動到EntityFrameworkCore項目中的實際DbContext中。另外,刪除AppUser的數據庫映射(我們將刪除這個實體)。或者,你可以將你自己的實體的數據庫映射代碼從… DbContextModelCreatingExtensions類放在實際DbContext的OnModelCreating方法中,并刪除靜態擴展類。
注:上文提到的AppUser數據庫映射這些代碼是包含在EntityFramworkCore的DbContext中,具體如下:
/* Configure the shared tables (with included modules) here */builder.Entity<AppUser>(b =>{b.ToTable(AbpIdentityDbProperties.DbTablePrefix + "Users"); //Sharing the same table "AbpUsers" with the IdentityUserb.ConfigureByConvention();b.ConfigureAbpUser();/* Configure mappings for your additional properties* Also see the BlazorEfCoreEntityExtensionMappings class*/});最終修改后的DbContext是下面這個樣子的:
using Microsoft.EntityFrameworkCore; using UnifiedContextsDemo.Users; using Volo.Abp.AuditLogging.EntityFrameworkCore; using Volo.Abp.BackgroundJobs.EntityFrameworkCore; using Volo.Abp.Data; using Volo.Abp.EntityFrameworkCore; using Volo.Abp.FeatureManagement.EntityFrameworkCore; using Volo.Abp.Identity.EntityFrameworkCore; using Volo.Abp.IdentityServer.EntityFrameworkCore; using Volo.Abp.PermissionManagement.EntityFrameworkCore; using Volo.Abp.SettingManagement.EntityFrameworkCore; using Volo.Abp.TenantManagement.EntityFrameworkCore;namespace UnifiedContextsDemo.EntityFrameworkCore {[ConnectionStringName("Default")]public class UnifiedContextsDemoDbContext: AbpDbContext<UnifiedContextsDemoDbContext>{public DbSet<AppUser> Users { get; set; }/* Add DbSet properties for your Aggregate Roots / Entities here.* Also map them inside UnifiedContextsDemoDbContextModelCreatingExtensions.ConfigureUnifiedContextsDemo*/public UnifiedContextsDemoDbContext(DbContextOptions<UnifiedContextsDemoDbContext> options): base(options){}protected override void OnModelCreating(ModelBuilder builder){base.OnModelCreating(builder);builder.ConfigurePermissionManagement();builder.ConfigureSettingManagement();builder.ConfigureBackgroundJobs();builder.ConfigureAuditLogging();builder.ConfigureIdentity();builder.ConfigureIdentityServer();builder.ConfigureFeatureManagement();builder.ConfigureTenantManagement();/* Configure your own tables/entities inside here *///builder.Entity<YourEntity>(b =>//{// b.ToTable(UnifiedContextsDemoConsts.DbTablePrefix + "YourEntities", UnifiedContextsDemoConsts.DbSchema);// b.ConfigureByConvention(); //auto configure for the base class props// //...//});}} }第五步,從解決方案中移除EntityFrameworkCore.DbMigrations?項目
將EntityFrameworkCore.DbMigrations移除并且將一切引用該項目替換為引用EntityFrameWorkCore項目。
同時,EntityFrameworkCore.DbMigrations項目的作用現在也變更為了EntityFrameworkCore項目。
在這個例子中,我需要將DbMigrator,Web和EntityFrameworkCore.Tests?這三個項目的對EntityFrameworkCore.DbMigrations的引用變更為EntityframeworkCore項目。
第六步,刪除AppUser實體類
你需要刪除AppUser實體類,因為Abp沒有辦法在兩個沒有繼承關系的類上面映射同一張表。
所以應該刪除它以及和他相關的內容,如果你要查詢有關用戶的內容,你應該用IdentityUser來代替。可以在官方文檔中查看與自定義屬性和AppUser相關的內容。
第七步,創建或者移動遷移內容
現在我們已經刪除了EntityFrameworkCore.DbMigrations項目。接下來我們要考慮關于數據庫遷移的事情了。如果你要保持數據庫的遷移歷史,你需要從EntityFrameworkCore.DbMigrations項目吧Migrations目錄中的內容拷貝到EntityFrameworkCore,并且將內容中的DbContext手工的替換為EntityFrameworkCore項目中定義的DbContext。
另一種做法是清除項目中的遷移歷史,并在數據庫中的已提交的遷移歷史上繼續,那你需要做的是在EntityFrameworkCore項目中創建一個數據庫遷移,并在該項目的根目錄下面執行下面的命令:
dotnet ef migrations add InitialUnified你無疑需要為這個遷移命令起一個全新的名字,這個遷移肯定會生成一堆內容,你需要小心的將Up和Down這兩個方法中的內容全部刪除,然后就可以將這個遷移(實際上是一個空的遷移)應用到數據庫了:
dotnet ef database update這個操作不會對數據庫造成任何更改,畢竟你已經將Up和Down方法里面的內容全刪除了。接下來,你就可以像平常一樣進行接下來的操作了。
AppUser 實體和自定義擴展屬性
現在數據庫映射邏輯、解決方案結構、遷移以及我們接下來要做的事情變得更簡單了。
作為缺點來說,我們需要刪除AppUser實體,它和Identity Module中定義的IdentityUser共享了數據庫中的同一張表。幸運的是,當你需要在已存在的實體上(比如Identity module中定義的IdentityUser)增加一些自定義的屬性時,Abp提供了一個相當靈活的系統。在這一節中,我將演示如何在IdentityUser上面增加一些自定義的屬性,并在程序編碼和數據庫查詢上應用這些自定義的字段。
關于這些內容我已經作為單獨的pr發布到github上,你可以點擊這個鏈接進行查看:
https://github.com/abpframework/abp-samples/pull/89github.com
聲明一個自定義的屬性
啟動模板中有一個關于在已存在實體上自定義屬性的入口,這個入口在Domain.Share項目下面,...ModuleExtensionConfigurator.cs(...代表你項目的名稱)這個文件中。打開這個文件并在ConfigureExtraProperties方法中下如下代碼:
ObjectExtensionManager.Instance.Modules().ConfigureIdentity(identity =>{identity.ConfigureUser(user =>{user.AddOrUpdateProperty<string>( //property type: string"SocialSecurityNumber", //property nameproperty =>{//validation rulesproperty.Attributes.Add(new RequiredAttribute());property.Attributes.Add(new StringLengthAttribute(64));});});});完事兒后,運行程序并在User table上面你可以看到這個屬性:
新的SocialSecurityNumber屬性也會在創建和更新Modal中顯示并應用校驗規則。查看如下鏈接了解關于擴展屬性的一切信息:
https://docs.abp.io/en/abp/latest/Module-Entity-Extensionsdocs.abp.io
映射到數據庫表
默認情況下,Abp將所有自定義的屬性保存在數據庫表中的ExtraProperties屬性上,作為一個JSON保存 。如果你想要將自定義的字段作為單獨的表字段保存,你需要在EntityFrameworkCore項目中定義的...EfCoreEntityExtensionMappings.cs文件(...代表你項目的名字)上進行編碼定義(在OneTimeRunner.Run方法中):
ObjectExtensionManager.Instance.MapEfCoreProperty<IdentityUser, string>("SocialSecurityNumber",(entityBuilder, propertyBuilder) =>{propertyBuilder.HasMaxLength(64).IsRequired().HasDefaultValue("");});這個完事兒后,你需要定義新的數據庫遷移方案,將你的新擴展的屬性進行遷移(在EntityframeworkCore項目下):
dotnet ef migrations add Added_SocialSecurityNumber_To_IdentityUser這會在EntityframeworkCore項目下面新增一個遷移文件,然后你要將這個遷移應用到數據庫:
dotnet ef database update你也可以運行.DbMigrator項目來應用遷移,這個項目的作用就在于此。
這會在數據庫AbpUsers表上創建一個SocialSecurityNumber字段。
在應用程序代碼中使用自定義字段
現在,我們可以在IdentityUser實體上使用GetProperty和SetProperty這兩個方法來使用我們自定義的屬性:
public class MyUserService : ITransientDependency {private readonly IRepository<IdentityUser, Guid> _userRepository;public MyUserService(IRepository<IdentityUser, Guid> userRepository){_userRepository = userRepository;}public async Task SetSocialSecurityNumberDemoAsync(string userName, string number){var user = await _userRepository.GetAsync(u => u.UserName == userName);user.SetProperty("SocialSecurityNumber", number);await _userRepository.UpdateAsync(user);}public async Task<string> GetSocialSecurityNumberDemoAsync(string userName){var user = await _userRepository.GetAsync(u => u.UserName == userName);return user.GetProperty<string>("SocialSecurityNumber");} } 上面的代碼中我們使用了”SocialSecurityNumber“硬編碼來直接調用,更好的做法是我們可以定義一些擴展方法來包裝這種調用。下面我們改進這種做法:
public static class MyUserExtensions {public const string SocialSecurityNumber = "SocialSecurityNumber";public static void SetSocialSecurityNumber(this IdentityUser user, string number){user.SetProperty(SocialSecurityNumber, number);}public static string GetSocialSecurityNumber(this IdentityUser user){return user.GetProperty<string>(SocialSecurityNumber);} }定義后擴展方法后,我們改進一開始的那種調用:
public async Task SetSocialSecurityNumberDemoAsync(string userName, string number) {var user = await _userRepository.GetAsync(u => u.UserName == userName);user.SetSocialSecurityNumber(number); //Using the new extension propertyawait _userRepository.UpdateAsync(user); }public async Task<string> GetSocialSecurityNumberDemoAsync(string userName) {var user = await _userRepository.GetAsync(u => u.UserName == userName);return user.GetSocialSecurityNumber(); //Using the new extension property }自定義屬性的查詢
你可能會基于自定義的屬性做一些查詢,我們會使用Entity Framework的API來完成,基于此,我們這里給出兩個解決方案:
1、引用Microsoft.EntityFrameworkCore包到你的項目中(Domain項目或者Application項目,具體看你的需求)。
2、在Domain中創建一個repository接口,并在EntityFrameworkCore項目中實現它。
我更傾向于第二個方案,所以我在repository接口中定義一些方法先:
using System; using System.Threading.Tasks; using Volo.Abp.Domain.Repositories; using Volo.Abp.Identity;namespace UnifiedContextsDemo.Users {public interface IMyUserRepository : IRepository<IdentityUser, Guid>{Task<IdentityUser> FindBySocialSecurityNumber(string number);} }然后在EntityframeworkCore項目中實現它:
using System; using System.Linq; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using UnifiedContextsDemo.EntityFrameworkCore; using Volo.Abp.Domain.Repositories.EntityFrameworkCore; using Volo.Abp.EntityFrameworkCore; using Volo.Abp.Identity;namespace UnifiedContextsDemo.Users {public class MyUserRepository: EfCoreRepository<UnifiedContextsDemoDbContext, IdentityUser, Guid>,IMyUserRepository{public MyUserRepository(IDbContextProvider<UnifiedContextsDemoDbContext> dbContextProvider): base(dbContextProvider){}public async Task<IdentityUser> FindBySocialSecurityNumber(string number){var dbContext = await GetDbContextAsync();return await dbContext.Set<IdentityUser>().Where(u => EF.Property<string>(u, "SocialSecurityNumber") == number).FirstOrDefaultAsync();}} }注意:使用一個常量而不是字符串硬編碼來搞這樣更好一些。現在,我們可以在Service里面注入repository來使用了:)
public class MyUserService : ITransientDependency {private readonly IMyUserRepository _userRepository;public MyUserService(IMyUserRepository userRepository){_userRepository = userRepository;}//...other methodspublic async Task<IdentityUser> FindBySocialSecurityNumberDemoAsync(string number){return await _userRepository.FindBySocialSecurityNumber(number);} }總結
這篇文章描述了如何刪除EntityFrameworkCore.DbMigrations項目來簡化你的數據庫映射、數據庫遷移以及應用程序編碼。在4.4這個版本中,我們已經在啟動模板中移除了這個項目了。
源碼
https://github.com/abpframework/abp-samples/tree/master/UnifiedEfCoreMigrations
總結
以上是生活随笔為你收集整理的ABP Vnext 4.4:统一Ef Core的DbContext/移除EF Core Migrations项目的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何入门.NET Core ? 推荐这1
- 下一篇: #if DEBUG 和 if (env.