2013年10月14日 星期一

ASP.NET MVC 使用 Entity Framework Code First - 基礎入門

最近看到 Bibby 與 KKBruce 都寫了 Code First 的相關文章,所以也就跟著來寫一篇,

Bibby - Simple Thoughts on Everything: Code First 紀錄

KingKong Bruce記事: 使用Entity Framework Code first建立多對多關係

用我的角度與敘述方式來為大家簡單說明,而這一篇文章所建立好的範例也將會在後續的其他主題的文章繼續沿用。

 


2013-10-15 更新:

目前作者「Hans Wolff」已將「ASP.NET MVC 4 Bootstrap Layout Template (VS2012)」與「ASP.NET MVC 4 Bootstrap Layout Template (VS2013)」從 Visual Studio Gallery 上給移除了,所以現在無法從 Visual Studio 的「擴充功能和更新」裡安裝,可以改使用另一個 Extensions「Bootstrap3 For Mvc4」,這邊有文章介紹「Visual Studio Extensions - Bootstrap3 For Mvc4」。

此篇文章所說明的 Entity Framework Code First 功能,不是只有在「ASP.NET MVC 4 Bootstrap Layout Template (VS2012)」專案範本裡才能使用,在 Visual Studio 預設的 ASP.NET MVC 4 專案範本裡就可以使用。

 

 

Code First」是 Entity Framework 三種建置方式的其中一種,其他兩種分別是「Database First」與「Model First」,而「Database First」是最被大家所熟悉以及應用在專案上,至於「Model First」就比較少看到有人採用這種建置方式。

以簡單的方式來說就是,我們在開發專案的時候,先定義好系統內所使用的類別,而 EF Code First 會依照程式裡類別的定義在指定的資料庫表格以及欄位,這麼一來程式開發人員只需要專案於系統的開發,而不需要花時間先在資料庫裡建表格及欄位。而因為需求變更而要修改類別時,使用 Code First 只需要變更屬性設定,接著下指令執行後就會去修改資料庫裡的表格欄位,另外雖然 Code First 有比較多且複雜的操作,但與 Database First 或 Model First 相較,Code First 對程式開發人員來說是提供了比較多的彈性與操作。

這邊並不是說 Database First 不好,Code First 與 Database First 各有各的優點與好處,如果開發人員對於物件導向觀念清楚,而且有充分的全力能夠控管專案所使用的資料庫時,在一個新的開發專案裡可以考慮使用 Code First,而如果所開發專案的資料庫是使用既有的內容,或者開發人員並沒有資料庫比較高的權限時,使用 Database First 則是比較適當。

所以依據開發專案的實際情況來判別並選擇要用那一種 Entity Framework 建置策略方式,並沒有說一定要用那一種比較好,都有各自的好處。

前面廢話了一堆,下面就直接進入實作的部份,要實作的範式是要建立後台最基本的兩個物件,一個是「系統使用人員」而另一個是「系統角色」,兩者是多對多「many to many」的關係,一個系統使用人員可以有多個系統角色,而一個系統角色也會有多個系統使用人員,我們就針對這樣的情境來使用 Code First 進行開發。

 

Step.1

於 VS2012 裡建立一個 ASP.NET MVC 4 專案,這裡所使用的是之前有介紹過得「ASP.NET MVC 4 Bootstrap Layout Template (VS2012)」專案範本,

image

image

 

Step.2

使用 ASP.NET MVC 4 Bootstrap Layout Template (VS2012) 並沒有加入 EntityFramework,所以在建立網站專案之後就需要使用 NuGet 加入 EntityFramewwork,

image

 

Step.3

然後在專案裡的 App_Data 目錄裡加入一個 SQL Server 資料庫,這個範例要使用 LocalDB 來完成,Code First 可以使用在 LocalDB, SQL Server Express 以及 SQL Server 上,

SNAGHTML60e64f0

image

再來開啟根目錄下的 Web.Config 檔案,加入資料庫連接字串,

image

<connectionStrings>
    <add
        name="DefaultConnection"
        connectionString="data source=(LocalDB)\v11.0;attachdbfilename=|DataDirectory|\Sample.mdf;integrated security=True;"
        providerName="System.Data.SqlClient" />
</connectionStrings>

 

Step.4

在 Models 目錄下分別建立 SystemUser.cs, SystemRole.cs, SampleContext.cs 三個類別,

image

SystemUser.cs

public class SystemUser
{
    [DisplayName("ID")]
    [Key]
    [Required]
    public Guid ID { get; set; }
 
