1. 程式人生 > >【轉載】從頭編寫 asp.net core 2.0 web api 基礎框架 (4) EF配置

【轉載】從頭編寫 asp.net core 2.0 web api 基礎框架 (4) EF配置

滿足 sqlserver init 集成 字符串 用戶 database aspnet nco

【轉載】從頭編寫 asp.net core 2.0 web api 基礎框架 (4) EF配置

Github源碼地址:https://github.com/solenovex/Building-asp.net-core-2-web-api-starter-template-from-scratch

前三部分弄完,我們已經可以對內存數據進行CRUD的基本操作,並且可以在asp.net core 2中集成Nlog了。

下面繼續:

Entity Framework Core 2.0

Entity Framework 是ORM(Object-Relational-Mapping)。ORM是一種讓你可以使用面向對象的範式對數據庫進行查詢和操作。

簡單的情況下,ORM可以把數據庫中的表和Model對象一一映射起來;也有比較復雜的情況,ORM允許使用OO(面向對象)功能來做映射,例如:Person作為基類,Employee作為Person的派生類,他們倆可以在數據庫中映射成一個表;或者在沒有繼承的情況下,數據庫中的一個表可能和多個類有映射關系。

EF Core 不是 EF6的升級版,這個大家應該知道,EF Core是輕量級、具有很好的擴展性的,並且是跨平臺的EF版本。

EF Core 目前有很多Providers,所以支持很多種數據庫,包括:MSSQL,SQLite,SQL Compact,Postgres,MySql,DB2等等。而且還有一個內存的Provider,用於測試和開發。開發UWP應用的時候也可以使用EF Core(用SQLite Provider)。

EF Core支持兩種模式:

Code First:簡單理解為 先寫C#(Model),然後生成數據庫。

Database First:現在數據庫中建立表,然後生成C#的Model。

由於用asp.net core 2.0開發的項目基本都是新項目,所以建議使用Code First。

創建 Entity

Entity就是普通的C#類,就像Dto一樣。Dto是與外界打交道的Model,entity則不一樣,有一些Dto的計算屬性我們並不像保存在數據庫中,所以entity中沒有這些屬性;而數據從entity傳遞到Dto後某些屬性也會和數據庫裏面的形式不一樣。

首先把我們原來的Product和Material這兩個Dto的名字重構一下,改成ProductDto和MaterialDto。

建立一個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使用一個DbContext和數據庫打交道,它代表著和數據庫之間的一個Session,可以用來查詢和保存我們的entities。

DbContext需要一個Provider,以便能訪問數據庫(這裏我們就用LocalDB吧)。

我們就建立一個DbContext吧(大一點的項目會使用多個DbContext)。建立MyContext並集成DbContext:

技術分享圖片 技術分享圖片
namespace CoreBackend.Api.Entities
{
    public class MyContext : DbContext
    {
        public DbSet<Product> Products { get; set; }
    }
}
技術分享圖片 技術分享圖片

這裏我們為Product建立了一個類型為DbSet<T>的屬性,它可以用來查詢和保存實例(針對DbSet的Linq查詢語句將會被解釋成針對數據庫的查詢語句)。

因為我們需要使用這個MyContext,所以就需要先在Container中註冊它,然後就可以在依賴註入中使用了。

打開Startup.cs,修改ConfigureServices,添加這一句話:

services.AddDbContext<MyContext>();

使用AddDbContext這個Extension method為MyContext在Container中進行註冊,它默認的生命周期使Scoped。

但是它如何連接數據庫?這就需要連接字符串,我們需要為DbContext提供連接字符串,這裏有兩種方式。

第一種是在MyContext中override OnConfiguring這個方法:

技術分享圖片 技術分享圖片
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);
        }
    }
}
技術分享圖片 技術分享圖片

其中的參數optionsBuilder提供了一個UseSqlServer()這個方法,它告訴Dbcontext將會被用來連接Sql Server數據庫,在這裏就可以提供連接字符串,這就是第一種方法。

第二種方法:

先大概看一下DbContext的源碼的定義:

技術分享圖片 技術分享圖片
namespace Microsoft.EntityFrameworkCore
{
    public class DbContext : IDisposable, IInfrastructure<IServiceProvider>, IDbContextDependencies, IDbSetCache, IDbContextPoolable
    {
        public DbContext([NotNullAttribute] DbContextOptions options);
技術分享圖片 技術分享圖片

有一個Constructor帶有一個DbContextOptions參數,那我們就在MyContext種建立一個Constructor,並overload這個帶有參數的Constructor。

技術分享圖片 技術分享圖片
namespace CoreBackend.Api.Entities
{
    public class MyContext : DbContext
    {
        public MyContext(DbContextOptions<MyContext> options)
            :base(options)
        {
            
        }

