2013年10月15日 星期二

ASP.NET MVC 使用 Entity Framework Code First - 變更多對多關聯資料

上一篇「ASP.NET MVC 使用 Entity Framework Code First - 基礎入門」已經完成了系統使用者(SystemUser)與系統角色(SystemRole)兩個類別,並且建立了兩個類別多對多的關係,而這一篇則將會以系統使用者指定系統角色的資料變更操作來做說明。

文章裡並不會逐條說明每段程式與每個頁面的詳細內容與建置步驟,只會關注在多對多關聯資料的變更操作上。


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 專案範本裡就可以使用。

 

 

一開始在建置的時候就已經明確說過,一個使用者會有多個系統角色,而系統角色也可以有多個使用者,兩者是多對多的關係,

image

在使用 Database First 時,我們會在 Entity 檢視圖上看到還有一個「SystemRolesSystemUsers」用來紀錄兩個多對多的關聯資料,而使用 Code First 時,在 Entity 檢視圖裡並不會看到「SystemRolesSystemUsers」,而使用Code First 維護多對多關聯資料的作法則會比使用 Database First 時更為直覺,因為不會直接去操作「SystemRolesSystemUsers」。

以下說明在編輯系統使用者資料頁面裡,我們去指定系統使用者具有那些系統角色的身份,預先建立好三個系統角色的資料,

image

 

我們所建立的 SystemUser Edit 頁面並沒有系統角色的資料,所以我們先在編輯頁面裡加入系統角色的資料,使用 Checkbox List 來顯示已建立且可使用的所有系統角色資料,

image

SystemUserController – Edit 方法

image

取出使用者資料的同時也需要將關聯的系統角色資料一併取出,預設並不會將所有關聯資料全部載入(除非是在查詢條件裡有使用到關聯資料),而 GetSystemRoleSelectListItems 這個方法則是取得 List<SelecListItem> 的資料,讓檢視頁面於建立 CheckBox List 時能夠使用,另外如果已經有指定的系統角色,則該項 SelectListItem 的 Selected 狀態為 true,詳細程式內容如下:

image

 

接著我們要在 ~/SystemUser/Edit.cshtml 頁面裡增加系統角色資料的 Checkbox List,

image

所呈現的檢視頁面如下:

image

 

而 SystemUserController 接收 POST 資料的 Edit 方法為了要能夠取得系統角色 Checkbox List 的選項,所以增加 string[] roles,roles 名稱是對應檢視頁面上 Checkbox 所使用的名稱,

image

接收 POST 的 Edit 方法程式內容,在 SystemUser 本身的資料更新部份應該是沒有太大問題,而其途中用紅線框起來的部份,是更新使用者的系統角色關聯,

image

完整的 SystemUser Edit 程式如下:

public ActionResult Edit(SystemUser systemuser, string[] roles)
{
    //檢查登入帳號是否重複
    if (db.SystemUsers.Any(x => x.ID != systemuser.ID && x.Account == systemuser.Account))
    {
        ModelState.AddModelError("Account", "登入帳號不可重複");
        return View(systemuser);
    }
 
    if (ModelState.IsValid)
    {
        SystemUser user = db.SystemUsers
            .Include(x => x.SystemRoles)
            .FirstOrDefault(x => x.ID == systemuser.ID);
 
        if (!string.IsNullOrWhiteSpace(systemuser.Password))
        {
            user.Password = CryptographyPassword(systemuser.Password, PasswordSalt);
        }
        user.Name = systemuser.Name;
        user.Account = systemuser.Account;
        user.Email = systemuser.Email;
 
        user.UpdateBy = this.CurrentUserID;
        user.UpdateOn = DateTime.Now;
 
        //Update SystemRoles
        this.UpdateSystemRoleRelation(roles, user);
 
        db.Entry(user).State = EntityState.Modified;
        db.SaveChanges();
 
        return RedirectToAction("Index");
    }
 
    ViewBag.Roles = this.GetSystemRoleSelectListItems(systemuser);
    return View(systemuser);
}

