.NET 6开发TodoList应用引入数据存储

2022-04-16 10:50:14
目录
一.需求二.目标三.原理和思路四.实现1. 引入Nuget包并进行配置
2. 添加DBContext对象并进行配置#
3. 配置文件修改
4. 主程序配置
5. 本地运行MSSQL Server容器及数据持久化
五.验证

一.需求

作为后端CRUD程序员(bushi,数据存储是开发后端服务一个非常重要的组件。对我们的TodoList项目来说,自然也需要配置数据存储。

目前的需求很简单:

需要能持久化TodoList对象并对其进行操作; 需要能持久化TodoItem对象并对其进行操作;

问题是,我们打算如何存储数据?

存储组件的选择非常多:以MSSQL Server/Postgres/mysql/Swww.easck.comQLite等为代表的关系型数据库,以MongoDB/ElasticSearch等为代表的非关系型数据库,除此之外,我们还可以在开发阶段选择内存数据库,在云上部署的时候还可以选择类似Azure Cosmos DB/AWS DynamoDB以及云上提供的多种关系型数据库。

应用程序使用数据库服务,一般都是借助成熟的第三方ORM框架,而在.NET后端服务开发的过程中,使用的最多的两个ORM框架应该是:EntityFrameworkCore和Dapper,相比之下,EFCore的使用率更高一些。所以我们也选择EFCore来进行演示。

二.目标

在这篇文章中,我们仅讨论如何实现数据存储基础设施的引入,具体的实体定义和操作后面专门来说。

使用MSSQL Server容器作为数据存储组件(前提是电脑上需要安装docker环境,下载并安装Docker Desktop即可);

这样选择的理由也很简单,对于使用Mac的小伙伴来说,使用容器来启动MSSQL Server可以避免因为非Windows平台导致的示例无法运行的问题。

三.原理和思路

因为我们对开发环境和生产环境的配置有差异,那先来看看共性的部分:

引入EFCore相关的nuget包并进行配置; 添加DbContext对象并进行依赖注入; 修改相关appsettings.{environment}.json文件; 主程序配置。 本地运行MSSQL Server容器并实现数据持久化;

同上一篇一样,和具体的第三方对接的逻辑我们还是放到Infrastructure里面去,应用程序中只保留对外部服务的抽象操作。

四.实现

1. 引入Nuget包并进行配置

需要在Infrastructure项目中引入以下Nuget包:

Microsoft.EntityFrameworkCore.SqlServer# 第二个包是用于使用PowerShell命令(Add-Migration/Update-Database/...)需要的,如果使用eftool,可以不安装这个包。Microsoft.EntityFrameworkCore.Tools

为了使用eftool,需要在Api项目中引入以下Nuget包:

Microsoft.EntityFrameworkCore.Design

2. 添加DBContext对象并进行配置#

在这一步里,我们要添加的是一个具体的DBContext对象,这对于有经验的开发者来说并不是很难的任务。但是在具体实现之前,我们可以花一点时间考虑一下现在的Clean Architecture结构:我们的目的是希望除了Infrastructure知道具体交互的第三方是什么,在Application以及Domain里都要屏蔽底层的具体实现。换言之就是需要在Infrastrcuture之外的项目中使用接口来做具体实现的抽象,那么我们在Application中新建一个Common/Interfaces文件夹用于存放应用程序定义的抽象接口IApplicationDbContext:

namespace TodoList.Application.Common.Interfaces;public interface IApplicationDbContext{    Task<int> SaveChangesAsync(CancellationToken cancellationToken);}

接下来在Infrastructure项目中新建Persistence文件夹用来存放和数据持久化相关的具体逻辑,我们在其中定义DbContext对象并实现刚才定义的接口。

using Microsoft.EntityFrameworkCore;using TodoList.Application.Common.Interfaces;namespace TodoList.Infrastructure.Persistence;public class TodoListDbContext : DbContext, IApplicationDbContext{    public TodoListDbContext(DbContextOptions<TodoListDbContext> options) : base(options)    {    }}

这里的处理方式可能会引起困惑,这个IApplicationDbContext存在的意义是什么。这里的疑问和关于要不要使用Repository模式有关,国外多位大佬讨论过这个问题,即Repository是不是必须的。可以简单理解大家达成的共识是:不是必须的,如果不是有某些特别的数据库访问逻辑,或者有足够的理由需要使用Repository模式,那就保持架构上的简洁,在Application层的多个CQRS Handlers中直接注入该IApplicationDbContext去访问数据库并进行操作。如果需要使用Repository模式,那在这里就没有必要定义这个接口来使用了,Application中只需要定义IRepository<T>r.microsoft.com/mssql/server:2019-latest "/opt/mssql/bin/perm…" 24 seconds ago Up 22 seconds 0.0.0.0:1433->1433/tcp mssql

五.验证

为了验证我们是否可以顺利连接到数据库,我们采用添加Migration并在程序启动时自动进行数据库的Migration方式进行:

首先安装工具:

dotnet tool install --global dotnet-ef# dotnet tool update --global dotnet-ef# 生成Migration$ dotnet ef migrations add SetupDb -p src/TodoList.Infrastructure/TodoList.Infrastructure.csproj -s src/TodoList.Api/TodoList.Api.csprojBuild started...Build succeeded.[17:29:15 INF] Entity Framework Core 6.0.1 initialized 'TodoListDbContext' using provider 'Microsoft.EntityFrameworkCore.SqlServer:6.0.1' with options: MigrationsAssembly=TodoList.Infrastructure, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null Done. To undo this action, use 'ef migrations remove'

为了在程序启动时进行自动Migration,我们向Infrastructure项目中增加一个文件ApplicationStartupExtensions.cs并实现扩展方法:

using Microsoft.AspNetCore.Builder;using Microsoft.EntityFrameworkCore;using Microsoft.Extensions.DependencyInjection;using TodoList.Infrastructure.Persistence;namespace TodoList.Infrastructure;public static class ApplicationStartupExtensions{    public static void MigrateDatabase(this WebApplication app)    {        using var scope = app.Services.CreateScope();        var services = scope.ServiceProvider;        try        {            var context = services.GetRequiredService<TodoListDbContext>();            context.Database.Migrate();        }        catch (Exception ex)        {            throw new Exception($"An error occurred migrating the DB: {ex.Message}");        }    }}

并在Api项目的Program.cs中调用扩展方法:

// 省略以上...app.MapControllers();// 调用扩展方法app.MigrateDatabase();app.Run();

最后运行主程序:

$ dotnet run --project src/TodoList.ApiBuilding...[17:32:32 INF] Entity Framework Core 6.0.1 initialized 'TodoListDbContext' using provider 'Microsoft.EntityFrameworkCore.SqlServer:6.0.1' with options: MigrationsAssembly=TodoList.Infrastructure, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null [17:32:32 INF] Executed DbCommand (22ms) [Parameters=[], CommandType='Text', CommandTimeout='30']SELECT 1[17:32:32 INF] Executed DbCommand (19ms) [Parameters=[], CommandType='Text', CommandTimeout='30']SELECT OBJECT_ID(N'[__EFMigrationsHistory]');[17:32:32 INF] Executed DbCommand (3ms) [Parameters=[], CommandType='Text', CommandTimeout='30']SELECT 1[17:32:32 INF] Executed DbCommand (2ms) [Parameters=[], CommandType='Text', CommandTimeout='30']SELECT OBJECT_ID(N'[__EFMigrationsHistory]');[17:32:33 INF] Executed DbCommand (4ms) [Parameters=[], CommandType='Text', CommandTimeout='30']SELECT [MigrationId], [ProductVersion]FROM [__EFMigrationsHistory]ORDER BY [MigrationId];[17:32:33 INF] Applying migration '20211220092915_SetupDb'.[17:32:33 INF] Executed DbCommand (4ms) [Parameters=[], CommandType='Text', CommandTimeout='30']INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion])VALUES (N'20211220092915_SetupDb', N'6.0.1');[17:32:33 INF] Now listening on: https://localhost:7039[17:32:33 INF] Now listening on: http://localhost:5050[17:32:33 INF] Application startedNVJmXa. Press Ctrl+C to shut down.[17:32:33 INF] Hosting environment: Development[17:32:33 INF] Content root path: /Users/yu.li1/Projects/asinta/blogs/cnblogs/TodoList/src/TodoList.Api/

使用数据库工具连接容器数据库,可以看到Migration已经成功地写入数据库表__EFMigrationsHistory了:

.NET 6开发TodoList应用引入数据存储

本篇文章仅完成了数据存储服务的配置工作,目前还没有添加任何实体对象和数据库表定义,所以暂时没有可视化的验证,仅我们可以运行程序看我们的配置是否成功:

总结:

在本文中,我们探讨并实现了如何给.NET 6 Web API项目添加数据存储服务并进行配置,下一篇开始将会深入数据存储部分,定义实体,构建Repository模式和SeedData等操作。