在 ASP.NET Core 中初始化 Entity Framework Core 資料庫
Entity Framework Core 沒有了 Database.SetInitializer() 相關 API 及設定檔的功能,那如何不透過命令的方式,像是 dotnet ef migrations add Initial 或 Add-Migration Initial,在程式中執行 Migration 來自動建立資料庫及資料表呢?
官方文件中,是用命令的方式建立資料庫!少了自動建立的這段,只好自己找出來。
背景資訊
官方文件
在 ASP.NET Core 上使用 EF Core 搭配新資料庫的使用者入門
範例是比照官方文件用 Visual Studio 來建專案,也可以直接用 dotnet new 命令列工具來做。
範例的執行環境
- Visual Studio 2017 15.8.7
- .NET Core 2.1 SDK (2.1.403)
建立新專案
- 開啟 Visual Studio 2017
- [檔案] > [新增] > [專案]
- 從左側功能表選取 [已安裝] > [Visual C#] > [.NET Core]。
- 選取 [ASP.NET Core Web 應用程式]。
- 輸入專案名稱,然後按一下 [確定]。
- 在 [新增 ASP.NET Core Web 應用程式] 對話方塊中:
- 確認下拉式清單中已選取 [.NET Core] 和 [ASP.NET Core 2.1] 選項
- 選取 [Web 應用程式 (模型-檢視-控制器)] 專案範本
- 確認 [驗證] 已設為 [無驗證]
- 按一下 [確定]
安裝 Entity Framework Core
如果使用 SQL Server,Microsoft.AspnetCore.App 中繼套件已包含 SQL Server 提供者套件,就不用再自行安裝。
建立資料物件
在 Models 資料夾中新增類別 Model.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
33using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
namespace XXX.Models
{
public class BloggingContext : DbContext
{
public BloggingContext(DbContextOptions<BloggingContext> options)
: base(options)
{ }
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }
}
public class Blog
{
public int BlogId { get; set; }
public string Url { get; set; }
public ICollection<Post> Posts { get; set; }
}
public class Post
{
public int PostId { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public int BlogId { get; set; }
public Blog Blog { get; set; }
}
}
- namespace 依專案。
- 正式場合,各個類別應該分置在各自的檔案。
註冊 DbContext
開啟 Startup.cs,在 ConfigureServices() 中用 AddDbContext() 註冊 DbContext1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
// 加入以下內容,註冊 DbContext
var connection = @"Server=.\SQLEXPRESS;Database=CoreNewDb;Trusted_Connection=True";
services.AddDbContext<BloggingContext>(options =>
options.UseSqlServer(connection)
);
}
- connection string 可置於設定檔中動態取得。
建立資料庫
前面都是官網範例,這裡才是本篇的重點!
如何正確取得 DbContext 並執行建資料庫的 API。
資料庫的 API
理論上,可以利用以下 2 種方式來建資料庫:
DbContext.Database.Migrate()
執行標準的 Migration 程序,同命令列的效果。DbContext.Database.EnsureCreated()
不做 Migration,單純建資料庫。
取得 DbContext 的方法
Startup.cs 的 Configure() 是做初始化的好入口。
網路及書本上常見有三種利用 DI 取得 DbContext 再進行操作的方法:
第一種 => 不行1
2
3
4Configure(IApplicationBuilder app...)
{
var context = app.ApplicationServices.GetRequiredService<BloggingContext>();
}
- 錯誤:System.InvalidOperationException: Cannot resolve scoped service ‘MigrationDemo.Models.BloggingContext’ from root provider.
第二種 => EnsureCreated() 可行1
2
3
4
5
6
7
8
9
10
11Configure(IApplicationBuilder app...)
{
using (var serviceScope = app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope())
{
var context = serviceScope.ServiceProvider.GetRequiredService<BloggingContext>();
context.Database.Migrate();
// 或
context.Database.EnsureCreated();
}
}
第三種 => EnsureCreated() 可行
最簡便 - 直接透過 DI, 不用管系統如何取得1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23Configure(IApplicationBuilder app..., BloggingContext context)
{
context.Database.Migrate();
// 或
context.Database.EnsureCreated();
// 沒資料就初始化。可以拉出到如 SeedData 的類別中專門處理
if(!context.Blogs.Any())
{
context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/adonet" });
var count = context.SaveChanges();
Console.WriteLine("{0} records saved to database", count);
Console.WriteLine();
Console.WriteLine("All blogs in database:");
foreach (var blog in context.Blogs)
{
Console.WriteLine(" - {0}", blog.Url);
}
}
}
結果
以下是現階段測試的結果。
- EnsureCreated() 可以正確建立資料庫及表格,但不能跟 migration 後續的變更管理合作。
- Migrate() 只會建立資料庫及 migration table,並不會建立 Model 的 table。訊息顯示沒有需要 migration 的資料。猜測是它只試著執行現有的 migration 需求,並未去做收集 Model 未建 table 的 initial 動作。
不用命令的方式,尚未找到只用程式可以 migration 自動建 table 的方法。
但,如果可以在開發環境中先執行過初始化命令,如 dotnet ef migrations add Initial 或 Add-Migration Initial,則產生的 Migrations 目錄及資料,就可以在執行 DbContext.Migrate() 時有事可做,如期望的去建 Model 的資料表。