分類:Entity framework

Entity Framework – Code First 簡易實作

前言

Entity framework 提供一個以程式為基礎來建立資料庫的方式,稱為 Code First。以下簡易示範作法。

一、建立資料表模型(Model)

public class Student
{
    public int ID { get; set; }
    public string Name { get; set; }
}

.

二、建立繼承 DbContext 的類別

using System.Data.Entity;
public class EFDBContext:DbContext
{
   public EFDBContext():base("DefaultConnection")
   {
       Database.SetInitializer<EFDBContext>(new CreateDatabaseIfNotExists<EFDBContext>());
   }
   public DbSet<Student> Students { get; set; }
}

.

三、Web.config 設定資料連線

我們使用預設的 LocalDB

<connectionStrings>
    <add name="DefaultConnection" connectionString="Data Source=(LocalDb)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\aspnet-CodefirstSample-20170118110538.mdf;Initial Catalog=aspnet-CodefirstSample-20170118110538;Integrated Security=True"
      providerName="System.Data.SqlClient" />
  </connectionStrings>

.

四、於 VS Package Manager Console 輸入指令

開啟 Package Manager Console

Image 1

輸入指令:
(一) Enable-Migrations -ContextTypeName 專案名稱.Models.繼承 DbContext 的類別名稱
例如,我們的專案名稱是:CodefirstSample。繼承 DbContext 的類別名稱是:EFDBContext。如下所示:

PM> Enable-Migrations -ContextTypeName CodefirstSample.Models.EFDBContext

執行指令之後會在專案裡建立一個「Migrations」目錄以及「Configuration.cs」檔案

(二) add-migration 移轉名稱
這指令會在專案的「Migrations」目錄內建立一個結構檔,VS 依據這個檔案來異動資料庫。

PM> add-migration v1

指令執行後,主控台會出現以下訊息

正在為移轉 'v1' 建立結構。
這個移轉檔案的設計工具程式碼包括目前 Code First 模型的快照。這個快照是用於為下一個移轉建立結構時計算模型的變更。如果您對模型進行其他變更,而且想將變更包含在這個移轉中,只要再次執行 'Add-Migration v1' 就能重新建立結構。

下一次資料庫的異動會檢查比對這個檔案來做資料庫異動。

(三) update-database

PM> update-database

指令執行後,主控台會出現以下訊息

正在套用明確移轉: [201701181514254_v1]。
正在套用明確移轉: 201701181514254_v1。
正在執行 Seed 方法。

指令執行成功後,會建立資料庫並且建立一個 __MigrationHistory 資料表,紀錄每一次的異動,提供下一次異動的比對。

四、資料庫資料表的異動

假設我們在 Student 這個模型加上一個欄位:Add。編譯程式,執行指令 add-migraion v2。

模型加上一個欄位:Add

public class Student
    {
        public int ID { get; set; }
        public string Name { get; set; }
        public string Add { get; set; }
    }

執行指令 add-migraion v2

public partial class v2 : DbMigration
{
   public override void Up()
   {
       AddColumn("dbo.Students", "Add", c => c.String());
   }

   public override void Down()
   {
       DropColumn("dbo.Students", "Add");
   }
}

最後執行指令更新資料庫:update-database

在資料庫的 __MigrationHistory 資料表可以看到二次異動紀錄。

zzzz

測試

再於 Student 模型加入一個欄位 class

public class Student
    {
        public int ID { get; set; }
        public string Name { get; set; }
        public string Add { get; set; }

        public string Class { get; set; }
    }

然後於資料庫移除 __MigrationHistory 資料表中的第二次異動紀錄。也就是只剩下一個異動紀錄,紀錄的是創建資料表。

這時執行指令 add-migration v3 ,觀察產生的結構檔如下:

public partial class v3 : DbMigration
    {
        public override void Up()
        {
            AddColumn("dbo.Students", "Add", c => c.String());
            AddColumn("dbo.Students", "Class", c => c.String());
        }

        public override void Down()
        {
            DropColumn("dbo.Students", "Class");
            DropColumn("dbo.Students", "Add");
        }
    }

原本的第二次異動紀錄會被產生。這意味著,每次執行指令 add-migration,VS會撈取 __MigrationHistory 資料表,取出最後一次異動的資料,和程式的模型比較,依據二者差異產生結構檔。

Up / Down method

Up 這個方法是讓 EF 執行目前你要更新資料庫的內容。 Down 方法則是當你想恢復某個版本, EF 會執行該版本之後的 Down 方法,來恢復。
http://stackoverflow.com/questions/24560469/asp-net-database-migration-whats-the-down-method-for

問題

多人開發下,經常發生一個狀況:某甲異動了資料庫,但某乙的程式的資料模型尚未更正。這時某乙編譯自己的程式後,讀取資料庫時會報錯,指出程式的資料模型與資料庫不一致。

這意味著,Entity framework 在存取資料庫之前會先檢查程式與資料庫的資料模型是否一致。問題是,Entity framework 如何快速檢查?

它會檢查程式的資料模型與資料庫的 Migration history table。如果你刪除掉 history table 某筆紀錄,專案內 Migration 資料夾對應的結構檔也移除,即使資料庫與程式的資料模型一致,當讀取資料庫也會報錯。@_@

必須再執行一次 Migration 解決這問題。但在 Up()內的重複產生的語法都要清除。

