網頁

使用Entity Framework Code first建立多對多關係

Code First

Code First中文可以稱「程式碼先行」或「程式碼優先」,在Entity Framework 4.1時正式支援,它與一般開發習慣不同,先透過程式碼來定義資料結構(一般是POCO,Plain Old CLR Object),定義完成之後即可開始進行開發工作。在開發進行過程,開發人員不用管資料庫之類的問題,Entity Framework會進行處理,開發人員只要專注處理POCO與邏輯程式碼身上。

多對多關係?

多對多關係(Many-To-Many)是指資料兩者之間的關係是多對多,例如,醫生與病人,一個醫生會醫治多個病人,一個病人依病情不同也會有多個醫生來治療(會診即是一種),這種關係在資料庫領域稱多對多關係(Many-to-Many)。

有一點點資料庫經驗的人員都知道,實務上在資料庫中是沒有多對多資料表,多對多關係到了資料庫裡會化成三張資料表,分別建立成one-to-many與one-to-many的關係,以上例說明:「醫生(one)-to-醫生病人(many)」與「病人(one)-to-醫生病人(many)」,所以會有「醫生資料表」「醫生病人資料表」「病人資料表」三張資料表,中間透過「醫生病人資料表」來串接多對多關係。

ASP.NET MVC實作Code First Many-to-Many Relationship

以下我們透過ASP.NET MVC [4|5]專案來實作一次Code First的多對多關係。我們模擬的關係是「開發人員-to-專案」,一位開發人員可以同時接手多個專案,一個專案同時有多個開發人員進行開發。

  1. 新增ASP.NET MVC [4|5]專案。

    MVC 4請將AccountController.cs、AccountModels.cs、InitializeSimpleMembershipAttribute.cs相關類別移除或註解。

    MVC 5請將AccountController.cs、AccountViewModels.cs、IdentityModels.cs移除或註解。
  2. 在Models新增Dev類別,代表開發人員。
        public class Dev
        {
            public Dev()
            {
                Projects = new HashSet<Project>();
            }
         public int DevId { get; set; }
         public string DevName { get; set; }
         public ICollection<Project> Projects { get; set; }
        }
       

    注意ICollection<>,它代表的是的關係。
  3. 在Models新增Project類別,代表專案。
        public class Project
        {
            public Project()
            {
                Devs = new HashSet<Dev>();
            }
            public int ProjectId { get; set; }
               public string ProjectName { get; set; }
               public ICollection<Dev> Devs { get; set; }
           }
       

    注意ICollection<>,它代表的是的關係。
  4. 在Models新增DevProjectContext類別,進行Code First的DbContent設定。
        using System.Data.Entity;
        public class DevProjectContext : DbContext
        {
            public DevProjectContext()
                : base("DefaultConnection")
            { }
    
            public DbSet<Dev> Dev { get; set; }
            public DbSet<Project> Project { get; set; }
        }
       

    base("DefaultConnection")裡的DefaultConnection請查詢web.config,這裡是要將資料表儲存至SqlLocalDb裡的意思。
  5. 到/Home/Index裡撰寫一些簡單的程式碼:
       public ActionResult Index()
       {
           var p1 = new Project() { ProjectName = "Web API 2" };
           var p2 = new Project() { ProjectName = "MVC 5" };
           var p3 = new Project() { ProjectName = "Bootstrap 3" };
       
           var e1 = new Dev() { DevName = "Bruce" };
           var e2 = new Dev() { DevName = "Sherry" };
           var e3 = new Dev() { DevName = "Happy" };
       
           p1.Devs.Add(e1);
           p1.Devs.Add(e2);
       
           p2.Devs.Add(e2);
           p2.Devs.Add(e3);
       
           p3.Devs.Add(e3);
           p3.Devs.Add(e1);
       
           using (var ctx = new DevProjectContext())
           {
               ctx.Project.Add(p1);
               ctx.Project.Add(p2);
               ctx.Project.Add(p3);
       
               ctx.SaveChanges();
           }
       
           return View();
       }
       
  6. 執行應用程式,第一次要建立新資料庫所以啟動的時間會比平常久一些些。

Code First產生資料表

在上面範例中,我們定義好相關POCO類別後,立即/Home/Index裡即開始進行開發動作,啟動應用程式後,來看看我們最關心的資料表。

在App_Data中會產生預設的SqlLocalDb檔案(預設不會顯示),左是MVC 4,右是MVC 5:

Code First建立的資料庫

點擊進入之後可以看到多對多的三張資料表(左是MVC 4,右是MVC 5,MVC 5會多一張記錄異動的資料表,但那是額外的,重點是那三張資料表):

Entity Framework Code First建立多對多資料表

就這樣,我們很輕易的利用Code First定義資料類別與關係,立即進行開發,然後資料庫處理就教給Entity Framework來處理。

參考資料

  1. http://bibby.be/2013/10/code-first.html
  2. http://blogs.msdn.com/b/wriju/archive/2011/05/14/code-first-ef-4-1-building-many-to-many-relationship.aspx
  3. http://msdn.microsoft.com/en-us/data/ee712907
  4. http://msdn.microsoft.com/zh-tw/data/JJ193542.aspx

4 則留言:

  1. 範例照打不會動喔

    該不會是故意的吧 哈哈

    等一下會不會有人 mail 來要程式碼 XD

    Devs 中 s 的問題,有些地方有 s,有些地方沒有 s

    回覆刪除
    回覆
    1. 已修正 Dev.cs / Project.cs 的內容。謝謝。

      刪除
  2. 您好,
    最近在實作時,在1對多或多對多關係的查詢上有點疑問。

    在實作上,都會使用ICollection來宣告子項目,但ICollection並不是擴充於IQueryable。
    而ODATA或LINQ子查詢使用IQueryable的項目會將所有查詢統合到SQL,理論上會有更好的效能。
    但使用ICollection子項目的話,會使用完整的SQL Command去撈回所有子項目,之後才在記憶體中去處理子查詢/排序。

    例如,我想撈出某Dev的所有未關閉的Project、並以建立時間排序:
    var dev = ctx.Dev.Find(id);
    return dev.Projects.Where(proj => !proj.IsClose).OrderBy(proj => proj.CreatedOn);
    我監視了一下,在SQL端的確使用INNER JOIN撈出了所有屬於該dev的Project。
    那麼,篩選與排序就是在Server上做的。

    這樣應該會造成不必要的效能(Web Server)和頻寬(SQL Server與Web Server之間)負擔?
    有辦法能解決嗎?
    還是說不需要擔心這點?

    回覆刪除
    回覆
    1. Entity Framework 的效能調效是個大主題。

      基本認知是:EF的方便性是用效能換來的,如果真的非常非常Care效能,那就走ADO.NET方式不要走Entity Framework。

      「entity framework 效能」與「entity framework performance」關鍵字有些不錯的文章可以參考。

      不過,EF 5之後,預設會採用“預編譯”、“快取”…等方式來加速,當然,“第一次”還是會慢一些,不過後面的重覆性查詢,有時可以得到幾乎ADO.NET一樣快的結果。

      刪除

感謝您的留言,如果我的文章你喜歡或對你有幫助,按個「讚」或「分享」它,我會很高興的。