        public DbSet<Product> Products { get; set; }
    }
}
技術分享圖片 技術分享圖片

這種方法相對第一種的優點是:它可以在我們註冊MyContext的時候就提供options,顯然這樣做比第一種override OnConfiguring更合理。

然後返回Startup:

技術分享圖片 技術分享圖片
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
#if DEBUG
            services.AddTransient<IMailService, LocalMailService>();
#else
            services.AddTransient<IMailService, CloudMailService>();
#endif
            var connectionString = @"Server=(localdb)\MSSQLLocalDB;Database=ProductDB;Trusted_Connection=True";
            services.AddDbContext<MyContext>(o => o.UseSqlServer(connectionString));
        }
技術分享圖片 技術分享圖片

使用AddDbContext的另一個overload的方法,它可以帶一個參數,在裏面調用UseSqlServer。

關於連接字符串,我是用的是LocalDb,實例名是MSSQLLocalDB。可以在命令行查詢本機LocalDb的實例,使用sqllocaldb info:

技術分享圖片

也可以通過VS的Sql Server Object Explorer查看:

技術分享圖片

連接字符串中的ProductDb是數據庫名;連接字符串的最後一部分表示這是一個受信任的連接,也就是說使用了集成驗證,在windows系統就是指windows憑證。

生成數據庫

因為我們使用的是Code First,所以如果還沒有數據庫的話,它應該會自動建立一個數據庫。

打開MyContext:

        public MyContext(DbContextOptions<MyContext> options)
            :base(options)
        {
            Database.EnsureCreated();
        }

這個Constructor在被依賴註入的時候會被調用,在裏面寫Database.EnsureCreated()。其中Database是DbContext的一個屬性對象。

EnsureCreated()的作用是,如果有數據庫存在,那麽什麽也不會發生。但是如果沒有,那麽就會創建一個數據庫。

但是現在就運行的話,並不會創建數據庫,因為沒有創建MyContext的實例,也就不會調用Constructor裏面的內容。

那我們就建立一個臨時的Controller,然後註入MyContext,此時就調用了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訪問Get這個Action後,我們可以從Debug窗口看見一些創建數據庫和表的Sql語句:

技術分享圖片

然後我們查看一下Sql Server Object Explorer:

技術分享圖片

我們可以看到數據庫建立好了,裏面還有dbo.Products這個表。

Database.EnsureCreated()確實可以保證創建數據庫,但是隨著代碼不斷被編寫,我們的Model不斷再改變,數據庫應該也隨之改變,而EnsureCreated()就不夠了,這就需要遷移(Migration)

不過遷移之前,我們先看看Product這個表的具體字段屬性:

技術分享圖片

Product的Id作為了主鍵,而Name這個字符串的長度是max,而Price沒有精度限制,這樣不行。我們需要對Model生成的表的字段進行限制!

解釋一下:Product這個entity中的Id,根據約定(Id或者ProductId)會被視為映射表的主鍵,並且該主鍵是自增的。

如果不使用Id或者ProductId這兩個名字作為主鍵的話,我們可以通過兩種方式把該屬性設置成為主鍵:Data Annotation註解和Fluet Api。我只在早期使用Data Annotation,後來一直使用Fluent Api,所以我這裏只介紹Fluent Api吧。

Fluet Api

針對Product這個entity,我們要把它映射成一個數據庫的表,所以針對每個屬性,可能需要設定一些限制,例如最大長度,是否必填等等。

針對Product,我們可以在MyContext裏面override OnModelCreating這個方法,然後這樣寫:

技術分享圖片 技術分享圖片
        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)");
        }
技術分享圖片 技術分享圖片

第一行表示設置Id為主鍵(其實我們並不需要這麽做)。然後Name屬性是必填的,而且最大長度是50。最後Price的精度是8,2,數據庫裏的類型為decimal。

fluent api有很多方法,具體請查看文檔:https://docs.microsoft.com/en-us/ef/core/modeling/

然後,我們就會發現一個嚴重的問題。如果項目裏面有很多entity,那麽所有的fluent api配置都需要寫在OnModelCreating這個方法裏,那太多了。

所以我們改進一下,使用IEntityTypeConfiguration<T>。建立一個叫ProductConfiguration的類:

技術分享圖片 技術分享圖片
    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裏寫的配置都移動到這裏,然後修改一些MyContext的OnModelCreating方法:

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.ApplyConfiguration(new ProductConfiguration());
        }

