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
33
using 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() 註冊 DbContext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public 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 種方式來建資料庫:

  1. DbContext.Database.Migrate()
    執行標準的 Migration 程序,同命令列的效果。

  2. DbContext.Database.EnsureCreated()
    不做 Migration,單純建資料庫。

取得 DbContext 的方法

Startup.cs 的 Configure() 是做初始化的好入口。

網路及書本上常見有三種利用 DI 取得 DbContext 再進行操作的方法:

第一種 => 不行

1
2
3
4
Configure(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
11
Configure(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
23
Configure(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 的資料表。

以上,開工吧!

參考資料及圖片來源

  1. 在 ASP.NET Core 上使用 EF Core 搭配新資料庫的使用者入門
  2. Essential Docker for ASP.NET Core MVC