2012年4月4日

Entity Framework - 使用Code First的Enabling Migrations

上一篇提到Code First,也提到了一個問題,這個問題就是,如果我們在第一次使用Code First並建立好資料庫,但如果第二次需要增加個資料欄位,該怎麼辦?如果是以前,只能很無奈地把db刪除掉,然後在執行一次,但自從Entity Framework 4.3版本後,多了一個新功能,那就叫做Enabling Migrations ( 啟動遷移 ),這個功能的目的,就是可以讓不用刪除db的情況下,加入新的欄位喔!

在開始前,我要先證明一下,如果沒有使用Enabling Migrations的情況下,會發生甚麼事情,我們假設已經利用Code First建立了一個Table ClientBasic;而現在要在ClientBasic多加上一個EName,所以如果是理想的狀況下,因該會在db裡面多加上EName的欄位!(這裡的ClientBasicId上面有個Key,表示要使用這個屬性為PK)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MigrationsCodeDemo
{
    public class ClientBasic
    {
        [Key]
        public int ClientBasicId { get; set; }
        public string Name { get; set; }
        public string EName { get; set; }
    }
}

但現實總是殘酷的,如果這樣做,就會拋出InvalidOperationException的例外。

”The model backing the 'XXXContext' context has changed since the database was created. Consider using Code First Migrations to update the database (http://go.microsoft.com/fwlink/?LinkId=238269).”

image

不過也因為會發生這樣的問題,才會有4.3版本的EF,而且也才會有這篇文章!這時候要怎樣做呢,首先,要先啟動"Enable-Migrations",正常情況下,應該是不會開啟Package Manager Console視窗,所以我們可以先透過下圖的位置開啟此視窗。

image

接下來,在Package Manager Console視窗下的PM後面打上Enable-Migrations,如果懶得全部打完,也可以打Ena然後按下Tab,會跳出來給你選。( 向小弟一樣的懶人專用!! )

image

如果出現紅字。

Detected database created with a database initializer. Scaffolded migration '201204031530186_InitialCreate' corresponding to current database schema. To use an automatic migration instead, delete the Migrations folder and re-run Enable-Migrations specifying the -EnableAutomaticMigrations parameter.

那表示,目前已經有Migrations目錄,而如果要啟動自動遷移,必須把剛剛建立好的Migrations目錄給砍掉,並且下Enable-Migrations –EnableAutomaticMigrations,但我們這篇是講手動的部分,所以也不需要啟動自動Migrations,所以不用管這串紅字,自動的部分後面會再提到。

當執行完命令後,就會產生一個Migrations文件夾,這個新的文件夾中包含兩個文件。

Configuration Class。這個Class的作用在於,允許你設定要遷移那些Context。這個範例中,我們會使用預設的配置,因為這個範例只有一個ClientBasicContext,所以預設產生的Configuration Class就已經夠用了。

另外一個檔案室InitalCreate,這個檔案會被建立出來,是因為我們在啟動Enabling Migrations之前,就已經先建立資料庫了,所以這個檔案會被建立出來;這個檔案的用處是,他會將目前所建立好的資料庫欄位給記錄下來,在這邊而言,就是紀錄了ClientBasicId和Name。

我來看一下圖,會建立這些東西。

image

建立好出最基礎的樣板後,我們要開始進行增加EName的動作了,我們先回顧一下程式碼,我們這次碰到的問題,就是新增加了EName,但產生錯誤。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MigrationsCodeDemo
{
    public class ClientBasic
    {
        [Key]
        public int ClientBasicId { get; set; }
        public string Name { get; set; }
        public string EName { get; set; }
    }
}

我們這邊要在Package Manager Console多下一個指令,稱為Add-Migration,如下圖,我們在PM前面打上Add-Migration AddEName。

image

然後,這次又出現長長的一串英文。

The Designer Code for this migration file includes a snapshot of your current Code First model. This snapshot is used to calculate the changes to your model when you scaffold the next migration. If you make additional changes to your model that you want to include in this migration, then you can re-scaffold it by running 'Add-Migration 201204031603044_AddEName' again.

簡單地說,就是這個AddEName的檔案內含了目前model的結構,如果建立起AddEName後,又改變了model,可以再使用'Add-Migration 201204031603044_AddEName' 來重現建立。

然後我們看一下新增加的程式碼AddEName,我們可以看到,他自動地幫我們產生了以下的結構,我們可以看到up那邊,如果up,就會將EName加入到欄位。

namespace MigrationsCodeDemo.Migrations
{
    using System.Data.Entity.Migrations;
    
    public partial class AddEName : DbMigration
    {
        public override void Up()
        {
            AddColumn("ClientBasics", "EName", c => c.String());
        }
        
        public override void Down()
        {
            DropColumn("ClientBasics", "EName");
        }
    }
}

如果我們再看一下,最初建立的InitalCreate檔案,這邊則是使用CreateTable,因為InitalCreate是最原始的資料庫快照。

namespace MigrationsCodeDemo.Migrations
{
    using System.Data.Entity.Migrations;
    
    public partial class InitialCreate : DbMigration
    {
        public override void Up()
        {
            CreateTable(
                "ClientBasics",
                c => new
                    {
                        ClientBasicId = c.Int(nullable: false, identity: true),
                        Name = c.String(),
                    })
                .PrimaryKey(t => t.ClientBasicId);
            
        }
        
        public override void Down()
        {
            DropTable("ClientBasics");
        }
    }
}

其實到這邊,我們就可以完全的知道工作的原理了,我們有一個最原始的InitalCreate,而如果今天新增加一些欄位,就會有對應的Migration檔案來對應,所以當要增加欄位的時候,透過這種方法,就能解決原本Entity Framework不能新增欄位的問題了。

最後我們要使用最後一個指令Update-Database!利用這個指令就可以順利的將EName欄位新增上去。

image

我們可以看到,多增加了一個EName的欄位!

image

而且,原本的資料,完全不會被砍掉。

image

以上是手動遷移,但其實Entity Framework也支援自動遷移,只需要在Package Manager Console下一個指令,Enable-Migrations –EnableAutomaticMigrations,就可以了。

或是去Configuration.cs檔案裡面,把AutomaticMigrationsEnabled = fales的地方改成true也是可以,如下。

public Configuration()
{
    AutomaticMigrationsEnabled = true;
}

這樣子,就可以直接下update-database來更新資料庫,而不用建立變更紀錄。

後記

其實到最後,原本的資料不會被砍掉,就可以發現到,它的原理並非是把整個Table Drop掉,而是用加上去的方式,而也利用這種方法來解決原本EF不能新增欄位的問題,也大大的加強了彈性,雖然整篇文章看起來很多,但實作一次後,一點也不會覺得困難!

參考資料

2 則留言:

  1. 您好,我目前遇到這方面問題想請教您,如果是網管人員是用sql management 修改資料庫表單欄位或新增表單時,與程式Model同步問題因該怎麼處理,謝謝

    回覆刪除
  2. 印象中code first似乎不能sql management 修改資料庫表單欄位或新增表單這種方式,除非你用的是model first or database first

    回覆刪除