如果不想要檢查程式資料模型與資料庫是否一致,請刪除資料庫的 Migration history table

感想

在現實開發世界,Code first 的存在很方便,而 database first 的存在也是必要。為了整合不同資料來源的資料,兩者經常同時使用在同一專案。

不同於 database first, code first 以程式設計師的專案為主,當多人共同開發時,要特別謹慎,溝通必須清楚。

個人來說,我比較偏好 database first。

Entity framework with TransactionScope

我們透過 EF 的 SaveChanges() 來增刪修資料庫資料, SaveChanges() 預設使用 transaction(交易) 包資料。

EF 也另外提供 DbContextTransaction 類別讓我們使用。範例如下:

DbEntities dbEntity = new DbEntities();
using (var transaction = dbEntity.Database.BeginTransaction())
{
    try
    {
        dbEntity.SaveChanges();
        transaction.Commit();
    }
    catch(Exception ex)
    {
       transaction.Rollback();
    }             
}

當有兩個以上的 DbContext ,比如需要把資料寫到兩個以上的不同資料庫,如何確保這些不同資料庫交易的一致性,EF 提供 TransactionScope 類別解決這問題。

TransactionScope

TransactionScope 內的不同資料庫連線,只要一個沒成功,就會 Rollback。

using (DBEntities db = new DBEntities())
{
    using (TransactionScope ts = new TransactionScope())
    {
        // do something A
        db.SaveChanges();
        // do something B
        db.SaveChanges();
        ts.Complete();
    }
}

參考

http://blog.darkthread.net/post-2012-12-19-transactionscope-suppress.aspx

Entity Framework 多種刪除

Entity Framework 有三種刪除資料的方法,如下。

第一個是常見的寫法:

Remove()

TestModel object = context.TestModel.Single(x => x.Name == "John");
context.TestModel.Remove(object);
context.SaveChanges();

.
第二個方式是改變 EntityState 狀態:

EntityState.Deleted

TestModel object = context.TestModel.Single(x => x.Name == "John");
context.Entry(object).State = EntityState.Deleted;
context.SaveChanges();

.
或者使用套件 Entity Framework Extended Library。就可以使用以下的寫法來刪除資料。

Delete()

context.TestModel.Delete(x => x.Id == ID);

或是

context.TestModel.Where(x => x.Id == ID).Delete();

[Entity framework] – No-Tracking Queries

當我們透過 Entity framework (以下簡稱 EF ) 向資料庫查詢並撈出某個資料表的資料, EF 預設將這個資料表的實體( entity )與資料存放在 DbContext 的快取,以便追蹤。

當再次向資料庫查詢相同的資料表,EF 會去查快取,看看有沒有這個資料表實體的資料,如果有,就從這個被追蹤的實體回傳資料。

using(dbContext db = new dbContext)
{
    // 第一次是向資料庫取資料存放在快取
    var data1 = db.Table.FirstOrDefault(a=>a.name == Name);
    // 第二次是向快取取資料
    var data2 = db.Table.FirstOrDefault(a=>a.name == Name);
} 

AsNoTracking( )

如果因特殊情況,必須向資料庫對相同資料表做兩次查詢,不想要快取的資料, EF 提供 AsNoTracking 擴充方法來執行不追蹤式查詢。如下所示:

using(dbContext db = new dbContext)
{
    // 第一次是向資料庫取資料存放在快取
    var data1 = db.Table.FirstOrDefault(a=>a.name == Name);
    // 第二次再向資料庫重新取資料
    var data2 = db.Table.AsNoTracking().FirstOrDefault(a=>a.name == Name);
} 

透過 AsNoTracking 取出的資料,該實體就不是 Detached,無法使用 context.SaveChanges( ) 將異動資料回存資料庫。

Find( ) 與 AsNoTracking( ) 不可以同時使用

以下示範是不能實現的

using(dbContext db = new dbContext)
{
   // AsNoTracking() 沒有 find() 方法
   var data1 = db.Table.AsNoTracking().Find(id); //錯的
} 

參考:
http://ef.readthedocs.io/en/latest/querying/tracking.html
https://msdn.microsoft.com/en-us/data/jj556203.aspx
這篇很棒
http://vito-note.blogspot.tw/2016/01/entity-framework-41.html

EF 6 連 MariaDB的錯誤:No MigrationSqlGenerator found for provider ‘MySql.Data.MySqlClient’

透過 EF 6 使用 MariaDB ,Code first 專案 add migration 出現以下錯誤:

add-migration "testMigration"
No MigrationSqlGenerator found for provider 'MySql.Data.MySqlClient'. Use the SetSqlGenerator method in the target migrations configuration class to register additional SQL generators.

解決方式:

一是先檢查是否安裝套件,請看 MySQl 官網這篇 EF 6 Support
二修正Configurations.cs

當執行 Enable-Migrations -EnableAutomaticMigrations 指令,VS會產生 Migrations 資料夾。
在 Migrations 資料夾內的 Configurations.cs 加入以下:

public Configuration()
        {          
            AutomaticMigrationsEnabled = false;
            SetSqlGenerator("MySql.Data.MySqlClient", new MySql.Data.Entity.MySqlMigrationSqlGenerator());
        }

之後重新執行 add-migration 指令。就可成功。