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用来创建和施行数据库迁移。它包含了你所使用的所有模块的数据库映射,所以你有一个单独并统一的数据库架构/方案。

当时这么做主要有两个原因:

  1. 你真正的DbContext保持了简单和集中(focused)。它只包含了你自己应用中的实体相关的内容并且不包含你所使用的关于其他模块的内容。

  2. 你可以创建自己的类,映射到依赖模块的表。例如,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,WebEntityFrameworkCore.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项目相关推荐

  1. 阿星Plus:基于abp vNext开源一个博客网站

    作为微软最早迈向开源的重要软件之一,.NET 5的发布具有重要意义! 微软希望 .NET Framework 开发者能够迁移他们的代码和应用到 .NET 5.0 上,为明年发布的 .NET 6.0 将 ...

  2. [Abp vNext 源码分析] - 4. 工作单元

    一.简要说明 统一工作单元是一个比较重要的基础设施组件,它负责管理整个业务流程当中涉及到的数据库事务,一旦某个环节出现异常自动进行回滚处理. 在 ABP vNext 框架当中,工作单元被独立出来作为一 ...

  3. ABP vNext 对接 Ant Design Vue 实现分页查询

    本文内容 ABP vNext中的分页查询 STable组件中的分页查询 实现参数转换层 最终对接效果 在 上一篇 博客中,博主和大家分享了如何在 EF Core 中实现多租户架构.在这一过程中,博主主 ...

  4. 给 ABP vNext 应用安装私信模块

    在上一节五分钟完成 ABP vNext 通讯录 App 开发 中,我们用完成了通讯录 App 的基础开发. 这本章节,我们会给通讯录 App 安装私信模块,使不同用户能够通过相互发送消息,并接收新私信 ...

  5. ABP vNext 的实体与服务扩展技巧分享

    使用 ABP vNext 有一个月左右啦,这中间最大的一个收获是:ABP vNext 的开发效率真的是非常好,只要你愿意取遵循它模块化.DDD 的设计思想.因为官方默认实现了身份.审计.权限.定时任务 ...

  6. 基于 abp vNext 和 .NET Core 开发博客项目 - 统一规范API,包装返回模型

    基于 abp vNext 和 .NET Core 开发博客项目 - 统一规范API,包装返回模型 转载于:https://github.com/Meowv/Blog 在实际开发过程中,每个公司可能不尽 ...

  7. Abp vNext 自定义 Ef Core 仓储引发异常

    Abp vNext 自定义 Ef Core 仓储引发异常 参考文章: (1)Abp vNext 自定义 Ef Core 仓储引发异常 (2)https://www.cnblogs.com/myzony ...

  8. 基于 abp vNext 和 .NET Core 开发博客项目 - 博客接口实战篇(一)

    基于 abp vNext 和 .NET Core 开发博客项目 - 博客接口实战篇(一) 转载于:https://github.com/Meowv/Blog 现在博客数据库中的数据是比较混乱的,为了看 ...

  9. 基于 abp vNext 和 .NET Core 开发博客项目 - 自定义仓储之增删改查

    基于 abp vNext 和 .NET Core 开发博客项目 - 自定义仓储之增删改查 转载于:https://github.com/Meowv/Blog 本篇说一下自定义仓储的实现方式,其实在ab ...

最新文章

  1. 跟无闻学习GO Web 编程(一) -- go 开发环境搭配(win7 64bit)
  2. java.lang.LinkageError: JAXB 2.0 API is being loaded from the bootstrap classloader, but this RI(xxx
  3. scenejs的一点Cameras小笔记
  4. python if写在return 后面_python中return如何写
  5. 计算机考研英语词汇书,求助:有知道电脑背考研英语单词的
  6. SNF软件开发机器人-子系统-导出-导入功能-多人合作时这个功能经常用到
  7. php %3c%3c%3cxml 报错,代码审计| APPCMS SQL-XSS-CSRF-SHELL
  8. 团队项目讨论及计划修订版
  9. 每日打卡熬夜挑战比赛/文字搭建教程
  10. Android MultiAutoCompleteTextView多文本输入提示
  11. #华为云·寻找黑马程序员#【代码重构之路】如何“消除”if/else
  12. 基于JAVA+SpringMVC+MYSQL的企业员工管理系统
  13. 程序员必备CDN加速jsDelivr+Gihub远程仓库
  14. 如何快速的把m4a转换成mp3格式
  15. python 同时输出国际和国内BMI值
  16. 提高代码质量——使用Jest和Sinon给已有的代码添加单元测试
  17. 解决单元测试时报Could not instantiate问题
  18. ios获取所有相册的视频并播放
  19. SQL基础知识(二)
  20. ognlognl表达式 研究

热门文章

  1. 运维自动化之基于python语言的文字界面的运维管理软件
  2. [转]table中设置tr行间距
  3. Memcached简介
  4. Linux下SVN安装
  5. USENIX 最佳论文奖:擦除 Windows Azure 存储编码
  6. ASP.NET下MVC设计模式的实现
  7. Macbook全系列详细分析及购机指南
  8. python 新闻摘要_每日新闻摘要:运营商承诺他们不再出售您的位置…
  9. os x 启动引导_什么是OS X的启动板以及它如何工作?
  10. React 深入学习:React 更新队列