    [DisplayName("帳號")]
    [Required]
    [StringLength(50)]
    [MinLength(5, ErrorMessage = "長度不可小於 5")]
    [MaxLength(50, ErrorMessage = "長度不可超過 50")]
    public string Account { get; set; }
 
    [DisplayName("密碼")]
    [Required]
    [StringLength(200)]
    [MinLength(5, ErrorMessage = "長度不可小於 5")]
    [MaxLength(200, ErrorMessage = "長度不可超過 200")]
    public string Password { get; set; }
 
    [DisplayName("名稱")]
    [Required]
    [StringLength(50)]
    [MinLength(2, ErrorMessage = "長度不可小於 2")]
    [MaxLength(50, ErrorMessage = "長度不可超過 50")]
    public string Name { get; set; }
 
    [DisplayName("Email")]
    [Required]
    [StringLength(200)]
    [MinLength(2, ErrorMessage = "長度不可小於 2")]
    [MaxLength(200, ErrorMessage = "長度不可超過 200")]
    public string Email { get; set; }
 
    [DisplayName("是否使用")]
    [Required]
    public bool IsEnable { get; set; }
 
    [DisplayName("建立者")]
    [Required]
    public Guid CreateBy { get; set; }
 
    [DisplayName("建立時間")]
    [Required]
    [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
    public DateTime CreateOn { get; set; }
 
    [DisplayName("更新者")]
    public Guid? UpdateBy { get; set; }
 
    [DisplayName("更新時間")]
    public DateTime? UpdateOn { get; set; }
 
    public SystemUser()
    {
        this.ID = Guid.NewGuid();
        this.IsEnable = false;
        this.CreateBy = new Guid();
 
        this.SystemRoles = new List<SystemRole>();
    }
 
    public ICollection<SystemRole> SystemRoles { get; set; }
}

SystemRole.cs

public class SystemRole
{
    [DisplayName("ID")]
    [Key]
    [Required]
    public Guid ID { get; set; }
 
    [DisplayName("名稱")]
    [Required]
    [StringLength(50)]
    [MinLength(2, ErrorMessage = "長度不可小於 2")]
    [MaxLength(50, ErrorMessage = "長度不可超過 50")]
    public string Name { get; set; }
 
    [DisplayName("排序")]
    [Required]
    public int Sort { get; set; }
 
    [DisplayName("是否使用")]
    [Required]
    public bool IsEnable { get; set; }
 
    [DisplayName("建立者")]
    [Required]
    public Guid CreateBy { get; set; }
 
    [DisplayName("建立時間")]
    [Required]
    [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
    public DateTime CreateOn { get; set; }
 
    [DisplayName("更新者")]
    public Guid? UpdateBy { get; set; }
 
    [DisplayName("更新時間")]
    public DateTime? UpdateOn { get; set; }
 
    public SystemRole()
    {
        this.ID = Guid.NewGuid();
        this.IsEnable = false;
        this.CreateBy = new Guid();
 
        this.SystemUsers = new List<SystemUser>();
    }
 
    public ICollection<SystemUser> SystemUsers { get; set; }
 
}

SystemUser 與 SystemRole 為多對多關係,所以兩個類別裡都有定義對方類別的集合物件屬性,

image

image

SampleContext.cs

namespace BlogSample.Models
{
    public class SampleContext : DbContext
    {
        public DbSet<SystemUser> SystemUsers { get; set; }
        public DbSet<SystemRole> SystemRoles { get; set; }
 
