話說之前那一篇的 Entity Framework 觀念其實還是有很多地方是不大清楚的,像是使用 Code First with existing database 時什麼情況會異動到資料庫?什麼情況下需要做 Migrations?什麼情況下會直接吐個 Exception 給你而不是自動幫你多個 Table 或 Column?,所以今天就趁著一點時間來寫一下從接觸至今的處理經驗了…(說的好像經驗老到其實還是個新手)

前置工作

這次的範例中我們會需要一個無辜的資料庫讓我們(恣意蹂躪)測試。 就讓我們把這個偉大的資料庫命名為…Demo!

DB_Schema

唔…其實 Products 先不用建起來,我們可以來做個實驗看看什麼時候會觸發 Migrations…好~建立起這個資料庫之後我們就可以馬上來寫 Code 玩玩看啦~!

這裡我們就使用 Console Application 就好。我們建立一個名為 EFMigrationDemo 的專案,並在根目錄處新增一個 Model 資料夾裡面放置我們最重要的類別。 在寫 Code 之前我們要先把 EntityFramework 裝起來~就直接打開 Package Manager Console 吧!請在 Console 中直接輸入:

Install-Package EntityFramework

若不放心是否為最新就接著下:

Update-Package EntityFramework

這樣就可以保證你所指定的套件是最新的! 前置工作這樣就算是告一段落了~

寫 Code 囉

我們新增一個類別檔案於 Model 資料夾中

 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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
using System.Data.Entity;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace EFMigrationDemo.Model
{
    public class Customers // 類別名稱與資料庫中的表格名稱一樣
    {
        // 標記該屬性(在資料庫中為欄位)為 Promary key
        //(若屬性結尾為 ID 依照 Conversion naming 規則 EF 會自己判斷出來)
        [Key]
        public int ID { get; set; }

        // 標記該欄位是不能為Null的
        [Required]
        public string Name { get; set; }

        [Required]
        public string Address { get; set; }

        [Required]
        public string PhoneNumber { get; set; }

        [Required]
        public string Email { get; set; }

        // 標記這個屬性對應到資料庫中的欄位名稱為 NickName 而非 TheNickName
        // 而且該欄位的內容長度不超過50
        [Column("NickName")]
        [MaxLength(50, ErrorMessage = "Your Nick Name too long")]
        public string TheNickName { get; set; }

        // 標記這個屬性並不會對應到資料庫中的任何欄位
        [NotMapped]
        public string NameplusNickName { get { return Name + NickName; } }

        // 這是個類別所以還是可以有方法,而且不會被認為是欄位或是預儲程序
        public void PrintName() { Console.WriteLine("My name is {0}", this.Name); }
    }

    // 繼承 DbContext 的類別可以當成是資料庫來進行操作,預設會去 Config 檔中抓取 connectionString 的 section 裡與類別名稱相同的項目,像是:
    // <add name="Demo" connectionString="" providerName="">
    // 而類別名稱其實也可以不用跟資料庫名稱一樣
    public class Demo : DbContext
    {
        // 被 DbSet 包起來的類別會被判定為是一個實體(也就是一個表格)
        // 所以屬性名稱並不會影響到表格名稱
        public DbSet<Customers> Customer { get; set; }
    }
}

這樣我們的 Model 類別就算準備好了,接下來就是準備呼叫端了~

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
using EFMigrationDemo.Model;

static void Main(string[] args)
{
    Program p = new Program();
    p.Run();
    Console.WriteLine("Process Run has been finished");
    Console.Read();
}

void Run()
{
    Console.WriteLine("Code First begining...");
    using (Demo db = new Demo())
    {
        var result = (from p in db.Customers
                        select p).FirstOrDefault();
        if (result != null)
            Console.WriteLine("Customer Name is {0}", result.NameplusNickName);
    }
}

執行之後就是會印出來資料庫內的第一筆資料哩~ 以上就是很順利的 Code First 運作情況,當然事事不會都這麼順利又美好的,若我 Customers 類別名稱跟資料庫不同的話呢?像是這樣:

 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
public class MyCustomers
{
    [Key]
    public int ID { get; set; }

    [Required]
    public string Name { get; set; }

    [Required]
    public string Address { get; set; }

    [Required]
    public string PhoneNumber { get; set; }

    [Required]
    public string Email { get; set; }

    [Column("NickName")]
    [MaxLength(50, ErrorMessage = "Your Nick Name too long")]
    public string TheNickName { get; set; }

    [NotMapped]
    public string NameplusNickName { get { return Name + NickName; } }
}

public class Demos : DbContext
{
    public DbSet<Mycustomersgt; Customer { get; set; }
}

重新建置後再次執行 Program.cs 的內容!Console 會印出:

1
2
3
Code First begining...

Process Run has been finished

沒了…但是讓我來看看DB有什麼變化吧

MyCustomers

噹~噹~! 恭喜你剛剛用 Code First 新建一個 Table 了耶!等等…這不是你要的效果嗎?! Entity Framework 一臉疑惑的問到 這時候你才發現到你剛剛手殘不小心把Table名稱打錯了,改快把名稱改回來重新建置一次,建置成功!讓我們再Run一次吧~

InvalideOperationException

你被殘酷的拒絕了…不過錯誤訊息很貼心的說你應該要用 Migration 來整合資料庫,因為已經不大一樣了。停!它怎麼知道資料庫不一樣了? 不要急…讓我們看看這張圖吧你就知道為什麼它會知道資料庫被動過了

addanunexcepttable

喔!什麼時候在系統資料表中新增了一個 _MigrationHistory 資料表了?!而且裡面還有一筆資料! 唔…這就是用 Code First 異動資料庫時它生出來的東西,在這種情況下要重新 run 時就請用 Migration 整合後再說吧! 不過先讓我們看看另外一種錯誤吧!

當我們在撰寫 Customers 類別時實在太粗心了,不小心多加了一個不屬於這個資料表的欄位(假設屬性名稱叫 NotExist )也就是說多了一個屬性,而且也沒有貼上[NotMapped]標籤,就直接很開心的 ctr+Alt+b 然後 F5 就下去了。依據經驗法則是不是會在資料庫中多一個欄位呢?讓我們看看吧~

FieldInvalied

嗯…是個錯誤訊息,表明了這欄位在 Customers 中找不到,所以放心~資料庫中的 Customers 表格也沒有多一個欄位。

最後(ps. Migration 怎麼用就留到下一篇吧),預設上 DbContext 會去找跟類別名稱相同的連線字串名稱,但是其實是可以自訂的,這需要參考 Syste.Data.Entity.dll。實際方式像是這樣:

1
2
3
4
5
6
7
8
public class Demos : DbContext
{
    public DbSet<customers> Customer { get; set; }

    // 這裡呼叫 DbContext 的建構子並且給予一個字串,這個字串就是所指定的連線字串名稱
    // 若有給定的話就不會用預設的方式去找連線字串名稱了
    public Demos() : base("Demo") { }
}

呼~這篇累積了好多 bug 沒有修啊…就下回待續吧 XD