ASP.NET MVC 使用 Entity Framework Code First

2022-12-10

筆記如何使用 Entity Framework 在 ASP.NET MVC 專案下使用 Code First 進行開發。

logo

說明

安裝 Entity Framework

Install-Package EntityFramework

建立 POCO 類別

namespace EF_CodeFirst.Models
{
    public class Person
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public DateTime? BirthDate { get; set; }
    }
}

建立 DBContext

SqlDbContext.cs

using System.Data.Entity;
using EF_CodeFirst.Models;

namespace EF_CodeFirst.DAL
{
    public class SqlDbContext : DbContext
    {
        public SqlDbContext() : base()
        {
        }
        public DbSet<Person> People { get; set; }
    }
}

完成上述的設定,Entity Framework 就已經能夠被啟動了,而這個時候資料庫會被建立在那裡?

預設上當 DbContext 的建構子繼承沒有提供任何參數時,預設就會以 namespace 命名資料庫,並且建立在 (LocalDB)\MSSQLLocalDB 當中。

而如果想要指定資料庫名稱,可以藉由提供建構子參數來達成:

public class SqlDbContext : DbContext
{
    public SqlDbContext() : base("DatabaseName")
    {
    }
    public DbSet<Person> People { get; set; }
}

此外也可以指定為 Web.config 當中的連線字串名稱或者是直接提供連線字串的方式,讓 Entity Framework 直接在目標資料庫伺服器進行資料庫的建立:

Web.config

<connectionStrings>
  <add name="CodeFirstDB"
        connectionString="Data Source=.;Initial Catalog=DabaseName;Integrated Security=True;
        Connect Timeout=30;Encrypt=False;TrustServerCertificate=True;
        ApplicationIntent=ReadWrite;MultiSubnetFailover=False"
        providerName="System.Data.SqlClient" />
</connectionStrings>

SqlDbContext.cs

public class SqlDbContext : DbContext
{
    public SqlDbContext() : base("name=CodeFirstDB")
    {
    }
    public DbSet<Person> People { get; set; }
}

DatabaseCreate & Database Initialization Strategies

HomeController.cs

public class HomeController : Controller
{
    private SqlDbContext context = new SqlDbContext();
    public ActionResult Index()
    {
        return View(context.People.ToList());
    }

    public ActionResult AddPerson()
    {
        context.People.Add(new Person { Name = "Alice" });
        context.SaveChanges();
        return RedirectToAction("Index");
    }
}

在 IISExpress 啟動後,資料庫就會自行建立,配合 Controller 可以測試資料的呈現。

而如果 POCO 類別發生改變,預設上資料庫只會在不存在時重建,因此 POCO 類別的改變不會讓資料庫的綱要重新建立。

這時可以搭配 Database.SetInitializer 來指定資料庫初始化的策略。

CreateDatabaseIfNotExists
預設的行為,當資料庫不存在時建立,EF 1.0 加入。
DropCreateDatabaseAlways
每次重新啟動 IISExpress 時,自動重新建立資料庫,適合使用在模擬與測試環境,EF 1.0 加入。
DropCreateDatabaseIfModelChanges
當模型 (POCO 類別) 發生改變時,自動重新建立資料庫,適合在設計類別的開發情境,EF 4.1 加入

上述的 Initialization Strategies 最大的限制就是可能誤對正式環境的資料造成影響,因此在 EF 4.3 加入的 Migrations成為了資料庫異動的最優先適用技術,能夠使用類似於 Git 的方式達到對資料庫的異動版本控制化,並且更具資料庫異動上的彈性。

SqlDbContext.cs

public class SqlDbContext : DbContext
{
    public SqlDbContext() : base("name=CodeFirstDB")
    {
        Database.SetInitializer<SqlDbContext>(new DropCreateDatabaseAlways<SqlDbContext>());
        Database.SetInitializer<SqlDbContext>(new CreateDatabaseIfNotExists<SqlDbContext>());
        Database.SetInitializer<SqlDbContext>(new DropCreateDatabaseIfModelChanges<SqlDbContext>());
    }
    public DbSet<Person> People { get; set; }
}

