使用.NET?6開發(fā)TodoList應(yīng)用之引入數(shù)據(jù)存儲(chǔ)的思路詳解
需求
作為后端CRUD程序員(bushi,數(shù)據(jù)存儲(chǔ)是開發(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ù),除此之外,我們還可以在開發(fā)階段選擇內(nèi)存數(shù)據(jù)庫(kù),在云上部署的時(shí)候還可以選擇類似Azure Cosmos DB
/AWS DynamoDB
以及云上提供的多種關(guān)系型數(shù)據(jù)庫(kù)。
應(yīng)用程序使用數(shù)據(jù)庫(kù)服務(wù),一般都是借助成熟的第三方ORM框架,而在.NET后端服務(wù)開發(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í)體定義和操作后面專門來(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)榉荳indows平臺(tái)導(dǎo)致的示例無(wú)法運(yùn)行的問(wèn)題。
原理和思路
因?yàn)槲覀儗?duì)開發(fā)環(huán)境和生產(chǎn)環(huán)境的配置有差異,那先來(lái)看看共性的部分:
- 引入EFCore相關(guān)的nuget包并進(jìn)行配置;
- 添加DbContext對(duì)象并進(jìn)行依賴注入;
- 修改相關(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)的開發(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è)?code>Application中新建一個(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ù)訪問(wèn)邏輯,或者有足夠的理由需要使用Repository
模式,那就保持架構(gòu)上的簡(jiǎn)潔,在Application
層的多個(gè)CQRS Handlers
中直接注入該IApplicationDbContext
去訪問(wèn)數(shù)據(jù)庫(kù)并進(jìn)行操作。如果需要使用Repository
模式,那在這里就沒有必要定義這個(gè)接口來(lái)使用了,Application
中只需要定義IRepository<T>
,在Infrastructure
中實(shí)現(xiàn)的BaseRepository
中訪問(wèn)DbContext
即可。
我們后面是需要使用Repository
的,是因?yàn)橄M菔咀畛S玫拈_發(fā)模式,但是在這一篇中我保留IApplicationDbConetxt
的原因是也希望展示一種不同的實(shí)現(xiàn)風(fēng)格,后面我們還是會(huì)專注到Repository
上的。
需要的對(duì)象添加好了,下一步是配置DbContext,我們還是遵循當(dāng)前的架構(gòu)風(fēng)格,在Infrastructure
項(xiàng)目中添加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. 配置文件修改
我們對(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
的話,連接字符串里是可以不寫的,但是為了展示如果使用的不是默認(rèn)端口應(yīng)該如何配置,還是顯式寫在這里了供大家參考。
4. 主程序配置
在Api
項(xiàng)目中,我們只需要調(diào)用上面寫好的擴(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)成功地寫入數(shù)據(jù)庫(kù)表__EFMigrationsHistory
了:
本篇文章僅完成了數(shù)據(jù)存儲(chǔ)服務(wù)的配置工作,目前還沒有添加任何實(shí)體對(duì)象和數(shù)據(jù)庫(kù)表定義,所以暫時(shí)沒有可視化的驗(yàn)證,僅我們可以運(yùn)行程序看我們的配置是否成功:
總結(jié)
在本文中,我們探討并實(shí)現(xiàn)了如何給.NET 6 Web API項(xiàng)目添加數(shù)據(jù)存儲(chǔ)服務(wù)并進(jìn)行配置,下一篇開始將會(huì)深入數(shù)據(jù)存儲(chǔ)部分,定義實(shí)體,構(gòu)建Repository模式和SeedData等操作。
除了本文演示的最基礎(chǔ)的使用方式意外,在實(shí)際使用的過(guò)程中,我們可能會(huì)遇到類似:為多個(gè)DbContext
分別生成Migrations或者為同一個(gè)DbContext
根據(jù)不同的環(huán)境生成不同Database Provider適用的Migrations等情況,擴(kuò)展閱讀如下,在這里就不做進(jìn)一步的演示了,也許以后有機(jī)會(huì)可以單獨(dú)寫篇實(shí)踐指南:
參考資料
到此這篇關(guān)于使用.NET 6開發(fā)TodoList應(yīng)用之引入數(shù)據(jù)存儲(chǔ)的文章就介紹到這了,更多相關(guān).NET 6開發(fā)TodoList引入數(shù)據(jù)存儲(chǔ)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- .NET 6開發(fā)TodoList應(yīng)用之實(shí)現(xiàn)查詢分頁(yè)
- .NET 6開發(fā)TodoList應(yīng)用之實(shí)現(xiàn)ActionFilter
- .NET 6開發(fā)TodoList應(yīng)用之實(shí)現(xiàn)接口請(qǐng)求驗(yàn)證
- .NET?6開發(fā)TodoList應(yīng)用之實(shí)現(xiàn)DELETE請(qǐng)求與HTTP請(qǐng)求冪等性
- .NET 6開發(fā)TodoList應(yīng)用之實(shí)現(xiàn)PUT請(qǐng)求
- .NET 6開發(fā)TodoList應(yīng)用之實(shí)現(xiàn)全局異常處理
- .NET 6開發(fā)TodoList應(yīng)用之使用AutoMapper實(shí)現(xiàn)GET請(qǐng)求
- .NET?6開發(fā)TodoList應(yīng)用之實(shí)現(xiàn)Repository模式
- .NET?6開發(fā)TodoList應(yīng)用之使用MediatR實(shí)現(xiàn)POST請(qǐng)求
- .NET 6開發(fā)TodoList應(yīng)用引入數(shù)據(jù)存儲(chǔ)
- .NET?6開發(fā)TodoList應(yīng)用引入第三方日志庫(kù)
- .NET 6開發(fā)TodoList應(yīng)用實(shí)現(xiàn)結(jié)構(gòu)搭建
- .NET?6開發(fā)TodoList應(yīng)用實(shí)現(xiàn)系列背景
- 使用.NET?6開發(fā)TodoList應(yīng)用之領(lǐng)域?qū)嶓w創(chuàng)建原理和思路
- .NET?6開發(fā)TodoList應(yīng)用之請(qǐng)求日志組件HttpLogging介紹
相關(guān)文章
Linux安裝.Net core 環(huán)境并運(yùn)行項(xiàng)目的方法
這篇文章主要介紹了Linux安裝.Net core 環(huán)境并運(yùn)行項(xiàng)目,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08在一個(gè)網(wǎng)站下再以虛擬目錄的方式掛多個(gè)網(wǎng)站的方法
在一個(gè)網(wǎng)站下再以虛擬目錄的方式掛N個(gè)網(wǎng)站的方法2010-04-04ASP.NET Sql Server安裝向?qū)В╝spnet_regsql.exe)錯(cuò)誤解決一例
這個(gè)程序是注冊(cè)網(wǎng)站的數(shù)據(jù)庫(kù),從而提供Membership , Role , Profile 等等諸多功能和管理權(quán)限的控件的使用。2010-03-03.NET下實(shí)現(xiàn)數(shù)字和字符相混合的驗(yàn)證碼實(shí)例
這篇文章介紹了.NET下實(shí)現(xiàn)數(shù)字和字符相混合的驗(yàn)證碼實(shí)例,有需要的朋友可以參考一下2013-11-11ASP.NET Core按用戶等級(jí)授權(quán)的方法
這篇文章主要介紹了ASP.NET Core按用戶等級(jí)授權(quán),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-01-01