維護LINQ to SQL多對多表間關系
在項目開發中,經常會碰到維護多對多(many to many)關系表間關系的操作,例如為人員配置角色、為人員配置部門、為產品配置類別等。如果沒有經過程序設計而直接進行開發,將會過多地關注其細節問題,如:應刪除那些數據、應添加哪些數據、應保留哪些數據等,導致開發效率降低。
名詞解釋
在本文開始之前,首先以用戶-用戶角色-角色表為例,聲明三個概念:
l 主表:如果為用戶配置角色,那么用戶就是主表;如果為角色配置用戶,那么角色就是主表。
l 從表:如果為用戶配置角色,那么角色就是從表。
l 關系表:記錄用戶與角色表間關系的表。
行為描述
經過總結,發現其行為有統一的地方:傳遞主表對象與從表對象集合->獲取現有關系->對比出要刪除的關系->對比出要添加的關系->提交更改。
在下面的文章中,將一步步介紹完成的過程。
測試用例
使用ASP.NET網站程序,新建如下圖所示的頁面:
在左側的用戶選擇DropDownList中,選擇一個現有用戶后,會在右側的CheckBoxList中顯示其具有的角色。在進行完配置后,可以點擊上方的”保存”LinkButton進行保存。
下面我將4以種情況,來展示這個示例。在每個操作完成后,我們執行以下腳本,來查看操作是否成功:
SELECT * FROM [dbo].[UsersInRoles] |
開始測試前:
添加一個關系
在本次操作中,將為馬六配置一個“Java程序員”的角色。
點擊保存后,查看數據庫數據:
可以看到,已經成功添加一個關系。
添加兩個關系,其中一個將新建,一個不做處理
在本次操作中,將為馬六配置 “Java程序員 + .NET程序員”的角色。
點擊保存后,查看數據庫數據:
可以看到數據庫中的關系數據變為了兩條,并且RoleID為“63B04…”的關系數據的主鍵“064AB…”并沒有發生改變,這說明我并沒有做“全部刪除,再全部添加”的暴力型操作。
添加兩個關系,其中一個將新建,一個不做處理,一個將刪除
在本次操作中,將為馬六配置 “Java程序員 + 項目經理”的角色。
點擊保存后,查看數據庫數據:
可以看到RoleID為“71DFA…”的關系被刪除了,而且新建了一個RoleID為“3F45B…”的關系,而RoleID為“63B04…”的關系沒有發生改變。操作成功完成。
刪除所有關系
在本次操作中,將馬六的全部角色置空。
點擊保存后,查看數據庫數據:
可以看到用戶的關系被全部刪除了。至此,測試工作告一段落,開始介紹功能是如何完成的。
在點擊保存按鈕時,要做的就是將選中的用戶,以及為其配置的角色保存到數據庫中。有過這樣的開發經驗的開發人員會知道這個過程還是十分繁瑣的,在下面的代碼中,我將使用設計后的方式來完成這個操作:
/// <summary> /// 點擊保存按鈕時的操作 /// </summary> protected void btnSave_Click(object sender, EventArgs arg) { Users user = DataContext.Users.SingleOrDefault(e => e.UserID == new Guid(ddlUsers.SelectedValue)); List<Roles> roles = new List<Roles>(); foreach (ListItem item in cblUserRoles.Items) { if (item.Selected) { Roles role = DataContext.Roles.SingleOrDefault(e => e.RoleID == new Guid(item.Value)); if (role != null) { roles.Add(role); } } } LinqM2MProvider.Execute(user, roles, true); } |
可以看到,除了封裝參數(用戶對象,角色對象集合)的常規操作外,只調用了一個方法:LinqM2MProvider.Execute(user, roles, true)。那么這個LinqM2MProvider是什么呢?答案是一個接口,下面的代碼介紹了這個接口的構造方法:
/// <summary> /// 管理用戶-用戶角色-角色之間關系的提供程序 /// </summary> public ILinqM2MProvider<Users, UsersInRoles, Roles> LinqM2MProvider { get { var provider = new LinqM2MProvider<Users, UsersInRoles, Roles>() { DataContext = this.DataContext, GetRelationHandler = (m, s) => DataContext.UsersInRoles.SingleOrDefault(e => e.UserID == m.UserID && e.RoleID == s.RoleID), RelationSetHandler = m => m.UsersInRoles, CreateRelationHandler = (m, s) => new UsersInRoles() { UserInRoleID = Guid.NewGuid(), Users = m, Roles = s } }; return provider; } } |
可以看到ILinqM2MProvider接口是一個泛型接口,它的三個泛型類型是用戶-用戶角色-角色,就跟本文“名詞解釋”段落中的圖片所顯示關系一致。使用它需要設置四個屬性:
l DataContext
l GetRelationHandler
l RelationSetHandler
l CreateRelationHandler
那么這個四個屬性分別代表什么含義呢?下文將進行詳細的說明。
接口ILinqM2MProvider<M, R, S>
/// <summary> /// 維護LINQ to SQL的多對多關聯關系 /// </summary> /// <typeparam name="M">主表類型</typeparam> /// <typeparam name="R">關系表類型</typeparam> /// <typeparam name="S">從表類型</typeparam> /// <remarks> /// Sunny D.D at 2010-8-20 /// sunny19788989@gmail.com /// </remarks> public interface ILinqM2MProvider<M, R, S> where M : class where R : class where S : class { /// <summary> /// LINQ to SQL入口 /// </summary> DataContext DataContext { get; set; } /// <summary> /// 描述如何根據主表對象和從表對象獲取中間關系表對象的行為 /// </summary> Func<M, S, R> GetRelationHandler { get; set; } /// <summary> /// 描述如何根據主表對象和從表對象創建中間關系表對象的行為 /// </summary> Func<M, S, R> CreateRelationHandler { get; set; } /// <summary> /// 描述如何根據主表對象獲取獲取中間關系表對象集合 /// </summary> Func<M, EntitySet<R>> RelationSetHandler { get; set; } /// <summary> /// 獲取將要刪除的關系 /// </summary> /// <param name="master">主表對象</param> /// <param name="slaves">從表對象</param> /// <returns></returns> IEnumerable<R> GetDeleting(M master, IEnumerable<S> slaves); /// <summary> /// 獲取將要新建的關系 /// </summary> /// <param name="master">主表對象</param> /// <param name="slaves">從表對象</param> /// <returns></returns> IEnumerable<R> GetAdding(M master, IEnumerable<S> slaves); /// <summary> /// 執行操作。默認在方法結束時不將變更提交至數據庫,需要顯式提交變更。 /// </summary> /// <param name="master">主表對象</param> /// <param name="slaves">從表對象</param> void Execute(M master, IEnumerable<S> slaves); /// <summary> /// 執行操作 /// </summary> /// <param name="master">主表對象</param> /// <param name="slaves">從表對象</param> /// <param name="isSubmitChanges">是否在方法結束時將變更提交至數據庫</param> void Execute(M master, IEnumerable<S> slaves, bool isSubmitChanges); } |
DataContext屬性
類型:System.Data.Linq.DataContext
RelationSetHandler屬性
描述如何根據主表對象獲取獲取中間關系表對象集合。在LINQ to SQL架構中,一個表的外鍵對象集合是用EntitySet來表示的,EntitySet中的元素代表著主鍵表關聯的外建表的條目。在本文的“測試用例”部分中,是這樣賦值的:RelationSetHandler = m => m.UsersInRoles
GetRelationHandler屬性
描述如何根據主表對象和從表對象獲取中間關系表對象的行為。在本文中,就是根據用戶ID與角色ID獲取一個UsersInRoles對象。它的作用是確定有多少個中間表對象需要被操作。當然,最后提交至數據庫的元素,是與RelationSetHandler操作結果中包含的元素進行比對后才被執行的。在本文的“測試用例”部分中,是這樣賦值的:
GetRelationHandler = (m, s) => DataContext.UsersInRoles.SingleOrDefault(e => e.UserID == m.UserID && e.RoleID == s.RoleID)
CreateRelationHandler屬性
描述如何根據主表對象和從表對象創建中間關系表對象的行為。在傳遞的從表集合中,發現有需要新建的關系時,就要用到這個操作。在本文的“測試用例”部分中,是這樣賦值的:
CreateRelationHandler = (m, s) => new UsersInRoles() { UserInRoleID = Guid.NewGuid(), Users = m, Roles = s } |
GetDeleting方法:獲取將要刪除的關系。
GetAdding方法:獲取將要新建的關系。
Execute方法:執行操作。
接口的實現LinqM2MProvider<M, R, S>
至于接口的實現,各位肯定都有自己的方式,在這里我就不詳細說明了,本文給出一個實現僅供參考。點擊下載
總結
使用本文中介紹的方式來管理多對多表間關系,就可以不關注操作到底是進行添加關系、刪除關系、還是更改關系了,開發人員需要做的只是將三個委托GetRelationHandler、CreateRelationHandler、RelationSetHandler構造好,并傳遞正確的參數(主表對象、從表集合)即可,從而不再關注操作細節,提高開發效率。
本文資源下載
建表腳本:下載
頁面代碼:UsersInRolesDemo.aspx UsersInRolesDemo.aspx.cs
接口ILinqM2MProvider:下載
實現LinqM2MProvider:下載
如果鏈接打不開,請配置hosts文件:
209.85.225.101 docs.google.com
74.125.127.100 writely.google.com
74.125.127.139 spreadsheets.google.com