此外也能基於上述的資料庫初始化策略,客製化資料庫初始化,結合自動加入測試資料的應用方式:

SqlDbContext.cs

public class SqlDbContext : DbContext
{
    public SqlDbContext() : base("name=CodeFirstDB")
    {
            Database.SetInitializer<SqlDbContext>(new Initializer());

    }
    public DbSet<Person> People { get; set; }
}

Initializer.cs

public class Initializer : DropCreateDatabaseIfModelChanges<SqlDbContext>
{
    protected override void Seed(SqlDbContext context)
    {
        base.Seed(context);

        context.People.Add(
          new Person { Name = "Alice", BirthDate = new System.DateTime(1995, 2, 1) });
        context.People.Add(
          new Person { Name = "Bob", BirthDate = new System.DateTime(1995, 12, 5) });

        context.SaveChanges();

        Console.WriteLine("Database Initialized Done.");
    }
}

上述填入初始資料的動作稱為 Database seeding

Migrations

而雖然有了資料庫初始化策略,但隨著開發過程的深入,使用者可能在資料庫已經留下許多的測試資料,不適合再貿然重置整個資料庫,這個時候就可以加入 Migrations 機制,為資料庫的異動建立版本控制,兼顧開發過程中,資料庫的升版與退版功能。

而在 Migrations 的使用,可以分為 Auto Migrations 以及 Code-Based Migrations。

Auto Migrations

使用 Auto Migrations 後,當 POCO 以及 Model 發生變化時,會自動進行資料庫綱要的變更,同時會在資料庫的 __MigrationHistory 資料表加上紀錄。

注意要使用的是 Nuget 套件管理員中的「套件管理主控台」,而非使用「開發人員 PowerShell」,否則會發生「 無法辨識 'enable-migration' 詞彙是否為 Cmdlet、函數、指令檔或可執行程式的名稱。請檢查名稱拼字是否正確,如果包含路徑的話,請確認路徑是否正確,然後再試一次。」的問題 😑

要使用 Auto Migrations 需要在 Package Manager Console 執行 Enable-Migrations

Enable-Migrations -EnableAutomaticMigration:$true

執行後,會自動加入 Migrations 資料夾以及 COnfiguration.cs

Migrations/Configuration.cs

internal sealed class Configuration : 
  DbMigrationsConfiguration<EF_CodeFirst.DAL.SqlDbContext>
{
    public Configuration()
    {
        AutomaticMigrationsEnabled = true;
        ContextKey = "EF_CodeFirst.DAL.SqlDbContext";
        AutomaticMigrationDataLossAllowed = true;
    }

    protected override void Seed(EF_CodeFirst.DAL.SqlDbContext context)
    {
        ...
    }
}

預設不會加入 AutomaticMigrationDataLossAllowed,啟用才能夠允許當 POCO 變更如果會造成資料移除的情境。

需要注意的是當使用 Auto Migratinos 後,雖然只有在POCO 以及 Model 發生變化時,才會在 __MigrationHistory 加入異動紀錄,但 Seed 的部分則是每次應用程式啟用都會被執行。因此如果有 Seeding Database 的需求,需要在 Seed 的部分檢查資料是否已經存在,而非直接重複性的加入資料,詳細可以參考 Stackoverflow 的討論 Confusion over EF Auto Migrations and seeding - seeding every program start

Code-Based Migration

enable-migrations
add-migrations -name Description
update-database
Update-Database
Update-Database -Script -SourceMigration:0

參考資料

Entity Framework Tutorial

[料理佳餚] Entity Framework Code First 不算太難用 | 軟體主廚的程式料理廚房

Creating an Entity Framework Data Model for an ASP.NET MVC Application