From 943b4767469798973a5b646bbccd2beaa2deeff5 Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Thu, 26 Mar 2020 23:18:57 +0800 Subject: [PATCH] Added Entity Extensions section to documentss --- .../Entity-Framework-Core-Integration.md | 7 +- ...-Application-Modules-Extending-Entities.md | 30 ++++- docs/zh-Hans/Entities.md | 9 +- .../Entity-Framework-Core-Migrations.md | 99 ++++++---------- docs/zh-Hans/Entity-Framework-Core.md | 107 +++++++++++++++++- 5 files changed, 178 insertions(+), 74 deletions(-) diff --git a/docs/zh-Hans/Best-Practices/Entity-Framework-Core-Integration.md b/docs/zh-Hans/Best-Practices/Entity-Framework-Core-Integration.md index 4d98c6cbc5..c74945ec01 100644 --- a/docs/zh-Hans/Best-Practices/Entity-Framework-Core-Integration.md +++ b/docs/zh-Hans/Best-Practices/Entity-Framework-Core-Integration.md @@ -89,20 +89,23 @@ public static class IdentityDbContextModelBuilderExtensions builder.Entity(b => { - b.ToTable(options.TablePrefix + "Users", options.Schema); + b.ToTable(options.TablePrefix + "Users", options.Schema); + b.ConfigureByConvention(); //code omitted for brevity }); builder.Entity(b => { b.ToTable(options.TablePrefix + "UserClaims", options.Schema); + b.ConfigureByConvention(); //code omitted for brevity - }); + }); //code omitted for brevity } } ```` +* **推荐** 为每个Enttiy映射调用 `b.ConfigureByConvention();`(如上所示). * **推荐** 通过继承 `ModelBuilderConfigurationOptions` 来创建 **configuration Options** 类. 例如: ````C# diff --git a/docs/zh-Hans/Customizing-Application-Modules-Extending-Entities.md b/docs/zh-Hans/Customizing-Application-Modules-Extending-Entities.md index 745861e2c3..1ff03ae104 100644 --- a/docs/zh-Hans/Customizing-Application-Modules-Extending-Entities.md +++ b/docs/zh-Hans/Customizing-Application-Modules-Extending-Entities.md @@ -25,7 +25,35 @@ return user.GetProperty("Title"); 参阅[实体文档](Entities.md)了解更多关于额外系统. -> 可以基于额外的属性执行**业务逻辑**. 你可以**override**服务方法获取或设置值. 重写服务在下面进行讨论. +> 可以基于额外的属性执行**业务逻辑**. 你可以[重写服务方法](Customizing-Application-Modules-Overriding-Services.md). 然后获取或设置如上所示的值. + +## 实体扩展 (EF Core) + +如上所述,实体所有的额外属性都作为单个JSON对象存储在数据库表中. 它不适用复杂的场景,特别是在你需要的时候. + +* 使用额外属性创建**索引**和**外键**. +* 使用额外属性编写**SQL**或**LINQ**(例如根据属性值搜索). +* 创建你**自己的实体**映射到相同的表,但在实体中定义一个额外属性做为 **常规属性**(参阅 [EF Core迁移文档](Entity-Framework-Core-Migrations.md)了解更多). + +为了解决上面的问题,用于EF Core的ABP框架实体扩展系统允许你使用上面定义相同的额外属性API,但将所需的属性存储在单独的数据库表字段中. + +假设你想要添加 `SocialSecurityNumber` 到[身份模块](Modules/Identity.md)的 `IdentityUser` 实体. 你可以使用 `EntityExtensionManager` 静态类: + +````csharp +EntityExtensionManager.AddProperty( + "SocialSecurityNumber", + b => { b.HasMaxLength(32); } +); +```` + +* 你提供了 `IdentityUser` 作为实体名(泛型参数), `string` 做为新属性的类型, `SocialSecurityNumber` 做为属性名(也是数据库表的字段名). +* 你还需要提供一个使用[EF Core Fluent API](https://docs.microsoft.com/en-us/ef/core/modeling/entity-properties)定义数据库映射属性的操作. + +> 必须在使用相关的 `DbContext` 之前执行此代码. 应用程序启动模板定义了一个名为 `YourProjectNameEntityExtensions` 的静态类. 你可以在此类中定义扩展确保在正确的时间执行它. 否则你需要自己处理. + +定义实体扩展后你需要使用EF Core的[Add-Migration](https://docs.microsoft.com/en-us/ef/core/miscellaneous/cli/powershell#add-migration)和[Update-Database](https://docs.microsoft.com/en-us/ef/core/miscellaneous/cli/powershell#update-database)命令来创建code first迁移类并更新数据库. + +然后你可以使用上一部分中定义的相同额外属性系统来操纵实体上的属性. ## 创建新实体映射到同一个数据库表/Collection diff --git a/docs/zh-Hans/Entities.md b/docs/zh-Hans/Entities.md index e5cd3cf839..32dc4ceb53 100644 --- a/docs/zh-Hans/Entities.md +++ b/docs/zh-Hans/Entities.md @@ -365,14 +365,17 @@ public static class IdentityUserExtensions 存储字典的方式取决于你使用的数据库提供程序. -* 对于 [Entity Framework Core](Entity-Framework-Core.md), 它以 `JSON` 字符串形式存储在 `ExtraProperties` 字段中. 序列化到 `JSON` 和反序列化到 `JSON` 由ABP使用EF Core的[值转换](https://docs.microsoft.com/zh-cn/ef/core/modeling/value-conversions)系统自动完成. +* 对于 [Entity Framework Core](Entity-Framework-Core.md),这是两种类型的配置; + * 默认它以 `JSON` 字符串形式存储在 `ExtraProperties` 字段中. 序列化到 `JSON` 和反序列化到 `JSON` 由ABP使用EF Core的[值转换](https://docs.microsoft.com/zh-cn/ef/core/modeling/value-conversions)系统自动完成. + * 如果需要,你可以使用 `EntityExtensionManager` 为所需的额外属性定义一个单独的数据库字段. 那些使用 `EntityExtensionManager` 配置的属性继续使用单个 `JSON` 字段. 当你使用预构建的[应用模块](Modules/Index.md)并且想要[扩展模块的实体](Customizing-Application-Modules-Extending-Entities.md). 参阅[EF Core迁移文档](Entity-Framework-Core.md)了解如何使用 `EntityExtensionManager`. * 对于 [MongoDB](MongoDB.md), 它以 **常规字段** 存储, 因为 MongoDB 天生支持这种 [额外](https://mongodb.github.io/mongo-csharp-driver/1.11/serialization/#supporting-extra-elements) 系统. ### 讨论额外的属性 -如果你使用**可重复使用的模块**,其中定义了一个实体,你想使用简单的方式get/set此实体相关的一些数据,那么额外的属性系统是非常有用的. 通常 **不需要** 为自己的实体使用这个系统,是因为它有以下缺点: +如果你使用**可重复使用的模块**,其中定义了一个实体,你想使用简单的方式get/set此实体相关的一些数据,那么额外的属性系统是非常有用的. +你通常 **不需要** 为自己的实体使用这个系统,是因为它有以下缺点: -* 它不是**完全类型安全的**. +* 它不是**完全类型安全的**,因为它使用字符串用作属性名称. * 这些属性**不容易[自动映射](Object-To-Object-Mapping.md)到其他对象**. * 它**不会**为EF Core在数据库表中**创建字段**,因此在数据库中针对这个字段创建索引或搜索/排序并不容易. diff --git a/docs/zh-Hans/Entity-Framework-Core-Migrations.md b/docs/zh-Hans/Entity-Framework-Core-Migrations.md index bcb0ac864e..223e0a56f8 100644 --- a/docs/zh-Hans/Entity-Framework-Core-Migrations.md +++ b/docs/zh-Hans/Entity-Framework-Core-Migrations.md @@ -95,7 +95,7 @@ Volo.Abp.IdentityServer.AbpIdentityServerDbProperties.DbTablePrefix = "Ids"; 这个项目有应用程序的 `DbContext`类(本例中的 `BookStoreDbContex` ). -**每个模块都使用自己的 `DbContext` 类**来访问数据库。同样你的应用程序有它自己的 `DbContext`. 通常在应用程序中使用这个 `DbContet`(如果你遵循最佳实践,应该在自定义[仓储](Repositories.md)中使用). 它几乎是一个空的 `DbContext`,因为你的应用程序在一开始没有任何实体,除了预定义的 `AppUser` 实体: +**每个模块都使用自己的 `DbContext` 类**来访问数据库。同样你的应用程序有它自己的 `DbContext`. 通常在应用程序中使用这个 `DbContet`(如果你遵循最佳实践,应该在[仓储](Repositories.md)中使用). 它几乎是一个空的 `DbContext`,因为你的应用程序在一开始没有任何实体,除了预定义的 `AppUser` 实体: ````csharp [ConnectionStringName("Default")] @@ -119,15 +119,15 @@ public class BookStoreDbContext : AbpDbContext builder.Entity(b => { - //Sharing the same table "AbpUsers" with the IdentityUser - b.ToTable("AbpUsers"); + //Sharing the same Users table with the IdentityUser + b.ToTable(AbpIdentityDbProperties.DbTablePrefix + "Users"); - //Configure base properties b.ConfigureByConvention(); b.ConfigureAbpUser(); - //Moved customization of the "AbpUsers" table to an extension method - b.ConfigureCustomUserProperties(); + /* Configure mappings for your additional properties + * Also see the MyProjectNameEntityExtensions class + */ }); /* Configure your own tables/entities inside the ConfigureBookStore method */ @@ -190,12 +190,6 @@ public class BookStoreMigrationsDbContext : AbpDbContext(b => - { - b.ConfigureCustomUserProperties(); - }); - /* Configure your own tables/entities inside the ConfigureBookStore method */ builder.ConfigureBookStore(); } @@ -276,7 +270,7 @@ public class BackgroundJobsDbContext 您可能想在应用程序中**重用依赖模块的表**. 在这种情况下你有两个选择: -1. 你可以**直接使用模块定义的实体**. +1. 你可以**直接使用模块定义的实体**(你仍然可以在某种程度上[扩展实体](Customizing-Application-Modules-Extending-Entities.md)). 2. 你可以**创建一个新的实体**映射到同一个数据库表。 ###### 使用由模块定义的实体 @@ -379,10 +373,8 @@ protected override void OnModelCreating(ModelBuilder builder) builder.Entity(b => { b.ToTable("AbpRoles"); - b.ConfigureByConvention(); - - b.ConfigureCustomRoleProperties(); + b.Property(x => x.Title).HasMaxLength(128); }); ... @@ -399,68 +391,42 @@ protected override void OnModelCreating(ModelBuilder builder) builder.Entity(b => { b.ToTable("AbpRoles"); - b.ConfigureByConvention(); - - b.ConfigureCustomRoleProperties(); + b.Property(x => x.Title).HasMaxLength(128); }); ```` * 它映射到 `AbpRoles` 表,与 `IdentityRole` 实体共享. * `ConfigureByConvention()` 配置了标准/基本属性(像`TenantId`),建议总是调用它. -`ConfigureCustomRoleProperties()` 还不存在. 在 `BookStoreDbContextModelCreatingExtensions` 类中定义它 (在 `.EntityFrameworkCore` 项目的 `DbContext` 附近): +你已经为你的 `DbContext` 配置自定义属性,该属性在应用程序运行时使用. +与其直接更改 `MigrationsDbContext`,我们应该使用ABP框架的实体扩展系统,找到 在解决方案的 `.EntityFrameworkCore` 项目中找到 `YourProjectNameEntityExtensions` 类(本示例中是 `BookStoreEntityExtensions`)并且进行以下更改: ````csharp -public static void ConfigureCustomRoleProperties(this EntityTypeBuilder b) - where TRole : class, IEntity +public static class MyProjectNameEntityExtensions { - b.Property(nameof(AppRole.Title)).HasMaxLength(128); -} -```` - -* 这个方法只定义实体的**自定义属性**. -* 遗憾的是,我们不能在这里充分的利用**类型安全**(通过引用`AppRole`实体). 我们能做的最好就是使用 `Title` 名称做为类型安全。 - -你已经为运行应用程序使用的 `DbContext` 配置了自定义属性. 我们还需要配置 `MigrationsDbContext`. - -打开`MigrationsDbContext`(本例是 `BookStoreMigrationsDbContext`)进行以下更改: - -````csharp -protected override void OnModelCreating(ModelBuilder builder) -{ - base.OnModelCreating(builder); - - /* Include modules to your migration db context */ - - ... - - /* Configure customizations for entities from the modules included */ + private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner(); - //CONFIGURE THE CUSTOM ROLE PROPERTIES - builder.Entity(b => + public static void Configure() { - b.ConfigureCustomRoleProperties(); - }); - - ... - - /* Configure your own tables/entities inside the ConfigureBookStore method */ - - builder.ConfigureBookStore(); + OneTimeRunner.Run(() => + { + EntityExtensionManager.AddProperty( + "Title", + b => { b.HasMaxLength(128); } + ); + }); + } } ```` -只增加下面几行: +> 我们建议使用 `nameof(AppRole.Title)` 而不是硬编码 "Title" 字符串 -````csharp -builder.Entity(b => -{ - b.ConfigureCustomRoleProperties(); -}); -```` +`EntityExtensionManager` 用于添加属性到现有的实体. 由于 `EntityExtensionManager` 是静态的,因此应调用一次. `OneTimeRunner` 是ABP框架定义简单的工具类. -通过这种方式,我们重用了用于为角色配置自定义属性映射的扩展方法. 但是对 `IdentityRole` 实体进行了相同的自定义. +参阅[EF Core集成文档](Entity-Framework-Core.md)了解更多关于实体扩展系统. + +我们在两个类中都重复了类似的数据库映射代码,例如 `HasMaxLength(128)`. 现在你可以在包管理控制台(记得选择 `.EntityFrameworkCore.DbMigrations` 做为PMC的默认项目并将 `.Web` 项目设置为启动项目)使用标准的 `Add-Migration` 命令添加一个新的EF Core数据库迁移. @@ -540,7 +506,7 @@ public class AppRoleAppService : ApplicationService, IAppRoleAppService ###### 使用ExtraProperties -所有从 `AggregateRoot` 派生的实体都可以在 `ExtraProperties` 属性中存储键值对, 它是 `Dictionary` 类型在数据库中被序列化为JSON. 所以你可以在字典中添加值用于查询,无需更改实体. +所有从 `AggregateRoot` 派生的实体都可以在 `ExtraProperties` 属性(因为它们都实现了 `IHasExtraProperties` 接口)中存储键值对, 它是 `Dictionary` 类型在数据库中被序列化为JSON. 所以你可以在字典中添加值用于查询,无需更改实体. 例如你可以将查询属性 `Title` 存储在 `IdentityRole` 中,而不是创建一个新的实体. 例: @@ -558,16 +524,13 @@ public class IdentityRoleExtendingService : ITransientDependency public async Task GetTitleAsync(Guid id) { var role = await _identityRoleRepository.GetAsync(id); - return role.GetProperty("Title"); } public async Task SetTitleAsync(Guid id, string newTitle) { var role = await _identityRoleRepository.GetAsync(id); - role.SetProperty("Title", newTitle); - await _identityRoleRepository.UpdateAsync(role); } } @@ -580,6 +543,12 @@ public class IdentityRoleExtendingService : ITransientDependency * 所有的额外属性都存储在数据库中的一个**JSON对象**,它们不是作为表的字段存储,与简单的表字段相比创建索引和针对此属性使用SQL查询将更加困难. * 属性名称是字符串,他们**不是类型安全的**. 建议这些类型的属性定义常量,以防止拼写错误. +###### 使用实体扩展系统 + +实体扩展系统解决了额外属性主要的问题: 它可以将额外属性做为**标准表字段**存储到数据库. + +你需要做的就是如上所诉使用 `EntityExtensionManager` 定义额外属性, 然后你就可以使得 `GetProperty` 和 `SetProperty` 方法对实体的属性进行get/set,但是这时它存储在数据库表的单独字段中. + ###### 创建新表 你可以创建**自己的表**来存储属性,而不是创建新实体并映射到同一表. 你通常复制原始实体的一些值. 例如可以将 `Name` 字段添加到你自己的表中,它是原表中 `Name` 字段的副本. diff --git a/docs/zh-Hans/Entity-Framework-Core.md b/docs/zh-Hans/Entity-Framework-Core.md index 0bec4d1ab9..23e3f30cb9 100644 --- a/docs/zh-Hans/Entity-Framework-Core.md +++ b/docs/zh-Hans/Entity-Framework-Core.md @@ -58,6 +58,53 @@ namespace MyCompany.MyProject } ```` +### 关于EF Core Fluent Mapping + +[应用程序启动模板](Startup-Templates/Application.md)已配置使用[EF Core fluent configuration API](https://docs.microsoft.com/en-us/ef/core/modeling/)映射你的实体到数据库表. + +你依然为你的实体属性使用**data annotation attributes**(像`[Required]`),而ABP文档通常遵循**fluent mapping API** approach方法. 如何使用取决与你. + +ABP框架有一些**实体基类**和**约定**(参阅[实体文档](Entities.md))提供了一些有用的扩展方法来配置从基本实体类继承的属性. + +#### ConfigureByConvention 方法 + +`ConfigureByConvention()` 是主要的扩展方法,它对你的实体**配置所有的基本属性**和约定. 所以在你的流利映射代码中为你所有的实体调用这个方法是 **最佳实践**, + +**示例**: 假设你有一个直接继承 `AggregateRoot` 基类的 `Book` 实体: + +````csharp +public class Book : AuditedAggregateRoot +{ + public string Name { get; set; } +} +```` + +你可以在你的 `DbContext` 重写 `OnModelCreating` 方法并且做以下配置: + +````csharp +protected override void OnModelCreating(ModelBuilder builder) +{ + //Always call the base method + base.OnModelCreating(builder); + + builder.Entity(b => + { + b.ToTable("Books"); + + //Configure the base properties + b.ConfigureByConvention(); + + //Configure other properties (if you are using the fluent API) + b.Property(x => x.Name).IsRequired().HasMaxLength(128); + }); +} +```` + +* 这里调用了 `b.ConfigureByConvention()` 它对于**配置基本属性**非常重要. +* 你可以在这里配置 `Name` 属性或者使用**data annotation attributes**(参阅[EF Core 文档](https://docs.microsoft.com/zh-cn/ef/core/modeling/entity-properties)). + +> 尽管有许多扩展方法可以配置基本属性,但如果需要 `ConfigureByConvention()` 内部会调用它们. 因此仅调用它就足够了. + ### 配置连接字符串选择 如果你的应用程序有多个数据库,你可以使用 `connectionStringName]` Attribute为你的DbContext配置连接字符串名称. @@ -225,7 +272,7 @@ public override async Task DeleteAsync( } ```` -### 访问 EF Core API +## 访问 EF Core API 大多数情况下应该隐藏仓储后面的EF Core API(这也是仓储的设计目地). 但是如果想要通过仓储访问DbContext实现,则可以使用`GetDbContext()`或`GetDbSet()`扩展方法. 例: @@ -251,9 +298,59 @@ public class BookService > 要点: 你必须在使用`DbContext`的项目里引用`Volo.Abp.EntityFrameworkCore`包. 这会破坏封装,但在这种情况下,这就是你需要的. -### 高级主题 +## Extra Properties & Entity Extension Manager + +额外属性系统允许你为实现了 `IHasExtraProperties` 的实体set/get动态属性. 当你想将自定义属性添加到[应用程序模块](Modules/Index.md)中定义的实体时,它特别有用. + +默认,实体的所有额外属性存储在数据库的一个 `JSON` 对象中. 实体扩展系统允许你存储额外属性在数据库的单独字段中. -#### 设置默认仓储类 +有关额外属性和实体扩展系统的更多信息,请参阅下列文档: + +* [自定义应用模块: 扩展实体](Customizing-Application-Modules-Extending-Entities.md) +* [实体](Entities.md) + +本节只解释了 `EntityExtensionManager` 及其用法. + +### AddProperty 方法 + +`EntityExtensionManager` 的 `AddProperty` 方法允许你实体定义附加的属性. + +**示例**: 浅咖 `Title` 属性 (数据库字段)到 `IdentityRole` 实体: + +````csharp +EntityExtensionManager.AddProperty( + "Title", + b => { b.HasMaxLength(128); } +); +```` + +如果相关模块已实现此功能(通过使用下面说明的 `ConfigureExtensions`)则将新属性添加到模型中. 然后你需要运行标准的 `Add-Migration` 和 `Update-Database` 命令更新数据库以添加新字段. + +>`AddProperty` 方法必须在使用相关的 `DbContext` 之前调用,它是一个静态方法. 最好的方法是尽早的应用程序中使用它. 应用程序启动模板含有 `YourProjectNameEntityExtensions` 类,可以在放心的在此类中使用此方法. + +### ConfigureExtensions + +如果你正在开发一个可重用使用的模块,并允许应用程序开发人员将属性添加到你的实体,你可以在实体映射使用 `ConfigureExtensions` 扩展方法: + +````csharp +builder.Entity(b => +{ + b.ConfigureExtensions(); + //... +}); +```` + +如果你调用 `ConfigureByConvention()` 扩展方法(在此示例中 `b.ConfigureByConvention`),ABP框架内部会调用 `ConfigureExtensions` 方法. 使用 `ConfigureByConvention` 方法是**最佳实践**,因为它还按照约定配置基本属性的数据库映射. + +参阅上面提到的 "*ConfigureByConvention 方法*" 了解更多信息. + +### GetPropertyNames + +`EntityExtensionManager.GetPropertyNames` 静态方法可以用作为此实体定义的扩展属性的名称. 应用程序代码通常不需要,但是ABP框架在内部使用它. + +## 高级主题 + +### 设置默认仓储类 默认的通用仓储的默认实现是`EfCoreRepository`类,你可以创建自己的实现,并将其做为默认实现 @@ -343,3 +440,7 @@ context.Services.AddAbpDbContext(options => ```` 在这个例子中,`OtherDbContext`实现了`IBookStoreDbContext`. 此功能允许你在开发时使用多个DbContext(每个模块一个),但在运行时可以使用单个DbContext(实现所有DbContext的所有接口). + +## 另请参阅 + +* [实体](Entities.md) \ No newline at end of file