        public SampleContext()
            : base("name=DefaultConnection")
        {
 
        }
    }
}

其中 SampleContext 建構式裡呼叫基底類別建構函式所傳入的值,這是只我們在 Web.Config 裡建立的資料庫連結字串名稱,

image

image

 

Step.5

接著我們要使用「套件管理器主控台」下指令的方式來進行建立資料庫的操作,

image

如果開啟的方案裡有多個專案時,請記得要選擇「預設專案」,

image

 

第一個指令:啟用資料庫移轉

enable-migrations

如果專案裡不只一個 DbContext 類別的話,可以原本的指令後面 DbContext 類別名稱
enable-migrations –ContextTypeName BlogSample.Models.SampleContext

執行 enable-migrations 指令之後會在專案裡建立一個「Migrations」目錄以及「Configuration.cs」檔案,

image

image

 

第二個指令:新增執行資料庫移轉

add-migration 移轉名稱

移轉名稱可以標示每次移轉的版本編號或是具有意義的名稱,

第一次執行新增資料庫移轉,可以參考 Bibby 文章裡所寫的,使用「add-migration Init

image

指令執行完成之後,在 Migrations 目錄裡會新增一個檔案,這個移轉資料庫命令的檔案是等一下執行更新資料庫更新移轉時所要執行的內容,會比對系統內所定義的類別與資料庫表格的差異,然後載明需要變化的內容,

image

image

 

第三個指令:執行更新資料庫移轉

update-database

image

執行 update-database 時,系統有出現訊息「Specify the '-Verbose' flag to view the SQL statements being applied to the target database.」,在 update-database 指令後面再加上 –Verbose 參數,這樣就可以看「Code First 移轉」 所執行的 SQL Statement 內容,如果是執行「update-database –Verbose」指令的話,在套件管理器主控台裡就可以看到以下的內容,

image

執行完「update-datebase」指令後,可以開啟 SSMS 或是直接在 VS2012 的伺服器總管查看 LocalDB 的內容,能夠看到有建立 SystemRole 與 SystemUser 這兩個 Table 之外,另外也有建立除存多對多關係的 SystemRolesSystemUsers 這個 Table,這個存放多對多關係的 Table 並不需要我們另外在專案理去建立類別,而是系統根據我們在類別裡所定義的類別而建立的,

image

image

 

使用 Code First 的步驟就如同上述的過程,在開發階段時,如果類別有更動或是新增,那麼就是再重新執行「add-migration 定義名稱」與「update-database」指令。

 


Entity Framework Power Tools Beta 4

http://visualstudiogallery.msdn.microsoft.com/72a60b14-1581-4b9b-89f2-846072eff19d

image

使用 Code First 進行開發時,我們可以在 Visual Studio 2012 裡安裝 Entity Framework Power Tool,

image

EF Power Tools 提供許多功能,例如:從現有資料庫進行反向工程,產生資料庫表格相對應的類別、自訂反向工程範本、檢視實體資料模型、檢視實體資料庫模型 XML、檢視實體資料庫模型 DDL SQL、產生檢視等。

在專案項目上按下滑鼠右鍵,在選單裡的「Entity Framework」所看到的項目,

image

在專案裡所建立的 DbContext 檔案項目按下滑鼠右鍵,選單的「Entity Framework」所看到的項目,

image

在 SampleContext.cs 選擇檢視實體資料模型, SampleContext 內所定義的類別與關聯,就如同我們使用 Database First 時所看到的檢視實體資料模型一樣,差別在於 Code First 的檢視只能單純的查看而無法編輯,

image

另外也可以選擇「Generate Views (產生檢視)」,產生 EF 執行階段所要使用的先行編譯檢視以改善啟動效能。

image

產生 Entity Framework 執行階段要用的先行編譯檢視,以改善啟動效能。產生的檢視檔案會加入至專案。

image

注意:如果所定義的類別有所更動並且重新執行更新移轉,請記得要在執行更新移轉後重新產生檢視檔案,否則會出現錯誤。

 

有關 Entity Framework Power Tools 的詳細說明,可以詳閱以下連結的文章:

MSDN - Data Access and Storage > 學習園地 > Entity Framework > 開始使用 > EF Power Tools

MSDN - Data Access and Storage > 學習園地 > Entity Framework > 開始使用 > EF5 的效能考量

快快樂樂學 Code First 開發,以現有資料庫為例 - 積沙成塔- 點部落

 


Entity Framework Code First 還有很多進階的操作處理,這一篇所說的只是最基本的一小段而已,建議各位可以到「MSDN > Data Access and Storage > 學習園地 > Entity Framework (EF) 使用者入門」將「使用 Code First 建立模型」單元內的教學內容看過一遍, Code First 有大致的了解,但還是需要多多練習。

image

 

參考連結:

Bibby - Simple Thoughts on Everything: Code First 紀錄

KingKong Bruce記事: 使用Entity Framework Code first建立多對多關係

MSDN > Data Access and Storage > 學習園地 > Entity Framework (EF) 使用者入門

MSDN - 新資料庫的 Entity Framework Code First

MSDN - Entity Framework Code First 移轉

 

以上

5 則留言:

  1. Kevin Tseng,您好!請問ASP.NET MVC 4 Bootstrap Layout Template (VS2012)現在不能下載了嗎,Add New Project時選擇Online添加ASP.NET MVC 4 Bootstrap Layout時報Error:The operation could not be completed

