2012年11月16日 星期五

Code First with existiong db - 手殘問題

前言:
話說之前那一篇的 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 資料夾中
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 類別就算準備好了,接下來就是準備呼叫端了~
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類別名稱跟資料庫不同的話呢?像是這樣:
    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會印出:
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。實際方式像是這樣:
public class Demos : DbContext
    {
        public DbSet<customers> Customer { get; set; }
        
        // 這裡呼叫 DbContext 的建構子並且給予一個字串,這個字串就是所指定的連線字串名稱
        // 若有給定的話就不會用預設的方式去找連線字串名稱了
        public Demos() : base("Demo") { }
    }

呼~這篇累積了好多 bug 沒有修啊,但是我打字有點累了...就下回待續吧 XD

沒有留言:

張貼留言