从头编写 asp.net core 2.0 web api 基础框架 (4) EF配置
?
第一部分:?https://www.cnblogs.com/frank0812/p/11165940.html
第二部分:https://www.cnblogs.com/frank0812/p/11166163.html
第三部分:https://www.cnblogs.com/frank0812/p/11167963.html
Github源碼地址:https://github.com/solenovex/Building-asp.net-core-2-web-api-starter-template-from-scratch
?
前三部分弄完,我們已經(jīng)可以對(duì)內(nèi)存數(shù)據(jù)進(jìn)行CRUD的基本操作,并且可以在asp.net core 2中集成Nlog了。
下面繼續(xù):
Entity Framework Core 2.0
Entity Framework 是ORM(Object-Relational-Mapping)。ORM是一種讓你可以使用面向?qū)ο蟮姆妒綄?duì)數(shù)據(jù)庫(kù)進(jìn)行查詢(xún)和操作。
簡(jiǎn)單的情況下,ORM可以把數(shù)據(jù)庫(kù)中的表和Model對(duì)象一一映射起來(lái);也有比較復(fù)雜的情況,ORM允許使用OO(面向?qū)ο?#xff09;功能來(lái)做映射,例如:Person作為基類(lèi),Employee作為Person的派生類(lèi),他們倆可以在數(shù)據(jù)庫(kù)中映射成一個(gè)表;或者在沒(méi)有繼承的情況下,數(shù)據(jù)庫(kù)中的一個(gè)表可能和多個(gè)類(lèi)有映射關(guān)系。
EF Core 不是 EF6的升級(jí)版,這個(gè)大家應(yīng)該知道,EF Core是輕量級(jí)、具有很好的擴(kuò)展性的,并且是跨平臺(tái)的EF版本。
EF Core 目前有很多Providers,所以支持很多種數(shù)據(jù)庫(kù),包括:MSSQL,SQLite,SQL Compact,Postgres,MySql,DB2等等。而且還有一個(gè)內(nèi)存的Provider,用于測(cè)試和開(kāi)發(fā)。開(kāi)發(fā)UWP應(yīng)用的時(shí)候也可以使用EF Core(用SQLite Provider)。
EF Core支持兩種模式:
Code First:簡(jiǎn)單理解為 先寫(xiě)C#(Model),然后生成數(shù)據(jù)庫(kù)。
Database First:現(xiàn)在數(shù)據(jù)庫(kù)中建立表,然后生成C#的Model。
由于用asp.net core 2.0開(kāi)發(fā)的項(xiàng)目基本都是新項(xiàng)目,所以建議使用Code First。
創(chuàng)建 Entity
Entity就是普通的C#類(lèi),就像Dto一樣。Dto是與外界打交道的Model,entity則不一樣,有一些Dto的計(jì)算屬性我們并不像保存在數(shù)據(jù)庫(kù)中,所以entity中沒(méi)有這些屬性;而數(shù)據(jù)從entity傳遞到Dto后某些屬性也會(huì)和數(shù)據(jù)庫(kù)里面的形式不一樣。
首先把我們?cè)瓉?lái)的Product和Material這兩個(gè)Dto的名字重構(gòu)一下,改成ProductDto和MaterialDto。
建立一個(gè)Entities文件夾,在里面建立Product.cs:
namespace CoreBackend.Api.Entities {public class Product{public int Id { get; set; }public string Name { get; set; }public float Price { get; set; }} }DbContext
EFCore使用一個(gè)DbContext和數(shù)據(jù)庫(kù)打交道,它代表著和數(shù)據(jù)庫(kù)之間的一個(gè)Session,可以用來(lái)查詢(xún)和保存我們的entities。
DbContext需要一個(gè)Provider,以便能訪問(wèn)數(shù)據(jù)庫(kù)(這里我們就用LocalDB吧)。
我們就建立一個(gè)DbContext吧(大一點(diǎn)的項(xiàng)目會(huì)使用多個(gè)DbContext)。建立MyContext并集成DbContext:
namespace CoreBackend.Api.Entities {public class MyContext : DbContext{public DbSet<Product> Products { get; set; }} }這里我們?yōu)镻roduct建立了一個(gè)類(lèi)型為DbSet<T>的屬性,它可以用來(lái)查詢(xún)和保存實(shí)例(針對(duì)DbSet的Linq查詢(xún)語(yǔ)句將會(huì)被解釋成針對(duì)數(shù)據(jù)庫(kù)的查詢(xún)語(yǔ)句)。
因?yàn)槲覀冃枰褂眠@個(gè)MyContext,所以就需要先在Container中注冊(cè)它,然后就可以在依賴(lài)注入中使用了。
打開(kāi)Startup.cs,修改ConfigureServices,添加這一句話:
services.AddDbContext<MyContext>();使用AddDbContext這個(gè)Extension method為MyContext在Container中進(jìn)行注冊(cè),它默認(rèn)的生命周期使Scoped。
但是它如何連接數(shù)據(jù)庫(kù)?這就需要連接字符串,我們需要為DbContext提供連接字符串,這里有兩種方式。
第一種是在MyContext中override OnConfiguring這個(gè)方法:
namespace CoreBackend.Api.Entities {public class MyContext : DbContext{public DbSet<Product> Products { get; set; }protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder){optionsBuilder.UseSqlServer("xxxx connection string");base.OnConfiguring(optionsBuilder);}} }其中的參數(shù)optionsBuilder提供了一個(gè)UseSqlServer()這個(gè)方法,它告訴Dbcontext將會(huì)被用來(lái)連接Sql Server數(shù)據(jù)庫(kù),在這里就可以提供連接字符串,這就是第一種方法。
第二種方法:
先大概看一下DbContext的源碼的定義:
namespace Microsoft.EntityFrameworkCore {public class DbContext : IDisposable, IInfrastructure<IServiceProvider>, IDbContextDependencies, IDbSetCache, IDbContextPoolable{public DbContext([NotNullAttribute] DbContextOptions options);有一個(gè)Constructor帶有一個(gè)DbContextOptions參數(shù),那我們就在MyContext種建立一個(gè)Constructor,并overload這個(gè)帶有參數(shù)的Constructor。
namespace CoreBackend.Api.Entities {public class MyContext : DbContext{public MyContext(DbContextOptions<MyContext> options):base(options){}public DbSet<Product> Products { get; set; }} }這種方法相對(duì)第一種的優(yōu)點(diǎn)是:它可以在我們注冊(cè)MyContext的時(shí)候就提供options,顯然這樣做比第一種override OnConfiguring更合理。
然后返回Startup:
public void ConfigureServices(IServiceCollection services){services.AddMvc(); #if DEBUGservices.AddTransient<IMailService, LocalMailService>(); #elseservices.AddTransient<IMailService, CloudMailService>(); #endifvar connectionString = @"Server=(localdb)\MSSQLLocalDB;Database=ProductDB;Trusted_Connection=True";services.AddDbContext<MyContext>(o => o.UseSqlServer(connectionString));}使用AddDbContext的另一個(gè)overload的方法,它可以帶一個(gè)參數(shù),在里面調(diào)用UseSqlServer。
關(guān)于連接字符串,我是用的是LocalDb,實(shí)例名是MSSQLLocalDB。可以在命令行查詢(xún)本機(jī)LocalDb的實(shí)例,使用sqllocaldb info:
也可以通過(guò)VS的Sql Server Object Explorer查看:
連接字符串中的ProductDb是數(shù)據(jù)庫(kù)名;連接字符串的最后一部分表示這是一個(gè)受信任的連接,也就是說(shuō)使用了集成驗(yàn)證,在windows系統(tǒng)就是指windows憑證。
生成數(shù)據(jù)庫(kù)
因?yàn)槲覀兪褂玫氖荂ode First,所以如果還沒(méi)有數(shù)據(jù)庫(kù)的話,它應(yīng)該會(huì)自動(dòng)建立一個(gè)數(shù)據(jù)庫(kù)。
打開(kāi)MyContext:
public MyContext(DbContextOptions<MyContext> options):base(options){Database.EnsureCreated();}這個(gè)Constructor在被依賴(lài)注入的時(shí)候會(huì)被調(diào)用,在里面寫(xiě)Database.EnsureCreated()。其中Database是DbContext的一個(gè)屬性對(duì)象。
EnsureCreated()的作用是,如果有數(shù)據(jù)庫(kù)存在,那么什么也不會(huì)發(fā)生。但是如果沒(méi)有,那么就會(huì)創(chuàng)建一個(gè)數(shù)據(jù)庫(kù)。
但是現(xiàn)在就運(yùn)行的話,并不會(huì)創(chuàng)建數(shù)據(jù)庫(kù),因?yàn)闆](méi)有創(chuàng)建MyContext的實(shí)例,也就不會(huì)調(diào)用Constructor里面的內(nèi)容。
那我們就建立一個(gè)臨時(shí)的Controller,然后注入MyContext,此時(shí)就調(diào)用了MyContext的Constructor:
namespace CoreBackend.Api.Controllers {[Route("api/[controller]")]public class TestController: Controller{private MyContext _context;public TestController(MyContext context){_context = context;}[HttpGet]public IActionResult Get(){return Ok();}} }使用Postman訪問(wèn)Get這個(gè)Action后,我們可以從Debug窗口看見(jiàn)一些創(chuàng)建數(shù)據(jù)庫(kù)和表的Sql語(yǔ)句:
然后我們查看一下Sql Server Object Explorer:
我們可以看到數(shù)據(jù)庫(kù)建立好了,里面還有dbo.Products這個(gè)表。
Database.EnsureCreated()確實(shí)可以保證創(chuàng)建數(shù)據(jù)庫(kù),但是隨著代碼不斷被編寫(xiě),我們的Model不斷再改變,數(shù)據(jù)庫(kù)應(yīng)該也隨之改變,而EnsureCreated()就不夠了,這就需要遷移(Migration)。
不過(guò)遷移之前,我們先看看Product這個(gè)表的具體字段屬性:
Product的Id作為了主鍵,而Name這個(gè)字符串的長(zhǎng)度是max,而Price沒(méi)有精度限制,這樣不行。我們需要對(duì)Model生成的表的字段進(jìn)行限制!
解釋一下:Product這個(gè)entity中的Id,根據(jù)約定(Id或者ProductId)會(huì)被視為映射表的主鍵,并且該主鍵是自增的。
如果不使用Id或者ProductId這兩個(gè)名字作為主鍵的話,我們可以通過(guò)兩種方式把該屬性設(shè)置成為主鍵:Data Annotation注解和Fluet Api。我只在早期使用Data Annotation,后來(lái)一直使用Fluent Api,所以我這里只介紹Fluent Api吧。
Fluet Api
針對(duì)Product這個(gè)entity,我們要把它映射成一個(gè)數(shù)據(jù)庫(kù)的表,所以針對(duì)每個(gè)屬性,可能需要設(shè)定一些限制,例如最大長(zhǎng)度,是否必填等等。
針對(duì)Product,我們可以在MyContext里面override OnModelCreating這個(gè)方法,然后這樣寫(xiě):
protected override void OnModelCreating(ModelBuilder modelBuilder){modelBuilder.Entity<Product>().HasKey(x => x.Id);modelBuilder.Entity<Product>().Property(x => x.Name).IsRequired().HasMaxLength(50);modelBuilder.Entity<Product>().Property(x => x.Price).HasColumnType("decimal(8,2)");}第一行表示設(shè)置Id為主鍵(其實(shí)我們并不需要這么做)。然后Name屬性是必填的,而且最大長(zhǎng)度是50。最后Price的精度是8,2,數(shù)據(jù)庫(kù)里的類(lèi)型為decimal。
fluent api有很多方法,具體請(qǐng)查看文檔:https://docs.microsoft.com/en-us/ef/core/modeling/
然后,我們就會(huì)發(fā)現(xiàn)一個(gè)嚴(yán)重的問(wèn)題。如果項(xiàng)目里面有很多entity,那么所有的fluent api配置都需要寫(xiě)在OnModelCreating這個(gè)方法里,那太多了。
所以我們改進(jìn)一下,使用IEntityTypeConfiguration<T>。建立一個(gè)叫ProductConfiguration的類(lèi):
public class ProductConfiguration : IEntityTypeConfiguration<Product>{public void Configure(EntityTypeBuilder<Product> builder){builder.HasKey(x => x.Id);builder.Property(x => x.Name).IsRequired().HasMaxLength(50);builder.Property(x => x.Price).HasColumnType("decimal(8,2)");}}把剛才在MyContext里寫(xiě)的配置都移動(dòng)到這里,然后修改一些MyContext的OnModelCreating方法:
protected override void OnModelCreating(ModelBuilder modelBuilder){modelBuilder.ApplyConfiguration(new ProductConfiguration());}就是把ProductConfiguration里面寫(xiě)的配置加載進(jìn)來(lái),和之前的效果是一樣的。
但是項(xiàng)目中如果有很多entities的話也需要寫(xiě)很多行代碼,更好的做法是寫(xiě)一個(gè)方法,可以加載所有實(shí)現(xiàn)了IEntityTypeConfiguration<T>的實(shí)現(xiàn)類(lèi)。在老版的asp.net web api 2.2里面有一個(gè)方法可以從某個(gè)Assembly加載所有繼承于EntityTypeConfiguration的類(lèi),但是entity framework core并沒(méi)有提供類(lèi)似的方法,以后我們自己寫(xiě)一個(gè)吧,現(xiàn)在先這樣。
然后把數(shù)據(jù)庫(kù)刪掉,重新生成一下數(shù)據(jù)庫(kù):
很好!
遷移 Migration
隨著代碼的更改,數(shù)據(jù)庫(kù)也會(huì)跟著變,所有EnsureCreated()不滿足要求。migration就允許我們把數(shù)據(jù)庫(kù)從一個(gè)版本升級(jí)到另一個(gè)版本。那我們就研究一下,首先把數(shù)據(jù)庫(kù)刪了,然后創(chuàng)建第一個(gè)遷移版本。
打開(kāi)Package Manager Console,做個(gè)遷移?Add-Migration?xxx:
Add-Migration 然后接著是一個(gè)你起的名字。
然后看一下VS的Solution Explorer 會(huì)發(fā)現(xiàn)生成了一個(gè)Migrations目錄:
里面有兩個(gè)文件,一個(gè)是Snapshot,它是目前entity的狀態(tài):
namespace CoreBackend.Api.Migrations {[DbContext(typeof(MyContext))]partial class MyContextModelSnapshot : ModelSnapshot{protected override void BuildModel(ModelBuilder modelBuilder){ #pragma warning disable 612, 618modelBuilder.HasAnnotation("ProductVersion", "2.0.0-rtm-26452").HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);modelBuilder.Entity("CoreBackend.Api.Entities.Product", b =>{b.Property<int>("Id").ValueGeneratedOnAdd();b.Property<string>("Name").IsRequired().HasMaxLength(50);b.Property<float>("Price").HasColumnType("decimal(8,2)");b.HasKey("Id");b.ToTable("Products");}); #pragma warning restore 612, 618}} }這就是當(dāng)前Product這個(gè)Model的狀態(tài)細(xì)節(jié),包括我們通過(guò)Fluent Api為其添加的映射限制等。
另一個(gè)文件是xxxx_ProductInfoDbInitialMigration,下劃線后邊的部分就是剛才Add-Migration命令后邊跟著的名字參數(shù)。
namespace CoreBackend.Api.Migrations {public partial class ProductInfoDbInitialMigration : Migration{protected override void Up(MigrationBuilder migrationBuilder){migrationBuilder.CreateTable(name: "Products",columns: table => new{Id = table.Column<int>(type: "int", nullable: false).Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),Name = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),Price = table.Column<float>(type: "decimal(8,2)", nullable: false)},constraints: table =>{table.PrimaryKey("PK_Products", x => x.Id);});}protected override void Down(MigrationBuilder migrationBuilder){migrationBuilder.DropTable(name: "Products");}} }這里面包含著migration builder需要的代碼,用來(lái)遷移這個(gè)版本的數(shù)據(jù)庫(kù)。里面有Up方法,就是從當(dāng)前版本升級(jí)到下一個(gè)版本;還有Down方法,就是從下一個(gè)版本再退回到當(dāng)前版本。
我們也可以不使用 Add-Migration命令,手寫(xiě)上面這些代碼也行,我感覺(jué)還是算了吧。
另外還有一件事,那就是要保證遷移migration都有效的應(yīng)用于數(shù)據(jù)庫(kù)了,那就是另一個(gè)命令?Update-Database。
先等一下,我們也可以使用代碼來(lái)達(dá)到同樣的目的,打開(kāi)MyContext:
public MyContext(DbContextOptions<MyContext> options): base(options){Database.Migrate();}把之前的EnsureCreated改成Database.Migrate(); 如果數(shù)據(jù)庫(kù)還沒(méi)刪除,那就最后刪除一次。
運(yùn)行,并除法TestController:
然后會(huì)看見(jiàn)Product表,除此之外還有一個(gè)__EFMigrationHistory表,看看有啥:
這個(gè)表里面保存了哪些遷移已經(jīng)被應(yīng)用于這個(gè)數(shù)據(jù)庫(kù)了。這也保證了Database.Migrate()或者Update-database命令不會(huì)執(zhí)行重復(fù)的遷移migration。
我們?cè)倥獋€(gè)遷移,為Product添加一個(gè)屬性:
namespace CoreBackend.Api.Entities {public class Product{public int Id { get; set; }public string Name { get; set; }public float Price { get; set; }public string Description { get; set; }}public class ProductConfiguration : IEntityTypeConfiguration<Product>{public void Configure(EntityTypeBuilder<Product> builder){builder.HasKey(x => x.Id);builder.Property(x => x.Name).IsRequired().HasMaxLength(50);builder.Property(x => x.Price).HasColumnType("decimal(8,2)");builder.Property(x => x.Description).HasMaxLength(200);}} }執(zhí)行Add-Migration后,會(huì)在Migrations目錄生成了一個(gè)新的文件:
namespace CoreBackend.Api.Migrations {public partial class AddDescriptionToProduct : Migration{protected override void Up(MigrationBuilder migrationBuilder){migrationBuilder.AddColumn<string>(name: "Description",table: "Products",type: "nvarchar(200)",maxLength: 200,nullable: true);}protected override void Down(MigrationBuilder migrationBuilder){migrationBuilder.DropColumn(name: "Description",table: "Products");}} }然后這次執(zhí)行Update-Database命令:
加上verbose參數(shù)就是顯示執(zhí)行過(guò)程的明細(xì)而已。
不用運(yùn)行,看看數(shù)據(jù)庫(kù):
Description被添加上了,然后看看遷移表:
目前差不太多了,但還有一個(gè)安全隱患。它是:
如何安全的保存敏感的配置數(shù)據(jù),例如:連接字符串
保存連接字符串,你可能會(huì)想到appSettings.json,但這不是一個(gè)好的想法。在本地開(kāi)發(fā)的時(shí)候還沒(méi)有什么問(wèn)題(使用的是集成驗(yàn)證),但是你要部署到服務(wù)器的時(shí)候,數(shù)據(jù)庫(kù)連接字符串可能包括用戶(hù)名和密碼(Sql Server的另一種驗(yàn)證方式)。加入你不小心把a(bǔ)ppSettings.json或?qū)懙紺#里面的連接字符串代碼提交到了Git或TFS,那么這個(gè)用戶(hù)名和密碼包括服務(wù)器的名稱(chēng)可能就被暴露了,這樣做很不安全。
我們可以這樣做,首先針對(duì)開(kāi)發(fā)環(huán)境(development environment)把C#代碼中的連接字符串拿掉,把它放到appSettings.json里面。然后針對(duì)正式生產(chǎn)環(huán)境(production environment),我們使用環(huán)境變量來(lái)保存這些敏感數(shù)據(jù)。
開(kāi)發(fā)環(huán)境:
appSettings.json:
{"mailSettings": {"mailToAddress": "admin__json@qq.com","mailFromAddress": "noreply__json@qq.com"},"connectionStrings": {"productionInfoDbConnectionString": "Server=(localdb)\\MSSQLLocalDB;Database=ProductDB;Trusted_Connection=True"} }Startup.cs:
public void ConfigureServices(IServiceCollection services){services.AddMvc(); #if DEBUGservices.AddTransient<IMailService, LocalMailService>(); #elseservices.AddTransient<IMailService, CloudMailService>(); #endifvar connectionString = Configuration["connectionStrings:productionInfoDbConnectionString"];services.AddDbContext<MyContext>(o => o.UseSqlServer(connectionString));}然后你可以設(shè)斷點(diǎn)看看connectionString的值。目前項(xiàng)目的環(huán)境變量是Production,先改成Development:
然后斷點(diǎn)調(diào)試:
可以看到這兩個(gè)JsonConfigurationProvider就是appSettings的兩個(gè)文件的配置。
這個(gè)就是appSettings.json,里面包含著我們剛才添加的連接字符串。
由于當(dāng)前是Development環(huán)境,所以如果你查看另外一個(gè)JsonConfigurationProvider的話,會(huì)發(fā)現(xiàn)它里面的值是空的(Data數(shù)是0).
所以沒(méi)有問(wèn)題。
生產(chǎn)環(huán)境:
在項(xiàng)目的屬性--Debug里面,我們看到了環(huán)境變量:
而這個(gè)環(huán)境變量,我們可以在程序中讀取出來(lái),所以可以在這里添加連接字符串:
注意它的key,要和appSettings.json里面的整體結(jié)構(gòu)一致;Value呢應(yīng)該是給一個(gè)服務(wù)器的數(shù)據(jù)庫(kù)的字符串,這里就隨便弄個(gè)假的吧。別忘了把Development改成Production。
然后調(diào)試一下:
沒(méi)錯(cuò)。如果你仔細(xì)調(diào)試一下看看的話:就會(huì)從EnvironmentVariablesConfigurationProvider的第64個(gè)找到我們剛才寫(xiě)到連接字符串:
但是還沒(méi)完。
打開(kāi)項(xiàng)目的launchSettings.json:
你會(huì)發(fā)現(xiàn):
{"iisSettings": {"windowsAuthentication": false,"anonymousAuthentication": true,"iisExpress": {"applicationUrl": "http://localhost:60835/","sslPort": 0}},"profiles": {"IIS Express": {"commandName": "IISExpress","launchBrowser": true,"environmentVariables": {"connectionStrings:productionInfoDbConnectionString": "Server=.;Database=ProductDB;UserId=sa;Password=pass;","ASPNETCORE_ENVIRONMENT": "Production"}},"CoreBackend.Api": {"commandName": "Project","launchBrowser": true,"environmentVariables": {"ASPNETCORE_ENVIRONMENT": "Development"},"applicationUrl": "http://localhost:60836/"}} }連接字符串在這里。這個(gè)文件一般都會(huì)源碼控制給忽略,也不會(huì)在發(fā)布的時(shí)候發(fā)布到服務(wù)器。那么服務(wù)器怎么讀取到這個(gè)連接字符串呢???
看上面調(diào)試EnvironmentVariablesConfigurationProvider的值,會(huì)發(fā)現(xiàn)里面有幾十個(gè)變量,這些基本都不是來(lái)自launchSettings.json,它們是從系統(tǒng)層面上定義的!!
這回我們這樣操作:
把launchSettings里面的連接字符串去掉:
{"iisSettings": {"windowsAuthentication": false,"anonymousAuthentication": true,"iisExpress": {"applicationUrl": "http://localhost:60835/","sslPort": 0}},"profiles": {"IIS Express": {"commandName": "IISExpress","launchBrowser": true,"environmentVariables": {"ASPNETCORE_ENVIRONMENT": "Production"}},"CoreBackend.Api": {"commandName": "Project","launchBrowser": true,"environmentVariables": {"ASPNETCORE_ENVIRONMENT": "Development"},"applicationUrl": "http://localhost:60836/"}} }然后這里自然也就沒(méi)有了:
現(xiàn)在任何json文件都沒(méi)有敏感信息了。
現(xiàn)在我們要把連接字符串添加到系統(tǒng)變量中。
在win10搜索框輸入 envi:
然后點(diǎn)擊上面的結(jié)果:
點(diǎn)擊環(huán)境變量:
這里面上邊是用戶(hù)的變量,下面是系統(tǒng)的變量,這就是剛才EnvironmentVariableConfigurationProvider里面調(diào)試出來(lái)的那一堆環(huán)境變量。
而這個(gè)地方就是在你應(yīng)該服務(wù)器上添加連接字符串的地方。再看一下調(diào)試:
Environment的Provider在第4個(gè)位置,appSettings.production.json的在第3個(gè)位置。也就是說(shuō)如果appSettings.Product.json和系統(tǒng)環(huán)境變量都有一樣Key的連接字符串,那么程序會(huì)選擇系統(tǒng)環(huán)境變量的值,因?yàn)樗呛筮叺呐渲脮?huì)覆蓋前邊的配置。
在系統(tǒng)環(huán)境變量中添加:
然后調(diào)試運(yùn)行(需要重啟VS,以便新添加的系統(tǒng)環(huán)境變量生效):
嗯,沒(méi)問(wèn)題!
種子數(shù)據(jù) Seed Data
目前EF Core還沒(méi)有內(nèi)置的方法來(lái)做種子數(shù)據(jù)。那么自己寫(xiě):
建立一個(gè)MyContextExtensions.cs:
namespace CoreBackend.Api.Entities {public static class MyContextExtensions{public static void EnsureSeedDataForContext(this MyContext context){if (context.Products.Any()){return;}var products = new List<Product>{new Product{Name = "牛奶",Price = 2.5f,Description = "這是牛奶啊"},new Product{Name = "面包",Price = 4.5f,Description = "這是面包啊"},new Product{Name = "啤酒",Price = 7.5f,Description = "這是啤酒啊"}};context.Products.AddRange(products);context.SaveChanges();}} }這是個(gè)Extension method,如果數(shù)據(jù)庫(kù)沒(méi)有數(shù)據(jù),那就弄點(diǎn)種子數(shù)據(jù),AddRange可以添加批量數(shù)據(jù)到Context(被Context追蹤),但是到這還沒(méi)有插入到數(shù)據(jù)庫(kù)。使用SaveChanges會(huì)把數(shù)據(jù)保存到數(shù)據(jù)庫(kù)。
然后再Startup的Configure方法中調(diào)用這個(gè)method:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory,MyContext myContext){// loggerFactory.AddProvider(new NLogLoggerProvider());loggerFactory.AddNLog();if (env.IsDevelopment()){app.UseDeveloperExceptionPage();}else{app.UseExceptionHandler();}myContext.EnsureSeedDataForContext();app.UseStatusCodePages();app.UseMvc();}首先注入MyContext,然后調(diào)用這個(gè)extension method。
然后把系統(tǒng)環(huán)境變量中的連接字符串刪了把,并且把項(xiàng)目屬性Debug中改成Development,這時(shí)候需要重啟VS,因?yàn)橐话悱h(huán)境變量是在軟件啟動(dòng)的時(shí)候附加到其內(nèi)存的,軟件沒(méi)關(guān)的情況下如果把系統(tǒng)環(huán)境變量給刪了,在軟件的內(nèi)存中應(yīng)該還是能找到該環(huán)境變量,所以軟件得重啟才能獲取最新的環(huán)境變量們。重啟VS,并運(yùn)行:
種子數(shù)據(jù)進(jìn)去了
?
https://www.cnblogs.com/cgzl/p/7661805.html
轉(zhuǎn)載于:https://www.cnblogs.com/frank0812/p/11168796.html
總結(jié)
以上是生活随笔為你收集整理的从头编写 asp.net core 2.0 web api 基础框架 (4) EF配置的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 全域低风险什么意思
- 下一篇: 2019年上半年总结__下半年计划