    回覆刪除
    回覆
    1. Hello,
      目前「ASP.NET MVC 4 Bootstrap Layout Template (VS2012)」原作者已經把兩個相關的 Extensions 給移除了,
      不清楚是否要做更新還是真的移除而不再更新,可以改用「Bootstrap3 For Mvc4」,可參考以下文章:
      http://kevintsengtw.blogspot.tw/2013/10/visual-studio-extensions-bootstrap3-for.html

      刪除
  2. 你好:
    最近遇到多對多的問題,想請問一下。
    依你這編文章的資料結構來說好了,
    若是要把以下的 sql 改成 linq語法的話,
    要如何下呢?

    SELECT SystemUsers.Name FROM SystemRoles
    LEFT JOIN SystemRoleSytemUsers ON SystemRoles.ID = SystemRoleSytemUsers.SystemRole_ID
    LEFT JOIN SystemUsers ON SystemRoleSytemUsers.SystemUser_ID = SystemUsers.ID
    WHERE SystemRoles.ID = 1

    回覆刪除
    回覆
    1. 有很多人會把 LINQ 與 T-SQL 給糾結在一起,因為同樣都是對資料進行操作,
      然後 LINQ 透過 Provider 的處理,可以轉喚出相對應的 SQL Script,不管是 T-SQL 或是 PL/SQL,
      只要有支援資料庫對應的 Provider 就可以,
      但我並不會把 LINQ 與 SQL 給糾結在一起,所會的糾結就是去想「SQL 這樣寫而應該怎麼改用 LINQ 表達出來」或「LINQ 怎麼寫才能轉出我所想的那個 SQL Script」,
      LINQ 不等於 SQL Script,LINQ 也不是取代 SQL Script,LINQ 也不是只單純去做為轉換出 SQL Script 的工具或功能。

      SQL 的操作是去處理資料,面對的是一堆的 Table 與欄位,然後運用各種語法來存取物件,
      而 LINQ 呢?也同樣是處理資料,但是所面對的是一堆的物件與關聯,
      常常我們使用 SQL 去存取資料時都是在想,怎麼串語法然後找出我們所需要的資料,
      而 LINQ 的操作則是使用與 SQL 相似的語法在物件與物件關聯之間去找出資料,
      以往在沒有使用 LINQ 的時候,在一堆物件集合裡要怎麼找出符合某些條件的資料呢?多半都是迴圈來迴圈去,
      而有了 LINQ 操作之後,用類似 SQL 查詢語法的方式去找出物件裡符合條件的資料,讓所謂的物件操作更加的容易與直覺。

      在沒有資料庫的情況下一樣可以用 LINQ 嗎?
      當然可以,前面說過,LINQ 是用類似 SQL 查詢語法的操作方式去對物件資料來做操作,
      如果開發人員過往只對 DataSet, DataTable 打交道,而對物件導向的操作比較陌生時,就會容易在 LINQ 與 SQL 之間的使用觀念上攪和。

      關於你的問題,看了我前面所說的,我就直接這麼說,你操作的這幾個 Table 有對映到物件嗎?
      或是有使用 EF 去建立實體資料模型呢?Table 之間有沒有建立關聯呢?
      如果有建立了 Table 之間的關聯,也建立的資料模型,也建立了物件以及物件之間的關聯,
      那麼你的這個查詢式為了要找出有系統角色ID為「1」的系統使用者名稱,就可以使用關聯的方式來找出。

      我想看到 SQL 的 JOIN 也不要直覺的去想怎麼在 LINQ 裡一樣也使用 JOIN 來做操作,
      的確,LINQ 也有 JOIN 的操作,只是要看使用情境,
      很多時候將你要找什麼資料以口語化的方式描述一次,只要物件之間的關聯有建立並且有建立好,那麼就可以用點的就點得出來。

      如果對於 LINQ 不熟的話,建議可以去下載 LINQPad 來做練習操作的工具,免費的而且有提供相當多的範例做為參考,
      包含你所提問題的 JOIN 操作.

      LINQPad - 好用到爆炸、.NET開發人員必備的好用工具
      http://kevintsengtw.blogspot.tw/2011/09/linqpad-net.html#.VK62wyuUcs9

      刪除

提醒

千萬不要使用 Google Talk (Hangouts) 或 Facebook 及時通訊與我聯繫、提問,因為會掉訊息甚至我是過了好幾天之後才發現到你曾經傳給我訊息過,請多多使用「詢問與建議」(在左邊,就在左邊),另外比較深入的問題討論,或是有牽涉到你實作程式碼的內容,不適合在留言板裡留言討論,請務必使用「詢問與建議」功能(可以夾帶檔案),謝謝。