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

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

一.需求

作为后端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 Web API项目添加数据存储服务并进行配置,下一篇开始将会深入数据存储部分,定义实体,构建Repository模式和SeedData等操作。

到此这篇关于.NET 6开发TodoList应用引入数据存储的文章就介绍到这了,更多相关.NET 6开发TodoList应用内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • .NET 6开发TodoList应用实现系列背景

    目录 1.列说明 2.系列导航 2.1 使用.NET 6开发TodoList应用文章索引 2.1.1创建项目 2.1.2.NET 6 WebAPI Program.cs的变更 2.1.3Change 1: Top-level statements 2.1.4Change 2: Implicit using directives 2.1.5Change 3: No Startup class 2.2 关于Pipeline的一些知识点 2.2.1Pipeline Sequence 2.2.2app.

  • 使用.NET 6开发TodoList应用之领域实体创建原理和思路

    需求 上一篇文章中我们完成了数据存储服务的接入,从这一篇开始将正式进入业务逻辑部分的开发. 首先要定义和解决的问题是,根据TodoList项目的需求,我们应该设计怎样的数据实体,如何去进行操作? 长文预警!包含大量代码 目标 在本文中,我们希望达到以下几个目标: 定义领域实体: 通过数据库操作领域实体: 原理和思路 虽然TodoList是一个很简单的应用,业务逻辑并不复杂,至少在这个系列文章中我并不想使其过度复杂.但是我还是打算借此简单地涉及领域驱动开发(DDD)的基础概念. 首先比较明确的是,

  • .NET 6开发TodoList应用实现结构搭建

    目录 1.TodoList需求简介 2.开发工具 2.1.NET 6 2.2Visual Studio Code 2.3Hoppscotch 3.Clean Architecture简介 4.搭建解决方案结构 5.运行 往期学习: .NET 6开发TodoList应用实现系列背景 1.TodoList需求简介 首先明确一下我们即将开发的这个TodoList应用都需要完成什么功能,我不会一次性把所有的特性诸如允许用户登陆之类的需求全部写上,只是先列出最基本的功能性需求: 我们可以维护一个TodoL

  • 使用.NET 6开发TodoList应用之引入数据存储的思路详解

    需求 作为后端CRUD程序员(bushi,数据存储是开发后端服务一个非常重要的组件.对我们的TodoList项目来说,自然也需要配置数据存储.目前的需求很简单: 需要能持久化TodoList对象并对其进行操作: 需要能持久化TodoItem对象并对其进行操作: 问题是,我们打算如何存储数据? 存储组件的选择非常多:以MSSQL Server/Postgres/MySql/SQLite等为代表的关系型数据库,以MongoDB/ElasticSearch等为代表的非关系型数据库,除此之外,我们还可以

  • .NET 6开发TodoList应用引入第三方日志库

    目录 1.需求 2.目标 3.原理和思路 4.实现 4.1日志配置实现 4.2主程序配置 4.3注入使用 5.验证 1.需求 在我们项目开发的过程中,使用.NET 6自带的日志系统有时是不能满足实际需求的,比如有的时候我们需要将日志输出到第三方平台上,最典型的应用就是在各种云平台上,为了集中管理日志和查询日志,通常会选择对应平台的日志SDK进行集成.使用Serilog提供的多种Sink,可以实现将日志写入不同云平台或者是非云平台的日志存储中去,这是我们这篇文章讲要研究的内容. 2.目标 我们将为

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

    目录 一.需求 二.目标 三.原理和思路 四.实现 1. 引入Nuget包并进行配置 2. 添加DBContext对象并进行配置# 3. 配置文件修改 4. 主程序配置 5. 本地运行MSSQL Server容器及数据持久化 五.验证 一.需求 作为后端CRUD程序员(bushi,数据存储是开发后端服务一个非常重要的组件.对我们的TodoList项目来说,自然也需要配置数据存储. 目前的需求很简单: 需要能持久化TodoList对象并对其进行操作: 需要能持久化TodoItem对象并对其进行操作

  • 详解iOS应用开发中Core Data数据存储的使用

    1.如果想创建一个带有coreData的程序,要在项目初始化的时候勾选中   2.创建完成之后,会发现在AppDelegate里多出了几个属性,和2个方法 复制代码 代码如下: <span style="font-size:18px;">    @property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;  @property (readonly, strong,

  • Android开发教程之ContentProvider数据存储

    一.ContentProvider保存数据介绍 一个程序可以通过实现一个ContentProvider的抽象接口将自己的数据完全暴露出去,而且ContentProvider是以类似数据库中表的方式将数据暴露的.那么外界获取其提供的数据,也就应该与从数据库中获取数据的操作基本一样,只不过是采用URL来表示外界需要访问的"数据库". ContentProvider提供了一种多应用间数据共享的方式. ContentProvider是个实现了一组用于提供其他应用程序存取数据的标准方法的类.应用

  • IOS 开发之数据存储writeToFile的应用实例

    IOS 开发之数据存储writeToFile的应用实例 最近项目上要弄数据的导入与导出,所以就研究了一下数据的保存,其实很简单  第一步:获得文件即将保存的路径: NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,  NSUserDomainMask,YES);//使用C函数NSSearchPathForDirectoriesInDomains来获得沙盒中目录的全路径.该函数有三个参数

  • 微信小程序开发之数据存储 参数传递 数据缓存

    微信小程序开发内测一个月.数据传递的方式很少.经常遇到页面销毁后回传参数的问题,小程序中并没有类似Android的startActivityForResult的方法,也没有类似广播这样的通讯方式,更没有类似eventbus的轮子可用. 现在已知传递参数的方法只找到三种,先总结下.由于正处于内测阶段,文档也不是很稳定,经常修改,目前尚没有人造轮子. 先上GIF: 1.APP.js 我把常用且不会更改的参数放在APP.js的data里面了.在各个page中都可以拿到var app = getApp(

  • Android开发笔记之: 数据存储方式详解

    无论是神马平台,神马开发环境,神马软件程序,数据都是核心.对于开发平台来讲,如果对数据的存储有良好的支持,那么对应用程序的开发将会有很大的促进作用.总体的来讲,数据存储方式有三种:一个是文件,一个是数据库,另一个则是网络.其中文件和数据库可能用的稍多一些,文件用起来较为方便,程序可以自己定义格式:数据库用起稍烦锁一些,但它有它的优点,比如在海量数据时性能优越,有查询功能,可以加密,可以加锁,可以跨应用,跨平台等等:网络,则用于比较重要的事情,比如科研,勘探,航空等实时采集到的数据需要马上通过网络

  • vue实现todolist基本功能以及数据存储功能实例详解

    实现todolist功能,具体实现如下: 可以实现对list添加.移除以及状态转变,其中添加功能既可以通过鼠标点击按钮实现,也可以通过回车键按下实现,通过使用v-model对checked的值进行双向绑定来完成状态的改变.在本次实际操作中仍存在一个小问题就是methods中函数调用,在完成数据存储前可以通过this.$options.methods.addFun();进行调用. <div id="app"> <input type="text" v

  • 微信小程序开发实用技巧之数据传递和存储

    结合自己在平时的开发中遇到的各种问题,和浏览各种问题的解决方案总结出一些自己在日常开发中常用的技巧和知点,希望各位不吝斧正. 1.短生命周期数据存储 以小程序启动到彻底关闭为周期的的数据建议存储在app.js文件夹中,引用app.js: const app =getApp(); 假设Value是在小程序本次生命周期中经常使用到的一个数据,比如说请求API的Token,动态的令牌等.那么就可以把这个值赋值到全局变量中去.实际上,并不是只有app.js中的globalData是全局变量,可以自己定义

随机推荐