新西兰服务器

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


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

发布时间:2021-12-27 12:30:26 来源:高防服务器网 阅读:78 作者:小新 栏目:开发技术

小编给大家分享一下.NET 6开发TodoList应用中如何引入数据存储,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!

    一.需求

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

    目前的需求很简单:

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

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

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

    存储组件的选择非常多:以MSSQL Server/Postgres/MySql/SQLite等为代表的关系型数据库,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>,在Infrastructure中实现的BaseRepository中访问DbContext即可。

    我们后面是需要使用Repository的,是因为希望演示最常用的开发模式,但是在这一篇中我保留IApplicationDbConetxt的原因是也希望展示一种不同的实现风格,后面我们还是会专注到Repository上的。

    需要的对象添加好了,下一步是配置DbContext,我们还是遵循当前的架构风格,在Infrastructure项目中添加DependencyInjection.cs文件用于添加所有第三方的依赖:

    using Microsoft.EntityFrameworkCore;  using Microsoft.Extensions.Configuration;  using Microsoft.Extensions.DependencyInjection;  using TodoList.Application.Common.Interfaces;  using TodoList.Infrastructure.Persistence;    namespace TodoList.Infrastructure;    public static class DependencyInjection  {      public static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration configuration)      {          services.AddDbContext<TodoListDbContext>(options =>              options.UseSqlServer(                  configuration.GetConnectionString("SqlServerConnection"),                  b => b.MigrationsAssembly(typeof(TodoListDbContext).Assembly.FullName)));            services.AddScoped<IApplicationDbContext>(provider => provider.GetRequiredService<TodoListDbContext>());            return services;      }  }

    3. 配置文件修改

    我们对appsettings.Development.json文件进行配置:

    {    "Logging": {      "LogLevel": {        "Default": "Information",        "Microsoft.AspNetCore": "Warning"      }    },    "UseFileToLog": true,    "ConnectionStrings": {      "SqlServerConnection": "Server=localhost,1433;Database=TodoListDb;User Id=sa;Password=StrongPwd123;"    }  }

    这里需要说明的是如果是使用MSSQL Server默认端口1433的话,连接字符串里是可以不写的,但是为了展示如果使用的不是默认端口应该如何配置,还是显式写在这里了供大家参考。

    4. 主程序配置

    在Api项目中,我们只需要调用上面写好的扩展方法,就可以完成配置。

    var builder = WebApplication.CreateBuilder(args);    // Add services to the container.  builder.ConfigureLog();    builder.Services.AddControllers();  builder.Services.AddEndpointsApiExplorer();  builder.Services.AddSwaggerGen();    // 添加基础设施配置  builder.Services.AddInfrastructure(builder.Configuration);    // 省略以下...

    5. 本地运行MSSQL Server容器及数据持久化

    在保证本地Docker环境正常启动之后,运行以下命令:

    # 拉取mssql镜像  $ docker pull mcr.microsoft.com/mssql/server:2019-latest  2019-latest: Pulling from mssql/server  7b1a6ab2e44d: Already exists   4ffe416cf537: Pull complete   fff1d174f64f: Pull complete   3588fd79aff7: Pull complete   c8203457909f: Pull complete   Digest: sha256:a098c9ff6fbb8e1c9608ad7511fa42dba8d22e0d50b48302761717840ccc26af  Status: Downloaded newer image for mcr.microsoft.com/mssql/server:2019-latest  mcr.microsoft.com/mssql/server:2019-latest    # 创建持久化存储  $ docker create -v /var/opt/mssql --name mssqldata  mcr.microsoft.com/mssql/server:2019-latest /bin/true  3c144419db7fba26398aa45f77891b00a3253c23e9a1d03e193a3cf523c66ce1    # 运行mssql容器,挂载持久化存储卷  $ docker run -d --volumes-from mssqldata --name mssql -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=StrongPwd123' -p 1433:1433 mcr.microsoft.com/mssql/server:2019-latest  d99d774f70229f688d71fd13e90165f15abc492aacec48de287d348e047a055e    # 确认容器运行状态  $ docker ps  CONTAINER ID   IMAGE                                        COMMAND                  CREATED          STATUS          PORTS                    NAMES  d99d774f7022   mcr.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.csproj  Build 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.Api  Building...  [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 started. 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应用中如何引入数据存储”有了一定的了解,如果想了解更多相关知识,欢迎关注高防服务器网行业资讯频道,感谢各位的阅读!

    [微信提示:高防服务器能助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。

    [图文来源于网络,不代表本站立场,如有侵权,请联系高防服务器网删除]
    [