UpdateSystemRoleRelation 方法的程式如下:

image

取得所有的系統角色資料,然後用迴圈方式與所選的系統角色項目逐一比對,

1. 使用者加入系統角色關聯,如果目前系統角色資料有存在於選項裡,且使用者並沒有這個系統角色項目時,則加入此系統角色。

2. 使用者移除系統角色關聯,如果目前的選項裡並沒有該項系統角色,而使用者已有此系統角色關聯時,則移除使用的該項系統角色關聯。

 

現在已經完成了系統使用者的編輯頁面以及更新程式,現在我們就可以更新系統使用者的系統角色資料,新增了一個 SystemUser「測試人員一」,剛建立的使用者尚未指定任何角色,

image

勾選「Normal」然後送出,SystemUserController 的 Edit 方法接收到所勾選的 SystemRole ID 資料,

image

在 SSMS 裡 SystemRolesSystemUser 查看,

image

再新增其他系統角色給「測試人員一」,

image

image

image

開啟 SystemUser 的 Details 頁面查看詳細資料,

image

 

如果是將「測試人員一」系統使用者資料刪除的話,那麼關聯的資料也需要一併刪除,而可以指定系統使用者所關聯的系統角色為 null,執行 db.SaveChanges() 後就會一併刪除 many-to-many 裡的關聯資料。

image

 

以上就是 ASP.NET MVC 使用 Entity Framework Code First 變更多對多資料的處理方式。

 



參考連結:

Updating Related Data with the Entity Framework in an ASP.NET MVC Application (6 of 10) : The Official Microsoft ASP.NET Site

 

以上

5 則留言:

  1. Kevin大你好,
    我想請問一下為甚麼 "~/SystemUser/Edit.cshtml" View 這邊只是 foreach in Roles 而已
    接下來在 Controller 中的 ActionResult 就可以接到 string[] roles ?
    看起來沒關聯啊,煩請指教,謝謝

    回覆刪除
    回覆
    1. 因為 Edit 頁面上 Checkbox 的 tag Name 為「Roles」,所以傳送到 controller 的 Action 方法就可以接收到,
      還是有關連的,這是基本的 modal binding 的操作。

      刪除
    2. 我懂了,謝謝你 Kevin大。

      刪除
  2. 因為有朋有在這一篇連續貼了四篇提問,可能因為內容有比較多的程式碼並且包含一些特殊字元,所以被 Google 留言功能給屏蔽,對於這樣的情況,請提問的朋友看一下現在頁面左邊中間的地方,是不是有看到「詢問與建議」的小工具呢?請多多利用這一個功能來與我討論,畢竟留言板只適合簡短的討論,並不適合做深度的探討,所以請多多利用「詢問與建議」,謝謝。

    BTW
    提問的朋友,有關程式的分層(切 Service, Repository 等分層),請注意各層的職責,Web 使用 Service 後,Web 是不應該也不可以透過 Service 去直接使用 Repository,所以你的問題會是在各分層的程式內容,職責是否清楚劃清。
    有關這部分的做法與相關內容,請參考這裡的「分層架構」系列文章,在[ASP.NET MVC 專案分層架構 - twMVC#18]這一篇文章裡有簡報檔並且將原始檔公開分享到 Github 上,所以可以去瀏覽與參考,謝謝。

    回覆刪除
  3. Kevin大大你好,

    感謝你的回覆,我會再花時間了解清楚「分層架構」系列文章。另外關於Contains的問題,我最後使用Any來取代了
    var item = new SelectListItem()
    {
    Text = role.Name,
    Value = role.Id.ToString(),
    Selected = systemUser.SystemRoles.Any(x=>x.Id==role.Id)
    };

    再次感謝你的指教。

    回覆刪除

提醒

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