ABP Vnext 4.4:统一Ef Core的DbContext/移除EF Core Migrations项目
Abp vnext 4.4出現(xiàn)了一個(gè)比較重大的變更:在Startup template中移除了EF Core Migrations項(xiàng)目,本文翻譯自community.abp.io/articl
由于本文發(fā)布的時(shí)候Abp vnext的版本還沒(méi)有到4.4,所以本文演示了如何從4.4以前的版本移除EntityFrameworkCore.DbMigrations這個(gè)項(xiàng)目,并且使用唯一的一個(gè)DbContext來(lái)進(jìn)行數(shù)據(jù)庫(kù)的映射和基于Code-First模式的遷移。
該項(xiàng)目的github地址如下:github.com/abpframework
動(dòng)機(jī)/背景
如果你使用Ef core作為數(shù)據(jù)庫(kù)provider創(chuàng)建一個(gè)解決方案,那么會(huì)有兩個(gè)與ef core有關(guān)的項(xiàng)目:
EntityFrameworkCore這個(gè)項(xiàng)目包含了你的應(yīng)用的真正的DbContext,它包含了所有的數(shù)據(jù)庫(kù)映射和你的Repository的實(shí)現(xiàn)。
另一方面,EntityFrameworkCore.DbMigrations?項(xiàng)目包含了另一個(gè)DbContext用來(lái)創(chuàng)建和施行數(shù)據(jù)庫(kù)遷移。它包含了你所使用的所有模塊的數(shù)據(jù)庫(kù)映射,所以你有一個(gè)單獨(dú)并統(tǒng)一的數(shù)據(jù)庫(kù)架構(gòu)/方案。
當(dāng)時(shí)這么做主要有兩個(gè)原因:
你真正的DbContext保持了簡(jiǎn)單和集中(focused)。它只包含了你自己應(yīng)用中的實(shí)體相關(guān)的內(nèi)容并且不包含你所使用的關(guān)于其他模塊的內(nèi)容。
你可以創(chuàng)建自己的類,映射到依賴模塊的表。例如,AppUser實(shí)體(包含在下載的解決方案中)映射到數(shù)據(jù)庫(kù)中的AbpUsers表,而AbpUsers表實(shí)際上映射到Identity Module的IdentityUser實(shí)體。這意味著它們共享相同的數(shù)據(jù)庫(kù)表。與IdentityServer相比,AppUser包含的屬性更少。您只添加您需要的屬性,而不是更多。這還允許您根據(jù)自定義需求向AppUser添加新的標(biāo)準(zhǔn)(類型安全)屬性,只要您仔細(xì)地管理數(shù)據(jù)庫(kù)映射。
對(duì)于這個(gè)方面的說(shuō)明我們?cè)诠俜降奈臋n中有詳細(xì)的說(shuō)明。然而,當(dāng)你重用那些你依賴的模塊的表時(shí),會(huì)存在一些問(wèn)題,那就是這樣的架構(gòu)會(huì)導(dǎo)致你的數(shù)據(jù)庫(kù)映射變得復(fù)雜。許多開(kāi)發(fā)者在做一些諸如映射這些類/實(shí)體的工作時(shí),會(huì)變得迷茫和犯錯(cuò),特別是當(dāng)他們想要將這些實(shí)體和其他實(shí)體聯(lián)系起來(lái)時(shí)。
所以,我們決定在4.4的版本中取消這種分離,刪除EntityFrameworkCore.DbMigrations這個(gè)項(xiàng)目。新版本的abp vnext中將只包含EntityFrameworkCore這個(gè)項(xiàng)目并且只擁有一個(gè)DbContext。
如果你今天就想嘗試這么干,請(qǐng)接著往下看。
警告
新的設(shè)計(jì)有一個(gè)缺點(diǎn)(軟件開(kāi)發(fā)中的一切都是一種權(quán)衡)。我們需要?jiǎng)h除AppUser實(shí)體,因?yàn)镋F Core不能在沒(méi)有繼承關(guān)系的情況下將兩個(gè)類映射到單個(gè)表。我將在本文后面介紹這一點(diǎn),并提供處理它的建議。步驟
我們的目標(biāo)是在EntityFrameworkCore項(xiàng)目中啟用數(shù)據(jù)庫(kù)遷移,移除EntityFrameworkCore.DbMigrations項(xiàng)目并根據(jù)該包重新訪問(wèn)代碼。
第一步:為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>第二步,創(chuàng)建design time DbContext factory
在EntityFrameworkCore項(xiàng)目中創(chuàng)建一個(gè)實(shí)現(xiàn)了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這個(gè)項(xiàng)目中粘貼過(guò)來(lái)的,重命名了一下并且將里面的DbContext替換成了EntityFrameworkCore項(xiàng)目中的那個(gè)DbContext。
第三步,創(chuàng)建數(shù)據(jù)庫(kù)方案遷移類
將EntityFrameworkCore...DbSchemaMigrator(...代表了你項(xiàng)目的名字)類復(fù)制到EntityFrameworkCore項(xiàng)目下,并且將其中的DbContext替換成EntityFrameworkCore項(xiàng)目中的那個(gè)真正的DbContext,在我的示例項(xiàng)目中,代碼是這樣的:
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();}} }第四步,轉(zhuǎn)移模塊的配置
遷移DbContext(在遷移項(xiàng)目中定義的那個(gè)DbContext)通常包含你使用的每個(gè)模塊的builder.ConfigureXXX()這樣的代碼行。我們可以將這些行移動(dòng)到EntityFrameworkCore項(xiàng)目中的實(shí)際DbContext中。另外,刪除AppUser的數(shù)據(jù)庫(kù)映射(我們將刪除這個(gè)實(shí)體)。或者,你可以將你自己的實(shí)體的數(shù)據(jù)庫(kù)映射代碼從… DbContextModelCreatingExtensions類放在實(shí)際DbContext的OnModelCreating方法中,并刪除靜態(tài)擴(kuò)展類。
注:上文提到的AppUser數(shù)據(jù)庫(kù)映射這些代碼是包含在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是下面這個(gè)樣子的:
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?項(xiàng)目
將EntityFrameworkCore.DbMigrations移除并且將一切引用該項(xiàng)目替換為引用EntityFrameWorkCore項(xiàng)目。
同時(shí),EntityFrameworkCore.DbMigrations項(xiàng)目的作用現(xiàn)在也變更為了EntityFrameworkCore項(xiàng)目。
在這個(gè)例子中,我需要將DbMigrator,Web和EntityFrameworkCore.Tests?這三個(gè)項(xiàng)目的對(duì)EntityFrameworkCore.DbMigrations的引用變更為EntityframeworkCore項(xiàng)目。
第六步,刪除AppUser實(shí)體類
你需要?jiǎng)h除AppUser實(shí)體類,因?yàn)锳bp沒(méi)有辦法在兩個(gè)沒(méi)有繼承關(guān)系的類上面映射同一張表。
所以應(yīng)該刪除它以及和他相關(guān)的內(nèi)容,如果你要查詢有關(guān)用戶的內(nèi)容,你應(yīng)該用IdentityUser來(lái)代替。可以在官方文檔中查看與自定義屬性和AppUser相關(guān)的內(nèi)容。
第七步,創(chuàng)建或者移動(dòng)遷移內(nèi)容
現(xiàn)在我們已經(jīng)刪除了EntityFrameworkCore.DbMigrations項(xiàng)目。接下來(lái)我們要考慮關(guān)于數(shù)據(jù)庫(kù)遷移的事情了。如果你要保持?jǐn)?shù)據(jù)庫(kù)的遷移歷史,你需要從EntityFrameworkCore.DbMigrations項(xiàng)目吧Migrations目錄中的內(nèi)容拷貝到EntityFrameworkCore,并且將內(nèi)容中的DbContext手工的替換為EntityFrameworkCore項(xiàng)目中定義的DbContext。
另一種做法是清除項(xiàng)目中的遷移歷史,并在數(shù)據(jù)庫(kù)中的已提交的遷移歷史上繼續(xù),那你需要做的是在EntityFrameworkCore項(xiàng)目中創(chuàng)建一個(gè)數(shù)據(jù)庫(kù)遷移,并在該項(xiàng)目的根目錄下面執(zhí)行下面的命令:
dotnet ef migrations add InitialUnified你無(wú)疑需要為這個(gè)遷移命令起一個(gè)全新的名字,這個(gè)遷移肯定會(huì)生成一堆內(nèi)容,你需要小心的將Up和Down這兩個(gè)方法中的內(nèi)容全部刪除,然后就可以將這個(gè)遷移(實(shí)際上是一個(gè)空的遷移)應(yīng)用到數(shù)據(jù)庫(kù)了:
dotnet ef database update這個(gè)操作不會(huì)對(duì)數(shù)據(jù)庫(kù)造成任何更改,畢竟你已經(jīng)將Up和Down方法里面的內(nèi)容全刪除了。接下來(lái),你就可以像平常一樣進(jìn)行接下來(lái)的操作了。
AppUser 實(shí)體和自定義擴(kuò)展屬性
現(xiàn)在數(shù)據(jù)庫(kù)映射邏輯、解決方案結(jié)構(gòu)、遷移以及我們接下來(lái)要做的事情變得更簡(jiǎn)單了。
作為缺點(diǎn)來(lái)說(shuō),我們需要?jiǎng)h除AppUser實(shí)體,它和Identity Module中定義的IdentityUser共享了數(shù)據(jù)庫(kù)中的同一張表。幸運(yùn)的是,當(dāng)你需要在已存在的實(shí)體上(比如Identity module中定義的IdentityUser)增加一些自定義的屬性時(shí),Abp提供了一個(gè)相當(dāng)靈活的系統(tǒng)。在這一節(jié)中,我將演示如何在IdentityUser上面增加一些自定義的屬性,并在程序編碼和數(shù)據(jù)庫(kù)查詢上應(yīng)用這些自定義的字段。
關(guān)于這些內(nèi)容我已經(jīng)作為單獨(dú)的pr發(fā)布到github上,你可以點(diǎn)擊這個(gè)鏈接進(jìn)行查看:
https://github.com/abpframework/abp-samples/pull/89github.com
聲明一個(gè)自定義的屬性
啟動(dòng)模板中有一個(gè)關(guān)于在已存在實(shí)體上自定義屬性的入口,這個(gè)入口在Domain.Share項(xiàng)目下面,...ModuleExtensionConfigurator.cs(...代表你項(xiàng)目的名稱)這個(gè)文件中。打開(kāi)這個(gè)文件并在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));});});});完事兒后,運(yùn)行程序并在User table上面你可以看到這個(gè)屬性:
新的SocialSecurityNumber屬性也會(huì)在創(chuàng)建和更新Modal中顯示并應(yīng)用校驗(yàn)規(guī)則。查看如下鏈接了解關(guān)于擴(kuò)展屬性的一切信息:
https://docs.abp.io/en/abp/latest/Module-Entity-Extensionsdocs.abp.io
映射到數(shù)據(jù)庫(kù)表
默認(rèn)情況下,Abp將所有自定義的屬性保存在數(shù)據(jù)庫(kù)表中的ExtraProperties屬性上,作為一個(gè)JSON保存 。如果你想要將自定義的字段作為單獨(dú)的表字段保存,你需要在EntityFrameworkCore項(xiàng)目中定義的...EfCoreEntityExtensionMappings.cs文件(...代表你項(xiàng)目的名字)上進(jìn)行編碼定義(在OneTimeRunner.Run方法中):
ObjectExtensionManager.Instance.MapEfCoreProperty<IdentityUser, string>("SocialSecurityNumber",(entityBuilder, propertyBuilder) =>{propertyBuilder.HasMaxLength(64).IsRequired().HasDefaultValue("");});這個(gè)完事兒后,你需要定義新的數(shù)據(jù)庫(kù)遷移方案,將你的新擴(kuò)展的屬性進(jìn)行遷移(在EntityframeworkCore項(xiàng)目下):
dotnet ef migrations add Added_SocialSecurityNumber_To_IdentityUser這會(huì)在EntityframeworkCore項(xiàng)目下面新增一個(gè)遷移文件,然后你要將這個(gè)遷移應(yīng)用到數(shù)據(jù)庫(kù):
dotnet ef database update你也可以運(yùn)行.DbMigrator項(xiàng)目來(lái)應(yīng)用遷移,這個(gè)項(xiàng)目的作用就在于此。
這會(huì)在數(shù)據(jù)庫(kù)AbpUsers表上創(chuàng)建一個(gè)SocialSecurityNumber字段。
在應(yīng)用程序代碼中使用自定義字段
現(xiàn)在,我們可以在IdentityUser實(shí)體上使用GetProperty和SetProperty這兩個(gè)方法來(lái)使用我們自定義的屬性:
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“硬編碼來(lái)直接調(diào)用,更好的做法是我們可以定義一些擴(kuò)展方法來(lái)包裝這種調(diào)用。下面我們改進(jìn)這種做法:
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);} }定義后擴(kuò)展方法后,我們改進(jìn)一開(kāi)始的那種調(diào)用:
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 }自定義屬性的查詢
你可能會(huì)基于自定義的屬性做一些查詢,我們會(huì)使用Entity Framework的API來(lái)完成,基于此,我們這里給出兩個(gè)解決方案:
1、引用Microsoft.EntityFrameworkCore包到你的項(xiàng)目中(Domain項(xiàng)目或者Application項(xiàng)目,具體看你的需求)。
2、在Domain中創(chuàng)建一個(gè)repository接口,并在EntityFrameworkCore項(xiàng)目中實(shí)現(xiàn)它。
我更傾向于第二個(gè)方案,所以我在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項(xiàng)目中實(shí)現(xiàn)它:
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();}} }注意:使用一個(gè)常量而不是字符串硬編碼來(lái)搞這樣更好一些。現(xiàn)在,我們可以在Service里面注入repository來(lái)使用了:)
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);} }總結(jié)
這篇文章描述了如何刪除EntityFrameworkCore.DbMigrations項(xiàng)目來(lái)簡(jiǎn)化你的數(shù)據(jù)庫(kù)映射、數(shù)據(jù)庫(kù)遷移以及應(yīng)用程序編碼。在4.4這個(gè)版本中,我們已經(jīng)在啟動(dòng)模板中移除了這個(gè)項(xiàng)目了。
源碼
https://github.com/abpframework/abp-samples/tree/master/UnifiedEfCoreMigrations
總結(jié)
以上是生活随笔為你收集整理的ABP Vnext 4.4:统一Ef Core的DbContext/移除EF Core Migrations项目的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 如何入门.NET Core ? 推荐这1
- 下一篇: #if DEBUG 和 if (env.