diff --git a/docs/zh-Hans/Entity-Framework-Core-Migrations.md b/docs/zh-Hans/Entity-Framework-Core-Migrations.md new file mode 100644 index 0000000000..3273ed277e --- /dev/null +++ b/docs/zh-Hans/Entity-Framework-Core-Migrations.md @@ -0,0 +1,320 @@ + +# EF核心高级数据库迁移 + +本文首先介绍[应用程序启动模板](Startup-Templates/Application.md)提供的**默认结构**,并讨论您可能希望为自己的应用程序实现的**各种场景**. + +> 本文档适用于希望完全理解和自定义[应用程序启动模板](Startup-Templates/Application.md)附带的数据库结构的人员. 如果你只是想创建实体和管理代码优先(code first)迁移,只需要遵循[启动教程](Tutorials/Index.md). + +## 关于EF Core 代码优先迁移 + +Entity Framework Core 提供了一种简单强大[数据库迁移系统](https://docs.microsoft.com/zh-cn/ef/core/managing-schemas/migrations/). ABP框架[启动模板](Startup-Templates/Index.md)使用这个系统,让你以标准的方式开发你的应用程序. + +但是EF Core迁移系统在[模块化环境中不是很好],在模块化环境中,每个模块都维护**自己的数据库架构**,而实际上两个或多个模块可以**共享一个数据库**. + +由于ABP框架在所有方面都关心模块化,所以它为这个问题提供了**解决方案**. 如果你需要**自定义数据库结构**,那么应当了解这个解决方案. + +> 参阅[EF Core文档](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/)充分了解EF Core Code First迁移,以及为什么需要这样的系统. + +## 默认解决方案与数据库配置 + +当你[创建一个新的Web应用程序](https://abp.io/get-started)(使用EF Core,它是默认的数据库提供程序),你的解决方案结构类似下图: + +![bookstore-visual-studio-solution-v3](images/bookstore-visual-studio-solution-v3.png) + +> 实际的解决方案结构可能会根据你的偏好有所不同,但是数据库部分是相同的. + +### 数据库架构 + +启动模板已预安装了一些[应用程序模块](Modules/Index.md). 解决方案的每一层都有相应的模块包引用. 所以 `.EntityFrameworkCore` 项目含有使用 `EntityFrameworkCore` 模块的Nuget的引用: + +![bookstore-efcore-dependencies](images/bookstore-efcore-dependencies.png) + +通过这种方式,你可以看到所有的`.EntityFrameworkCore`项目下的EF Core的依赖. + +> 除了模块引用之外,它还引用了 `Volo.Abp.EntityFrameworkCore.SqlServer` 包,因为启动模板预配置的是Sql Server. 参阅文档了解如何[切换到其它DBMS](Entity-Framework-Core-Other-DBMS.md). + +虽然每个模块在设计上有自己的`DbContext`类,并且可以使用其自己的**物理数据库**,但解决方案的配置是使用**单个共享数据库**如下图所示: + +![single-database-usage](images/single-database-usage.png) + +这是**最简单的配置**,适用于大部分的应用程序. `appsettings.json` 文件有名为`Default`**单个连接字符串**: + +````json +"ConnectionStrings": { + "Default": "..." +} +```` + +所以你有一个**单一的数据库模式**,其中包含**共享**此数据库的模块的所有表. + +ABP框架的[连接字符串](Connection-Strings.md)系统允许你轻松为所需的模块**设置不同的连接字符串**: + +````json +"ConnectionStrings": { + "Default": "...", + "AbpAuditLogging": "..." +} +```` + +示例配置告诉ABP框架[审计日志模块](Modules/Audit-Logging.md)应使用第二个连接字符串. + +然而这仅仅只是开始. 你还需要创建第二个数据库以及里面审计日志表并使用code frist的方法维护数据库表. 本文档的主要目的之一就是指导你了解这样的数据库分离场景. + +#### 模块表 + +每个模块都使用自己的数据库表. 例如[身份模块](Modules/Identity.md)有一些表来管理系统中的用户和角色. + +#### 表前缀 + +由于所有模块都允许共享一个数据库(这是默认配置),所以模块通常使用前缀来对自己的表进行分组. + +基础模块(如[身份](Modules/Identity.md), [租户管理](Modules/Tenant-Management.md) 和 [审计日志](Modules/Audit-Logging.md))使用 `Abp` 前缀, 其他的模块使用自己的前缀. 如[Identity Server](Modules/IdentityServer.md) 模块使用前缀 `IdentityServer`. + +如果你愿意,你可以为你的应用程序的模块更改数据库表前缀. +例: + +````csharp +Volo.Abp.IdentityServer.AbpIdentityServerDbProperties.DbTablePrefix = "Ids"; +```` + +这段代码更改了[Identity Server](Modules/IdentityServer.md)的前缀. 在应用程序的最开始编写这段代码. + +> 每个模块还定义了 `DbSchema` 属性,你可以在支持schema的数据库中使用它. + +### 项目 + +从数据库的角度来看.有三个重要的项目将在下一节中解释. + +#### .EntityFrameworkCore 项目 + +这个项目有应用程序的 `DbContext`类(本例中的 `BookStoreDbContex` ). + +每个模块都使用自己的 `DbContext` 类来访问数据库。同样你的应用程序有它自己的 `DbContext`. 通常在应用程序中使用这个 `DbContet`(如果你遵循最佳实践,应该在自定义[仓储](Repositories.md)中使用). 它几乎是一个空的 `DbContext`,因为你的应用程序在一开始没有任何实体,除了预定义的 `AppUser` 实体: + +````csharp +[ConnectionStringName("Default")] +public class BookStoreDbContext : AbpDbContext +{ + public DbSet Users { get; set; } + + /* Add DbSet properties for your Aggregate Roots / Entities here. */ + + public BookStoreDbContext(DbContextOptions options) + : base(options) + { + + } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + + /* Configure the shared tables (with included modules) here */ + + builder.Entity(b => + { + //Sharing the same table "AbpUsers" with the IdentityUser + b.ToTable("AbpUsers"); + + //Configure base properties + b.ConfigureByConvention(); + b.ConfigureAbpUser(); + + //Moved customization of the "AbpUsers" table to an extension method + b.ConfigureCustomUserProperties(); + }); + + /* Configure your own tables/entities inside the ConfigureBookStore method */ + builder.ConfigureBookStore(); + } +} +```` + +这个简单的 `DbContext` 类仍然需要一些解释: + +* 它定义了一个 `[connectionStringName]` Attribute,它告诉ABP始终为此 `Dbcontext` 使用 `Default` 连接字符串. +* 它从 `AbpDbContext` 而不是标准的 `DbContext` 类继承. 你可以参阅[EF Core集成](Entity-Framework-Core.md)文档了解更多. 现在你需要知道 `AbpDbContext` 基类实现ABP框架的一些约定,为你自动化一些常见的任务. +* 它为 `AppUser` 实体定义了 `DbSet` 属性. `AppUser` 与[身份模块]的 `IdentityUser` 实体共享同一个表(默认名为 `AbpUsers`). 启动模板在应用程序中提供这个实体,因为我们认为用户实体一般需要应用程序中进行定制. +* 构造函数接受一个 `DbContextOptions` 实例. +* 它覆盖了 `OnModelCreating` 方法定义EF Core 映射. + * 首先调用 `base.OnModelCreating` 方法让ABP框架为我们实现基础映射. + * 然后它配置了 `AppUser` 实体的映射. 这个实体有一个特殊的情况(它与Identity模块共享一个表),在下一节中进行解释. + * 最后它调用 `builder.ConfigureBookStore()` 扩展方法来配置应用程序的其他实体. + +在介绍其他数据库相关项目之后,将更详细地说明这个设计. + +#### .EntityFrameworkCore.DbMigrations 项目 + +正如前面所提到的,每个模块(和你的应用程序)有**它们自己**独立的 `DbContext` 类. 每个 `DbContext` 类只定义了自身模块的实体到表的映射,每个模块(包括你的应用程序)在**运行时**都使用相关的 `DbContext` 类. + +如你所知,EF Core Code First迁移系统依赖于 `DbContext` 类来跟踪和生成Code First迁移. 那么我们应该使用哪个 `DbContext` 进行迁移? 答案是它们都不是. `.EntityFrameworkCore.DbMigrations` 项目中定义了另一个 `DbContext` (示例解决方案中的 `BookStoreMigrationsDbContext`). + +##### MigrationsDbContext + +`MigrationsDbContext` 仅用于创建和应用数据库迁移. **不在运行时使用**. 它将所有使用的模块的所有实体到表的映射以及应用程序的映射**合并**. + +通过这种方式你可以创建和维护**单个数据库迁移路径**. 然而这种方法有一些困难,接下来的章节将解释ABP框架如何克服这些困难. 首先以 `BookStoreMigrationsDbContext` 类为例: + +````csharp +/* This DbContext is only used for database migrations. + * It is not used on runtime. See BookStoreDbContext for the runtime DbContext. + * It is a unified model that includes configuration for + * all used modules and your application. + */ +public class BookStoreMigrationsDbContext : AbpDbContext +{ + public BookStoreMigrationsDbContext( + DbContextOptions options) + : base(options) + { + + } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + + /* Include modules to your migration db context */ + builder.ConfigurePermissionManagement(); + builder.ConfigureSettingManagement(); + builder.ConfigureBackgroundJobs(); + builder.ConfigureAuditLogging(); + builder.ConfigureIdentity(); + builder.ConfigureIdentityServer(); + builder.ConfigureFeatureManagement(); + builder.ConfigureTenantManagement(); + + /* Configure customizations for entities from the modules included */ + builder.Entity(b => + { + b.ConfigureCustomUserProperties(); + }); + + /* Configure your own tables/entities inside the ConfigureBookStore method */ + builder.ConfigureBookStore(); + } +} +```` + +##### 共享映射代码 + +第一个问题是: 一个模块使用自己的 `DbContext` 这就需要到数据库的映射. 该 `MigrationsDbContext` 也需要相同的映射创建此模块的数据库表. 我们绝对不希望复制的映射代码. + +解决方案是定义一个扩展方法(在`ModelBuilder`)由两个 `DbContext` 类调用. 所以每个模块都定义了这样的扩展方法. + +For example, the `builder.ConfigureBackgroundJobs()` method call configures the database tables for the [Background Jobs module](Modules/Background-Jobs.md). The definition of this extension method is something like that: + +例如,`builder.ConfigureBackgroundJobs()` 方法调用[后台作业模块]配置数据库表. 扩展方法的定义如下: + +````csharp +public static class BackgroundJobsDbContextModelCreatingExtensions +{ + public static void ConfigureBackgroundJobs( + this ModelBuilder builder, + Action optionsAction = null) + { + var options = new BackgroundJobsModelBuilderConfigurationOptions( + BackgroundJobsDbProperties.DbTablePrefix, + BackgroundJobsDbProperties.DbSchema + ); + + optionsAction?.Invoke(options); + + builder.Entity(b => + { + b.ToTable(options.TablePrefix + "BackgroundJobs", options.Schema); + + b.ConfigureCreationTime(); + b.ConfigureExtraProperties(); + + b.Property(x => x.JobName) + .IsRequired() + .HasMaxLength(BackgroundJobRecordConsts.MaxJobNameLength); + + //... + }); + } +} +```` + +此扩展方法还获取选项用于更改此模块的数据库表前缀和模式,但在这里并不重要. + +最终的应用程序在 `MigrationsDbContext` 类中调用扩展方法, 因此它可以确定此 `MigrationsDbContext` 维护的数据库中包含哪些模块. 如果要创建第二个数据库并将某些模块表移动到第二个数据库,则需要有第二个`MigrationsDbContext` 类,该类仅调用相关模块的扩展方法. 下一部分将详细介绍该主题. + +同样 `ConfigureBackgroundJobs` 方法也被后台作业模块的 `DbContext` 调用: + +````csharp +[ConnectionStringName(BackgroundJobsDbProperties.ConnectionStringName)] +public class BackgroundJobsDbContext + : AbpDbContext, IBackgroundJobsDbContext +{ + public DbSet BackgroundJobs { get; set; } + + public BackgroundJobsDbContext(DbContextOptions options) + : base(options) + { + + } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + + //Reuse the same extension method! + builder.ConfigureBackgroundJobs(); + } +} +```` + +以这种方式,可以在 `DbContext` 类之间共享模块的映射配置. + +##### 重用模块的表 + +您可能想在应用程序中重用依赖模块的表. 在这种情况下你有两个选择: + +1. 你可以直接使用模块定义的实体. +2. 你可以创建一个新的实体映射到同一个数据库表。 + +###### 使用由模块定义的实体 + +使用实体定义的模块有标准用法非常简单. 例如身份模块定义了 `IdentityUser` 实体. 你可以为注入 `IdentityUser` 仓储,为此实体执行标准仓储操作. +例: + +````csharp +using System; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Domain.Repositories; +using Volo.Abp.Identity; + +namespace Acme.BookStore +{ + public class MyService : ITransientDependency + { + private readonly IRepository _identityUserRepository; + + public MyService(IRepository identityUserRepository) + { + _identityUserRepository = identityUserRepository; + } + + public async Task DoItAsync() + { + //Get all users + var users = await _identityUserRepository.GetListAsync(); + } + } +} +```` + +示例注入了 `IRepository`(默认仓储). 它定义了标准的存储库方法并实现了 `IQueryable` 接口. + +另外,身份模块定义了 `IIdentityUserRepository`(自定义仓储), 你的应用程序也可以注入和使用它. `IIdentityUserRepository` 为 `IdentityUser` 实体提供了额外的定制方法,但它没有实现 `IQueryable`. + +###### 创建一个新的实体 + +TODO + +##### 讨论另一种场景:每个模块管理自己的迁移路径 + +TODO \ No newline at end of file diff --git a/docs/zh-Hans/Modules/Identity.md b/docs/zh-Hans/Modules/Identity.md new file mode 100644 index 0000000000..ea50006de9 --- /dev/null +++ b/docs/zh-Hans/Modules/Identity.md @@ -0,0 +1,3 @@ +# 身份管理模块 + +参阅 [源码](https://github.com/abpframework/abp/tree/dev/modules/identity). 文档很快会被完善. \ No newline at end of file diff --git a/docs/zh-Hans/Modules/Tentant-Management.md b/docs/zh-Hans/Modules/Tentant-Management.md new file mode 100644 index 0000000000..fa999428dd --- /dev/null +++ b/docs/zh-Hans/Modules/Tentant-Management.md @@ -0,0 +1,3 @@ +# 租户管理模块 + +TODO \ No newline at end of file diff --git a/docs/zh-Hans/_resources/Diagrams.docx b/docs/zh-Hans/_resources/Diagrams.docx new file mode 100644 index 0000000000..792d473e3f Binary files /dev/null and b/docs/zh-Hans/_resources/Diagrams.docx differ diff --git a/docs/zh-Hans/images/bookstore-efcore-dependencies.png b/docs/zh-Hans/images/bookstore-efcore-dependencies.png new file mode 100644 index 0000000000..3de5a679ef Binary files /dev/null and b/docs/zh-Hans/images/bookstore-efcore-dependencies.png differ diff --git a/docs/zh-Hans/images/single-database-usage.png b/docs/zh-Hans/images/single-database-usage.png new file mode 100644 index 0000000000..108173fd86 Binary files /dev/null and b/docs/zh-Hans/images/single-database-usage.png differ