話說之前那一篇的 Entity Framework 觀念其實還是有很多地方是不大清楚的,像是使用 Code First with existing database 時什麼情況會異動到資料庫?什麼情況下需要做 Migrations?什麼情況下會直接吐個 Exception 給你而不是自動幫你多個 Table 或 Column?,所以今天就趁著一點時間來寫一下從接觸至今的處理經驗了…(說的好像經驗老到其實還是個新手)
前置工作#
這次的範例中我們會需要一個無辜的資料庫讓我們(恣意蹂躪)測試。
就讓我們把這個偉大的資料庫命名為…Demo!
唔…其實 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有什麼變化吧
噹~噹~!
恭喜你剛剛用 Code First 新建一個 Table 了耶!等等…這不是你要的效果嗎?! Entity Framework 一臉疑惑的問到
這時候你才發現到你剛剛手殘不小心把Table名稱打錯了,改快把名稱改回來重新建置一次,建置成功!讓我們再Run一次吧~
你被殘酷的拒絕了…不過錯誤訊息很貼心的說你應該要用 Migration 來整合資料庫,因為已經不大一樣了。停!它怎麼知道資料庫不一樣了?
不要急…讓我們看看這張圖吧你就知道為什麼它會知道資料庫被動過了
喔!什麼時候在系統資料表中新增了一個 _MigrationHistory 資料表了?!而且裡面還有一筆資料!
唔…這就是用 Code First 異動資料庫時它生出來的東西,在這種情況下要重新 run 時就請用 Migration 整合後再說吧!
不過先讓我們看看另外一種錯誤吧!
當我們在撰寫 Customers 類別時實在太粗心了,不小心多加了一個不屬於這個資料表的欄位(假設屬性名稱叫 NotExist )也就是說多了一個屬性,而且也沒有貼上[NotMapped]標籤,就直接很開心的 ctr+Alt+b 然後 F5 就下去了。依據經驗法則是不是會在資料庫中多一個欄位呢?讓我們看看吧~
嗯…是個錯誤訊息,表明了這欄位在 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