.Net Core Web Api_筆記23_api結合EFCore資料庫操作part1_專案前置準備
專案前置準備
建立並配置好visual studio .net core web api專案
.net5 專案中預設若我們將OpenAPI勾選起來會自動配置好Swagger文檔相關的dll與設定
預設用的為Swashbuckle.AspNetCore
https://github.com/domaindrivendev/Swashbuckle.AspNetCore
預設專案的Startup.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.OpenApi.Models; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace Net5EFCoreWebApiApp { public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddControllers(); services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo { Title = "Net5EFCoreWebApiApp", Version = "v1" }); }); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); app.UseSwagger(); app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "Net5EFCoreWebApiApp v1")); } app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); } } } |
這個swagger UI的畫面
會發現再啟動完並沒有產生額外新table
主要是因為在~\Properties\launchSettings.json
"launchUrl": "swagger" 設定的關係
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | { "$schema": "http://json.schemastore.org/launchsettings.json", "iisSettings": { "windowsAuthentication": false, "anonymousAuthentication": true, "iisExpress": { "applicationUrl": "http://localhost:12099", "sslPort": 0 } }, "profiles": { "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, "launchUrl": "swagger", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } }, "Net5EFCoreWebApiApp": { "commandName": "Project", "dotnetRunMessages": "true", "launchBrowser": true, "launchUrl": "swagger", "applicationUrl": "http://localhost:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } } } } |
EF Core的安裝
Microsoft.EntityFrameworkCore.sqlserver
在此要注意由於我目前還是用vs2019 .net5做開發
因此太高版別的EFCore會不支援(需要vs2022 .net6)
這裡改為5.0.13版本(不要用6.x的)
CodeFirst開發前置準備
當我們經過一段時間的需求分析與資料朔模後
會產出一些ERD和表關聯
這裡就用簡單的產品分類與產品項目資訊
設計資料庫中存在的兩張table關聯
這裡資料字典如下
Step1.準備好資料實體模型(各自都對應一張table)於Model Folder下
~\Models\Product.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using System.Threading.Tasks; namespace Net5EFCoreWebApiApp.Models { public class Product { /// <summary> /// 產品ID /// </summary> [Key] public Guid ProId { get; set; } /// <summary> /// 產品名稱 /// </summary> [MaxLength(200)] public string ProTitle { get; set; } /// <summary> /// 產品總數 /// </summary> public int ProSum { get; set; } /// <summary> /// 產品價格 /// </summary> public Decimal ProPrice { get; set; } /// <summary> /// 產品類別ID /// </summary> public Guid PCategoryId { get; set; } [ForeignKey("PCategoryId")] public PCategory PCategory { get; set; } } } |
~\Models\PCategory.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Threading.Tasks; namespace Net5EFCoreWebApiApp.Models { public class PCategory { /// <summary> /// 產品分類ID /// </summary> [Key] public Guid CId { get; set; } /// <summary> /// 產品分類標題 /// </summary> [MaxLength(100)] public string CTitle { get; set; } } } |
Step2.建立DbContext衍生類
~\Data\ProductDbContext.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Net5EFCoreWebApiApp.Models; namespace Net5EFCoreWebApiApp.Data { public class ProductDbContext : DbContext { public ProductDbContext(DbContextOptions<ProductDbContext> options) : base(options) { } public DbSet<Product> Products { get; set; } public DbSet<PCategory> PCategories { get; set; } } } |
Step3.至appsettings.json配置DB connection string
1 2 3 4 5 6 7 8 9 10 11 12 13 | { "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } }, "AllowedHosts": "*", "ConnectionStrings": { "ProductDb" : "Server=.;Database=AspNetEFCoreDb;uid=sa;pwd=rootroot" } } |
Step4.至Startup.cs的ConfigureServices進行DB服務的依賴注入
Startup.cs多引入命名空間
using Microsoft.EntityFrameworkCore;
using Net5EFCoreWebApiApp.Data;
~\Startup.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.OpenApi.Models; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Net5EFCoreWebApiApp.Data; namespace Net5EFCoreWebApiApp { public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddDbContext<ProductDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("ProductDb"))); services.AddControllers(); services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo { Title = "Net5EFCoreWebApiApp", Version = "v1" }); }); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); app.UseSwagger(); app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "Net5EFCoreWebApiApp v1")); } app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); } } } |
Step5.至主程式Program.cs中的Main()去實踐第一次(只執行一次)的DB上下文建立
把原先的程式碼CreateHostBuilder(args).Build().Run();刪掉or註解掉
需引入命名空間Microsoft.Extensions.DependencyInjection
using Microsoft.Extensions.DependencyInjection;
~\Program.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Net5EFCoreWebApiApp.Data; namespace Net5EFCoreWebApiApp { public class Program { public static void Main(string[] args) { //CreateHostBuilder(args).Build().Run(); var host = CreateHostBuilder(args).Build(); using (var scope = host.Services.CreateScope()) { var services = scope.ServiceProvider; try { var dbContext = services.GetRequiredService<ProductDbContext>(); dbContext.Database.EnsureCreated(); } catch (Exception ex) { var logger = services.GetRequiredService<ILogger<Program>>(); logger.LogError(ex, "Create DB structure error. "); } } host.Run(); } public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); }); } } |
我們希望讓Web應用於一開始啟動時
就會在Startup class中註冊的DB上下文服務能獲得
我們自行建立的衍生DbContext物件 "ProductDbContext"
這裡藉由dbContext.Database.EnsureCreated();此方法來創建DB
EnsureCreated() 回傳Boolean
true 代表新的資料庫結構已完成建立。
false 代表資料庫結構已經存在不需重新建立
在啟動以前先觀察目前SSMS是還存在AspNetEFCoreDb這個資料庫的
(沿用之前示範ado.net的資料庫)
因為該DB已存在
這裡有兩種方式(適應不同情境)
情境1.這是全新的一項專案
就是更改DB名稱
再重執行一次應用程式
或是你想要用同一個Db只是若存在既有的先做刪除後建立
// Drop the database if it exists
dbContext.Database.EnsureDeleted();
情境2.這是針對既有已存在的DB要再搭配EF進行開發
沿用之前示範ado.net的資料庫 (AspNetEFCoreDb這個資料庫)
我們首先要啟用專案的migration機制
需要透過nuget補安裝
Microsoft.EntityFrameworkCore.Tools
第一步驟.一定要先啟用和產生初始化的DB Migration
這樣子才能讓EF Core得知有捨麼DB遷移變動跟額外要補增加哪幾張Table到既有的DB當中
「Tools」 - 「NuGet Package Manager」 - 「Package Manager Console」,輸入以下指令:
1 | add-migration {自行命名migration名稱} |
enable-migrations
啟用migration機制
現在到了EF Core就不用了
第二步驟.
接著程式中
應該改為
dbContext.Database.Migrate();
再重啟應用就會看到DB多產生目標資料表以前既有的table不會有影響資料都還在
當然如果不想用啟動專方式也可以透過指令
update-database
當應用程式部屬後通常不太可能類似自己在專案中人工判斷有無初始化或等待資料庫遷移的任務因此也可以在程式端去藉由
DbContext.Database.GetPendingMigrations().Any()直接在應用程式做判斷
最終Program.cs程式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Net5EFCoreWebApiApp.Data; using Microsoft.EntityFrameworkCore; namespace Net5EFCoreWebApiApp { public class Program { public static void Main(string[] args) { //CreateHostBuilder(args).Build().Run(); var host = CreateHostBuilder(args).Build(); using (var scope = host.Services.CreateScope()) { var services = scope.ServiceProvider; try { var dbContext = services.GetRequiredService<ProductDbContext>(); //dbContext.Database.Migrate(); //dbContext.Database.EnsureCreated(); var result = dbContext.Database.EnsureCreated(); if (!result) { if (dbContext.Database.GetPendingMigrations().Any()) { dbContext.Database.Migrate(); } } } catch (Exception ex) { var logger = services.GetRequiredService<ILogger<Program>>(); logger.LogError(ex, "Create DB structure error. "); } } host.Run(); } public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); }); } } |
Ref:
Automatic migration of Entity Framework Core
Where should I put Database.EnsureCreated?
EF core not creating tables on migrate method
Database.Migrate() creating database but not tables, EF & .NET Core 2
How and where to call Database.EnsureCreated and Database.Migrate?
Can't enable migrations for Entity Framework on VS 2017 .NET Core
[Entity Framework 6] Code Frist (3) - Migration commands
3. Migrations
Code First 大道番外-Migrations
留言
張貼留言