溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

.NET?6開(kāi)發(fā)TodoList應(yīng)用中如何引入數(shù)據(jù)存儲(chǔ)

發(fā)布時(shí)間:2021-12-27 12:30:26 來(lái)源:億速云 閱讀:170 作者:小新 欄目:開(kāi)發(fā)技術(shù)

小編給大家分享一下.NET 6開(kāi)發(fā)TodoList應(yīng)用中如何引入數(shù)據(jù)存儲(chǔ),希望大家閱讀完這篇文章之后都有所收獲,下面讓我們一起去探討吧!

    一.需求

    作為后端CRUD程序員(bushi,數(shù)據(jù)存儲(chǔ)是開(kāi)發(fā)后端服務(wù)一個(gè)非常重要的組件。對(duì)我們的TodoList項(xiàng)目來(lái)說(shuō),自然也需要配置數(shù)據(jù)存儲(chǔ)。

    目前的需求很簡(jiǎn)單:

    • 需要能持久化TodoList對(duì)象并對(duì)其進(jìn)行操作;

    • 需要能持久化TodoItem對(duì)象并對(duì)其進(jìn)行操作;

    問(wèn)題是,我們打算如何存儲(chǔ)數(shù)據(jù)?

    存儲(chǔ)組件的選擇非常多:以MSSQL Server/Postgres/MySql/SQLite等為代表的關(guān)系型數(shù)據(jù)庫(kù),MongoDB/ElasticSearch等為代表的非關(guān)系型數(shù)據(jù)庫(kù),除此之外,我們還可以在開(kāi)發(fā)階段選擇內(nèi)存數(shù)據(jù)庫(kù),在云上部署的時(shí)候還可以選擇類(lèi)似Azure Cosmos DB/AWS DynamoDB以及云上提供的多種關(guān)系型數(shù)據(jù)庫(kù)。

    應(yīng)用程序使用數(shù)據(jù)庫(kù)服務(wù),一般都是借助成熟的第三方ORM框架,而在.NET后端服務(wù)開(kāi)發(fā)的過(guò)程中,使用的最多的兩個(gè)ORM框架應(yīng)該是:EntityFrameworkCore和Dapper,相比之下,EFCore的使用率更高一些。所以我們也選擇EFCore來(lái)進(jìn)行演示。

    二.目標(biāo)

    在這篇文章中,我們僅討論如何實(shí)現(xiàn)數(shù)據(jù)存儲(chǔ)基礎(chǔ)設(shè)施的引入,具體的實(shí)體定義和操作后面專(zhuān)門(mén)來(lái)說(shuō)。

    • 使用MSSQL Server容器作為數(shù)據(jù)存儲(chǔ)組件(前提是電腦上需要安裝Docker環(huán)境,下載并安裝Docker Desktop即可);

    這樣選擇的理由也很簡(jiǎn)單,對(duì)于使用Mac的小伙伴來(lái)說(shuō),使用容器來(lái)啟動(dòng)MSSQL Server可以避免因?yàn)榉?code>Windows平臺(tái)導(dǎo)致的示例無(wú)法運(yùn)行的問(wèn)題。

    三.原理和思路

    因?yàn)槲覀儗?duì)開(kāi)發(fā)環(huán)境和生產(chǎn)環(huán)境的配置有差異,那先來(lái)看看共性的部分:

    • 引入EFCore相關(guān)的nuget包并進(jìn)行配置;

    • 添加DbContext對(duì)象并進(jìn)行依賴(lài)注入;

    • 修改相關(guān)appsettings.{environment}.json文件;

    • 主程序配置。

    • 本地運(yùn)行MSSQL Server容器并實(shí)現(xiàn)數(shù)據(jù)持久化;

    同上一篇一樣,和具體的第三方對(duì)接的邏輯我們還是放到Infrastructure里面去,應(yīng)用程序中只保留對(duì)外部服務(wù)的抽象操作。

    四.實(shí)現(xiàn)

    1. 引入Nuget包并進(jìn)行配置

    需要在Infrastructure項(xiàng)目中引入以下Nuget包:

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

    為了使用eftool,需要在Api項(xiàng)目中引入以下Nuget包:

    Microsoft.EntityFrameworkCore.Design

    2. 添加DBContext對(duì)象并進(jìn)行配置#

    在這一步里,我們要添加的是一個(gè)具體的DBContext對(duì)象,這對(duì)于有經(jīng)驗(yàn)的開(kāi)發(fā)者來(lái)說(shuō)并不是很難的任務(wù)。但是在具體實(shí)現(xiàn)之前,我們可以花一點(diǎn)時(shí)間考慮一下現(xiàn)在的Clean Architecture結(jié)構(gòu):我們的目的是希望除了Infrastructure知道具體交互的第三方是什么,在Application以及Domain里都要屏蔽底層的具體實(shí)現(xiàn)。換言之就是需要在Infrastrcuture之外的項(xiàng)目中使用接口來(lái)做具體實(shí)現(xiàn)的抽象,那么我們?cè)贏pplication中新建一個(gè)Common/Interfaces文件夾用于存放應(yīng)用程序定義的抽象接口IApplicationDbContext:

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

    接下來(lái)在Infrastructure項(xiàng)目中新建Persistence文件夾用來(lái)存放和數(shù)據(jù)持久化相關(guān)的具體邏輯,我們?cè)谄渲卸xDbContext對(duì)象并實(shí)現(xiàn)剛才定義的接口。

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

    這里的處理方式可能會(huì)引起困惑,這個(gè)IApplicationDbContext存在的意義是什么。這里的疑問(wèn)和關(guān)于要不要使用Repository模式有關(guān),國(guó)外多位大佬討論過(guò)這個(gè)問(wèn)題,即Repository是不是必須的??梢院?jiǎn)單理解大家達(dá)成的共識(shí)是:不是必須的,如果不是有某些特別的數(shù)據(jù)庫(kù)訪(fǎng)問(wèn)邏輯,或者有足夠的理由需要使用Repository模式,那就保持架構(gòu)上的簡(jiǎn)潔,在Application層的多個(gè)CQRS Handlers中直接注入該IApplicationDbContext去訪(fǎng)問(wèn)數(shù)據(jù)庫(kù)并進(jìn)行操作。如果需要使用Repository模式,那在這里就沒(méi)有必要定義這個(gè)接口來(lái)使用了,Application中只需要定義IRepository<T>,在Infrastructure中實(shí)現(xiàn)的BaseRepository中訪(fǎng)問(wèn)DbContext即可。

    我們后面是需要使用Repository的,是因?yàn)橄M菔咀畛S玫拈_(kāi)發(fā)模式,但是在這一篇中我保留IApplicationDbConetxt的原因是也希望展示一種不同的實(shí)現(xiàn)風(fēng)格,后面我們還是會(huì)專(zhuān)注到Repository上的。

    需要的對(duì)象添加好了,下一步是配置DbContext,我們還是遵循當(dāng)前的架構(gòu)風(fēng)格,在Infrastructure項(xiàng)目中添加DependencyInjection.cs文件用于添加所有第三方的依賴(lài):

    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. 配置文件修改

    我們對(duì)appsettings.Development.json文件進(jìn)行配置:

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

    這里需要說(shuō)明的是如果是使用MSSQL Server默認(rèn)端口1433的話(huà),連接字符串里是可以不寫(xiě)的,但是為了展示如果使用的不是默認(rèn)端口應(yīng)該如何配置,還是顯式寫(xiě)在這里了供大家參考。

    4. 主程序配置

    在Api項(xiàng)目中,我們只需要調(diào)用上面寫(xiě)好的擴(kuò)展方法,就可以完成配置。

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

    5. 本地運(yùn)行MSSQL Server容器及數(shù)據(jù)持久化

    在保證本地Docker環(huán)境正常啟動(dòng)之后,運(yùn)行以下命令:

    # 拉取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
    
    # 創(chuàng)建持久化存儲(chǔ)
    $ docker create -v /var/opt/mssql --name mssqldata  mcr.microsoft.com/mssql/server:2019-latest /bin/true
    3c144419db7fba26398aa45f77891b00a3253c23e9a1d03e193a3cf523c66ce1
    
    # 運(yùn)行mssql容器,掛載持久化存儲(chǔ)卷
    $ 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
    
    # 確認(rèn)容器運(yùn)行狀態(tài)
    $ 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

    五.驗(yàn)證

    為了驗(yàn)證我們是否可以順利連接到數(shù)據(jù)庫(kù),我們采用添加Migration并在程序啟動(dòng)時(shí)自動(dòng)進(jìn)行數(shù)據(jù)庫(kù)的Migration方式進(jìn)行:

    首先安裝工具:

    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'

    為了在程序啟動(dòng)時(shí)進(jìn)行自動(dòng)Migration,我們向Infrastructure項(xiàng)目中增加一個(gè)文件ApplicationStartupExtensions.cs并實(shí)現(xiàn)擴(kuò)展方法:

    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項(xiàng)目的Program.cs中調(diào)用擴(kuò)展方法:

    // 省略以上...
    app.MapControllers();
    
    // 調(diào)用擴(kuò)展方法
    app.MigrateDatabase();
    
    app.Run();

    最后運(yùn)行主程序:

    $ 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/

    使用數(shù)據(jù)庫(kù)工具連接容器數(shù)據(jù)庫(kù),可以看到Migration已經(jīng)成功地寫(xiě)入數(shù)據(jù)庫(kù)表__EFMigrationsHistory了:

    .NET?6開(kāi)發(fā)TodoList應(yīng)用中如何引入數(shù)據(jù)存儲(chǔ)

    看完了這篇文章,相信你對(duì)“.NET 6開(kāi)發(fā)TodoList應(yīng)用中如何引入數(shù)據(jù)存儲(chǔ)”有了一定的了解,如果想了解更多相關(guān)知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道,感謝各位的閱讀!

    向AI問(wèn)一下細(xì)節(jié)

    免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

    AI