我们在该应用中使用**EF Core**。EF Core 是一个由微软提供的**对象关系映射**(**ORM**) 提供程序。ORM 提供了抽象,让您感觉像是在使用代码实体对象而不是数据库表。我们将在\[*第 6 章*\] 《使用数据访问基础架构》中介绍 ABP 的 EF Core 集成。现在,我们先了解如何使用它。
1. 首先,我们将实体添加到`DbContext`类并定义实体和数据库表之间的映射;
2. 然后,我们将使用 EF Core 的**Code First**方法创建对应的数据库表;
3. 接下来,我们再看 ABP 的**种子数据**系统,并插入一些初始数据;
4. 最后,我们会将数据库表结构和种子数据迁移到数据库中,以便为应用程序做好准备。
让我们从定义`DbSet`实体的属性开始。
## 将实体添加到 DbContext 类
EF的`DbContext`有两个主要用途:
1. 定义实体和数据库表之间映射;
2. 访问数据库和执行数据库相关实体的操作。
*在.EntityFrameworkCore*项目中打开`ProductManagementDbContext`该类,添加以下属性:
```
public DbSet<Product> Products { get; set; }
public DbSet<Category> Categories { get; set; }
```
EF Core 可以使用基于属性名称和类型的约定进行大部分映射。如果要自定义默认的映射配置或额外的配置,有两种方法:**数据注释**(属性)和**Fluent API**。
在数据注释方法中,我们向实体属性添加特性,例如`[Required]`和`[StringLength]`,非常方便,也很容易理解。
与Fluent API相比,数据注释容易受限,比如,当你需要使用EF Core的自定义特性时,他会让你的领域层依赖EF Core的NuGet包,比如`[Index]`和`[Owned]`
在本章中,我更倾向 Fluent API 方法,它使实体更干净,并将所有 ORM 逻辑放在基础设施层中。
## 将实体映射到数据库表
类`ProductManagementDbContext`(在\*.EntityFrameworkCore\*项目中)包含一个`OnModelCreating`方法用来配置实体到数据库表的映射。当你首先创建您的解决方案时,此方法看起来如下所示:
```
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.ConfigurePermissionManagement();
builder.ConfigureSettingManagement();
builder.ConfigureIdentity();
...configuration of the other modules
/* Configure your own tables/entities here */
}
```
再添加`Category`和`Product`实体的配置和映射关系:
```
builder.Entity<Category>(b =>
{
b.ToTable("Categories");
b.Property(x => x.Name)
.HasMaxLength(CategoryConsts.MaxNameLength)
.IsRequired();
b.HasIndex(x => x.Name);
});
builder.Entity<Product>(b =>
{
b.ToTable("Products");
b.Property(x => x.Name)
.HasMaxLength(ProductConsts.MaxNameLength)
.IsRequired();
b.HasOne(x => x.Category)
.WithMany()
.HasForeignKey(x => x.CategoryId)
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.HasIndex(x => x.Name).IsUnique();
});
```
我们使用`CategoryConsts.MaxNameLength`设置表`Category`的`Name`字段的最大长度。`Name`字段也是必填属性。最后,我们为属性定义了一个唯一的数据库索引,因为它有助于按`Name`字段搜索。
`Product`映射类似于`Category`。此外,它还定义了`Category`实体与`Product`实体之间的关系;一个`Product`实体属于一个`Category`实体,而一个`Category`实体可以有多个`Product`实体。
您可以参考 EF Core 文档进一步了解 Fluent API 的所有详细信息和其他选项。
映射配置完成后,我们就可以创建数据库迁移,把我们新加的实体转换成数据库结构。
## 添加迁移命令
当你创建一个新的实体或对现有实体进行更改,还应该同步到数据库中。EF Core 的**Code First**就是用来同步数据库和实体结构的强大工具。通常,我们需要先生成迁移脚本,然后执行迁移命令。迁移会对数据库的架构进行增量更改。有两种方法可以生成新迁移:
### 1 使用 Visual Studio
如果你正在使用Visual Studio,请打开视图|包管理器控制台菜单:
![](https://img.kancloud.cn/7d/b6/7db6e3da8619f203f837be1064bdb65a_1106x352.png)
>[warning] 选择.EntityFrameworkCore项目作为默认项目,并右键设置.Web项目作为启动项目
现在,您可以在 控制台中键入以下命令:
```
Add-Migration "Added_Categories_And_Products"
```
此命令的输出应类似于:
![](https://img.kancloud.cn/de/cd/decd248a44ec35cf5b47a2ad56018369_731x164.png)
如果你得到一个诸如*No DbContext was found in assembly...* 之类的错误,请确保您已将\*.EntityFrameworkCore\*项目设置为默认项目。
如果一切顺利,会在.EntityFrameworkCore项目的Migrations文件夹中添加一个新的迁移类。
### 2 在命令行中
如果您不使用Visual Studio,你可以使用 EF Core命令行工具。如果尚未安装,请在命令行终端中执行以下命令:
```
dotnet tool install --global dotnet-ef
```
现在,在.EntityFrameworkCore项目的根目录中打开一个命令行终端,然后输入以下命令:
```
dotnet ef migrations add "Added_Categories_And_Products"
```
一个新的迁移类会添加到.EntityFrameworkCore项目的Migrations文件夹中。
## 种子数据
种子数据系统用于迁移数据库时添加一些初始数据。例如,身份模块在数据库中创建一个管理员用户,该用户具有登录应用程序的所有权限。
虽然种子数据在我们的场景中不是必需的,这里我想将一些产品类别和产品的初始化数据添加到数据库中,以便更轻松地开发和测试应用程序。
关于 EF Core 种子数据
本节使用 ABP 的种子数据系统,而 EF Core 有自己的种子数据功能。ABP 种子数据系统允许您在代码中**注入运行时服务并实现高级逻辑**,适用于**开发、测试和生产环境**。但是,对于简单的开发和测试,使用 EF Core 的种子数据基本够用。请查看[官方文档](https://docs.microsoft.com/en-us/ef/core/modeling/data-seeding)。
在ProductManagement.Domain项目的Data文件夹中创建一个`ProductManagementDataSeedContributor`类:
```
using ProductManagement.Categories;
using ProductManagement.Products;
using System;
using System.Threading.Tasks;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories;
namespace ProductManagement.Data
{
public class ProductManagementDataSeedContributor :
IDataSeedContributor, ITransientDependency
{
private readonly IRepository<Category, Guid>_categoryRepository;
private readonly IRepository<Product, Guid>_productRepository;
public ProductManagementDataSeedContributor(
IRepository<Category, Guid> categoryRepository,
IRepository<Product, Guid> productRepository)
{
_categoryRepository = categoryRepository;
_productRepository = productRepository;
}
public async Task SeedAsync(DataSeedContext context)
{
/***** TODO: Seed initial data here *****/
}
}
}
```
该类实现了`IDataSeedContributor`接口,ABP 会自动发现并调用其`SeedAsync`方法。您也可以实现构造函数注入并使用类中的任何服务(例如本示例中的存储库)。
然后,在`SeedAsync`方法内部编码:
```
if (await _categoryRepository.CountAsync() > 0)
{
return;
}
var monitors = new Category { Name = "Monitors" };
var printers = new Category { Name = "Printers" };
await _categoryRepository.InsertManyAsync(new[] { monitors, printers });
var monitor1 = new Product
{
Category = monitors,
Name = "XP VH240a 23.8-Inch Full HD 1080p IPS LED Monitor",
Price = 163,
ReleaseDate = new DateTime(2019, 05, 24),
StockState = ProductStockState.InStock
};
var monitor2 = new Product
{
Category = monitors,
Name = "Clips 328E1CA 32-Inch Curved Monitor, 4K UHD",
Price = 349,
IsFreeCargo = true,
ReleaseDate = new DateTime(2022, 02, 01),
StockState = ProductStockState.PreOrder
};
var printer1 = new Product
{
Category = monitors,
Name = "Acme Monochrome Laser Printer, Compact All-In One",
Price = 199,
ReleaseDate = new DateTime(2020, 11, 16),
StockState = ProductStockState.NotAvailable
};
await _productRepository.InsertManyAsync(new[] { monitor1, monitor2, printer1 });
```
我们创建了两个类别和三种产品并将它们插入到数据库中。每次您运行DbMigrator应用时都会执行此类。同时,我们检查`if (await _categoryRepository.CountAsync() > 0)`以防止数据重复插入。
种子数据和数据库表结构准备就绪, 下面进入正式迁移。
## 迁移数据库
>[warning] EF Core 和 ABP 迁移有何区别?
ABP 启动模板中包含一个在开发和生产环境中非常有用的DbMigrator控制台项目。当您运行它时,所有待处理的迁移都将应用到数据库中,并执行数据初始化。
它**支持多租户/多数据库的场景**,这是使用`Update-Database`无法实现的。
>[warning] 为什么要从主应用中分离出迁移项目?
在生产环境中部署和执行时,通常作为**持续部署**(**CD**) 管道的一个环节。从主应用中分离出迁移功能有个好处,主应用不需要更改数据库的权限。此外,如果不做分离可能会遇到数据库迁移和执行的并发问题。
将.DbMigrator项目设置为启动项目,然后*按 Ctrl*+*F5* 运行该项目,待应用程序退出后,您可以检查*Categories*和*Products*表是否已插入数据库中(如果您使用 Visual Studio,则可以使用**SQL Server 对象资源管理器**连接到**LocalDB**并浏览数据库)。
数据库已准备好了。接下来我们将在 UI 上显示产品数据。
- 前言
- 第一部分
- 第1章 现代软件开发和 ABP 框架
- 企业级 Web 开发的挑战
- ABP框架的能力清单
- 第2章 ABP框架入门
- 安装 ABP CLI
- 创建新解决方案
- 运行解决方案
- 探索预构建模块
- 第3章 逐步开发开发ABP应用
- 创建解决方案
- 定义领域对象
- EFCore和数据库映射
- 定义应用服务
- 测试产品
- 产品列表
- 创建产品
- 编辑产品
- 删除产品
- 第4章 探索 EventHub解决方案
- 应用介绍
- 架构探索
- 方案运行
- 第二部分
- 第5章 探索ABP基础架构
- 了解模块化
- 使用依赖注入系统
- 配置应用程序
- 实现选项模式
- 日志系统
- 第6章 数据访问基础架构
- 定义实体
- 定义仓储库
- EF Core集成
- 了解 UoW
- 第7章 探索横切关注点
- 认证授权
- 用户验证
- 异常处理
- 第8章 体验 ABP 的功能和服务
- 获取当前用户
- 使用数据过滤
- 控制审计日志
- 缓存数据
- 本地化用户界面
- 第三部分
- 第9章 理解领域驱动设计
- 介绍 DDD
- 构建基于 DDD 的 解决方案
- 处理多个应用程序
- 了解执行流程
- DDD的通用原则
- 第10章 领域层 Domain
- 领域事件案例分析
- 聚合和实体的设计原则和实践
- 实现领域服务
- 落地存储库
- 构建规约(Specification)
- 领域事件
- 第11章 应用层 Application
- 落地应用服务
- 设计 DTO
- 理解各层的职责
- 第四部分
- 第12章 MVC/Razor 页面
- 主题系统
- 绑定和压缩
- 导航菜单
- Bootstrap标签助手
- 创建表单并验证
- 使用模态窗口
- 使用JS API
- 调用HTTP API
- 第13章 Blazor WebAssembly UI
- 什么是Blazor
- ABP Blazor UI
- 验证用户身份
- 理解主题系统
- 使用菜单
- 使用基本服务
- 使用UI服务
- 消费HTTP API
- 使用全局脚本和样式
- 第14章 HTTP API 和实时服务
- 构建HTTP API
- 使用HTTP API
- 使用SignalR
- 第五部分
- 第15章 落地模块化
- 理解模块化
- 构建支付模块
- 安装模块
- 第16章 实现多租户
- 理解多租户
- 多租户基础设施
- 使用功能系统
- 何时使用多租户
- 第17章 构建自动化测试
- 了解ABP测试基础设施
- 构建单元测试
- 构建集成测试