就是把ProductConfiguration裏面寫的配置加載進來,和之前的效果是一樣的。

但是項目中如果有很多entities的話也需要寫很多行代碼,更好的做法是寫一個方法,可以加載所有實現了IEntityTypeConfiguration<T>的實現類。在老版的asp.net web api 2.2裏面有一個方法可以從某個Assembly加載所有繼承於EntityTypeConfiguration的類,但是entity framework core並沒有提供類似的方法,以後我們自己寫一個吧,現在先這樣。

然後把數據庫刪掉,重新生成一下數據庫:

技術分享圖片

很好!

遷移 Migration

隨著代碼的更改,數據庫也會跟著變,所有EnsureCreated()不滿足要求。migration就允許我們把數據庫從一個版本升級到另一個版本。那我們就研究一下,首先把數據庫刪了,然後創建第一個遷移版本。

打開Package Manager Console,做個遷移 Add-Migration xxx:

技術分享圖片

Add-Migration 然後接著是一個你起的名字。

然後看一下VS的Solution Explorer 會發現生成了一個Migrations目錄:

技術分享圖片

裏面有兩個文件,一個是Snapshot,它是目前entity的狀態:

技術分享圖片 技術分享圖片
namespace CoreBackend.Api.Migrations
{
    [DbContext(typeof(MyContext))]
    partial class MyContextModelSnapshot : ModelSnapshot
    {
        protected override void BuildModel(ModelBuilder modelBuilder)
        {
#pragma warning disable 612, 618
            modelBuilder
                .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
        }
    }
}
技術分享圖片 技術分享圖片

這就是當前Product這個Model的狀態細節,包括我們通過Fluent Api為其添加的映射限制等。

另一個文件是xxxx_ProductInfoDbInitialMigration,下劃線後邊的部分就是剛才Add-Migration命令後邊跟著的名字參數。

技術分享圖片 技術分享圖片
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需要的代碼,用來遷移這個版本的數據庫。裏面有Up方法,就是從當前版本升級到下一個版本;還有Down方法,就是從下一個版本再退回到當前版本。

我們也可以不使用 Add-Migration命令,手寫上面這些代碼也行,我感覺還是算了吧。

另外還有一件事,那就是要保證遷移migration都有效的應用於數據庫了,那就是另一個命令 Update-Database

先等一下,我們也可以使用代碼來達到同樣的目的,打開MyContext:

        public MyContext(DbContextOptions<MyContext> options)
            : base(options)
        {
            Database.Migrate();
        }

把之前的EnsureCreated改成Database.Migrate(); 如果數據庫還沒刪除,那就最後刪除一次。

運行,並除法TestController:

技術分享圖片

然後會看見Product表,除此之外還有一個__EFMigrationHistory表,看看有啥:

技術分享圖片

這個表裏面保存了哪些遷移已經被應用於這個數據庫了。這也保證了Database.Migrate()或者Update-database命令不會執行重復的遷移migration。

我們再弄個遷移,為Product添加一個屬性:

技術分享圖片 技術分享圖片
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);
        }
    }
}
技術分享圖片 技術分享圖片

技術分享圖片

執行Add-Migration後,會在Migrations目錄生成了一個新的文件:

技術分享圖片 技術分享圖片
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");
        }
    }
}
技術分享圖片 技術分享圖片

然後這次執行Update-Database命令:

技術分享圖片

加上verbose參數就是顯示執行過程的明細而已。

不用運行,看看數據庫:

技術分享圖片

Description被添加上了,然後看看遷移表:

技術分享圖片

目前差不太多了,但還有一個安全隱患。它是:

如何安全的保存敏感的配置數據,例如:連接字符串

保存連接字符串,你可能會想到appSettings.json,但這不是一個好的想法。在本地開發的時候還沒有什麽問題(使用的是集成驗證),但是你要部署到服務器的時候,數據庫連接字符串可能包括用戶名和密碼(Sql Server的另一種驗證方式)。加入你不小心把appSettings.json或寫到C#裏面的連接字符串代碼提交到了Git或TFS,那麽這個用戶名和密碼包括服務器的名稱可能就被暴露了,這樣做很不安全。

我們可以這樣做,首先針對開發環境(development environment)把C#代碼中的連接字符串拿掉,把它放到appSettings.json裏面。然後針對正式生產環境(production environment),我們使用環境變量來保存這些敏感數據。

開發環境:

appSettings.json:

