我们在该应用中使用**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 上显示产品数据。