技術分享圖片 技術分享圖片
{
  "mailSettings": {
    "mailToAddress": "[email protected]",
    "mailFromAddress": "[email protected]"
  },
  "connectionStrings": {
    "productionInfoDbConnectionString": "Server=(localdb)\\MSSQLLocalDB;Database=ProductDB;Trusted_Connection=True"
  } 
}
技術分享圖片 技術分享圖片

Startup.cs:

技術分享圖片 技術分享圖片
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
#if DEBUG
            services.AddTransient<IMailService, LocalMailService>();
#else
            services.AddTransient<IMailService, CloudMailService>();
#endif
            var connectionString = Configuration["connectionStrings:productionInfoDbConnectionString"];
            services.AddDbContext<MyContext>(o => o.UseSqlServer(connectionString));
        }
技術分享圖片 技術分享圖片

然後你可以設斷點看看connectionString的值。目前項目的環境變量是Production,先改成Development:

技術分享圖片

然後斷點調試:

技術分享圖片

可以看到這兩個JsonConfigurationProvider就是appSettings的兩個文件的配置。

技術分享圖片

這個就是appSettings.json,裏面包含著我們剛才添加的連接字符串。

由於當前是Development環境,所以如果你查看另外一個JsonConfigurationProvider的話,會發現它裏面的值是空的(Data數是0).

所以沒有問題。

生產環境:

在項目的屬性--Debug裏面,我們看到了環境變量:

技術分享圖片

而這個環境變量,我們可以在程序中讀取出來,所以可以在這裏添加連接字符串:

技術分享圖片

註意它的key,要和appSettings.json裏面的整體結構一致;Value呢應該是給一個服務器的數據庫的字符串,這裏就隨便弄個假的吧。別忘了把Development改成Production。

然後調試一下:

技術分享圖片

沒錯。如果你仔細調試一下看看的話:就會從EnvironmentVariablesConfigurationProvider的第64個找到我們剛才寫到連接字符串:

技術分享圖片

但是還沒完。

打開項目的launchSettings.json:

技術分享圖片

你會發現:

技術分享圖片 技術分享圖片
{
  "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/"
    }
  }
}
技術分享圖片 技術分享圖片

連接字符串在這裏。這個文件一般都會源碼控制給忽略,也不會在發布的時候發布到服務器。那麽服務器怎麽讀取到這個連接字符串呢???

看上面調試EnvironmentVariablesConfigurationProvider的值,會發現裏面有幾十個變量,這些基本都不是來自launchSettings.json,它們是從系統層面上定義的!!

這回我們這樣操作:

把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/"
    }
  }
}
技術分享圖片 技術分享圖片

然後這裏自然也就沒有了:

技術分享圖片

現在任何json文件都沒有敏感信息了。

現在我們要把連接字符串添加到系統變量中。

在win10搜索框輸入 envi:

技術分享圖片

然後點擊上面的結果:

技術分享圖片

點擊環境變量:

技術分享圖片

這裏面上邊是用戶的變量,下面是系統的變量,這就是剛才EnvironmentVariableConfigurationProvider裏面調試出來的那一堆環境變量。

而這個地方就是在你應該服務器上添加連接字符串的地方。再看一下調試:

技術分享圖片

Environment的Provider在第4個位置,appSettings.production.json的在第3個位置。也就是說如果appSettings.Product.json和系統環境變量都有一樣Key的連接字符串,那麽程序會選擇系統環境變量的值,因為它是後邊的配置會覆蓋前邊的配置。

在系統環境變量中添加:

技術分享圖片

然後調試運行(需要重啟VS,以便新添加的系統環境變量生效):

技術分享圖片

嗯,沒問題!

種子數據 Seed Data

目前EF Core還沒有內置的方法來做種子數據。那麽自己寫:

建立一個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();
        }
    }
}
技術分享圖片 技術分享圖片

這是個Extension method,如果數據庫沒有數據,那就弄點種子數據,AddRange可以添加批量數據到Context(被Context追蹤),但是到這還沒有插入到數據庫。使用SaveChanges會把數據保存到數據庫。

然後再Startup的Configure方法中調用這個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,然後調用這個extension method。

然後把系統環境變量中的連接字符串刪了把,並且把項目屬性Debug中改成Development,這時候需要重啟VS,因為一般環境變量是在軟件啟動的時候附加到其內存的,軟件沒關的情況下如果把系統環境變量給刪了,在軟件的內存中應該還是能找到該環境變量,所以軟件得重啟才能獲取最新的環境變量們。重啟VS,並運行:

技術分享圖片

種子數據進去了!

先寫到這吧!!!!

轉自:http://www.cnblogs.com/cgzl/p/7661805.html

【轉載】從頭編寫 asp.net core 2.0 web api 基礎框架 (4) EF配置