LINQ to SQL多对多表间关系的维护方法

开发 后端
我们经常会碰到维护多对多(many to many)关系表间关系的操作,本文将介绍的是相关的维护方法。

在项目开发中,经常会碰到维护多对多(many to many)关系表间关系的操作,例如为人员配置角色、为人员配置部门、为产品配置类别等。如果没有经过程序设计而直接进行开发,将会过多地关注其细节问题,如:应删除那些数据、应添加哪些数据、应保留哪些数据等,导致开发效率降低。

名词解释

在本文开始之前,首先以用户-用户角色-角色表为例,声明三个概念:

clip_image004

l  主表:如果为用户配置角色,那么用户就是主表;如果为角色配置用户,那么角色就是主表。

l  从表:如果为用户配置角色,那么角色就是从表。

l  关系表:记录用户与角色表间关系的表。

行为描述

经过总结,发现其行为有统一的地方:传递主表对象与从表对象集合->获取现有关系->对比出要删除的关系->对比出要添加的关系->提交更改。

在下面的文章中,将一步步介绍完成的过程。

测试用例

使用ASP.NET网站程序,新建如下图所示的页面:

clip_image006

在左侧的用户选择DropDownList中,选择一个现有用户后,会在右侧的CheckBoxList中显示其具有的角色。在进行完配置后,可以点击上方的”保存”LinkButton进行保存。

下面我将4以种情况,来展示这个示例。在每个操作完成后,我们执行以下脚本,来查看操作是否成功:

脚本:

  1. SELECT * FROM [dbo].[UsersInRoles] 

开始测试前:

clip_image008

添加一个关系

在本次操作中,将为马六配置一个“Java程序员”的角色。

clip_image010

点击保存后,查看数据库数据:

clip_image012

可以看到,已经成功添加一个关系。

添加两个关系,其中一个将新建,一个不做处理

在本次操作中,将为马六配置 “Java程序员 + .NET程序员”的角色。

clip_image014

点击保存后,查看数据库数据:

clip_image016

可以看到数据库中的关系数据变为了两条,并且RoleID为“63B04…”的关系数据的主键“064AB…”并没有发生改变,这说明我并没有做“全部删除,再全部添加”的暴力型操作。

添加两个关系,其中一个将新建,一个不做处理,一个将删除

在本次操作中,将为马六配置 “Java程序员 + 项目经理”的角色。

clip_image018

点击保存后,查看数据库数据:

clip_image020

可以看到RoleID为“71DFA…”的关系被删除了,而且新建了一个RoleID为“3F45B…”的关系,而RoleID为“63B04…”的关系没有发生改变。操作成功完成。

删除所有关系

在本次操作中,将马六的全部角色置空。

clip_image022

点击保存后,查看数据库数据:

clip_image024

可以看到用户的关系被全部删除了。至此,测试工作告一段落,开始介绍功能是如何完成的。

在点击保存按钮时,要做的就是将选中的用户,以及为其配置的角色保存到数据库中。有过这样的开发经验的开发人员会知道这个过程还是十分繁琐的,在下面的代码中,我将使用设计后的方式来完成这个操作:

  1. /// <summary>  
  2. /// 点击保存按钮时的操作  
  3. /// </summary>  
  4. protected void btnSave_Click(object sender, EventArgs arg)  
  5. {  
  6. Users user = DataContext.Users.SingleOrDefault(e => e.UserID == new Guid(ddlUsers.SelectedValue));  
  7.     List<Roles> roles = new List<Roles>();  
  8.     foreach (ListItem item in cblUserRoles.Items)  
  9.     {  
  10.         if (item.Selected)  
  11.         {  
  12.     Roles role = DataContext.Roles.SingleOrDefault(e => e.RoleID == new Guid(item.Value));  
  13.             if (role != null)  
  14.             {  
  15.                 roles.Add(role);  
  16.             }  
  17.         }  
  18.     }  
  19.     LinqM2MProvider.Execute(user, roles, true);  

可以看到,除了封装参数(用户对象,角色对象集合)的常规操作外,只调用了一个方法:LinqM2MProvider.Execute(user, roles, true)。那么这个LinqM2MProvider是什么呢?答案是一个接口,下面的代码介绍了这个接口的构造方法:

  1. /// <summary>  
  2. /// 管理用户-用户角色-角色之间关系的提供程序  
  3. /// </summary>  
  4. public ILinqM2MProvider<Users, UsersInRoles, Roles> LinqM2MProvider  
  5. {  
  6.     get 
  7.     {  
  8.         var provider = new LinqM2MProvider<Users, UsersInRoles, Roles>()  
  9.         {  
  10.             DataContext = this.DataContext,  
  11.  GetRelationHandler = (m, s) => DataContext.UsersInRoles.SingleOrDefault(e => e.UserID == m.UserID && e.RoleID == s.RoleID),  
  12.             RelationSetHandler = m => m.UsersInRoles,  
  13.             CreateRelationHandler = (m, s) => new UsersInRoles()  
  14.             {  
  15.                 UserInRoleID = Guid.NewGuid(),  
  16.                 Users = m,  
  17.                 Roles = s  
  18.             }  
  19.         };  
  20.         return provider;  
  21.     }  

可以看到ILinqM2MProvider接口是一个泛型接口,它的三个泛型类型是用户-用户角色-角色,就跟本文“名词解释”段落中的图片所显示关系一致。使用它需要设置四个属性:

l  DataContext

l  GetRelationHandler

l  RelationSetHandler

l  CreateRelationHandler

那么这个四个属性分别代表什么含义呢?下文将进行详细的说明。

接口ILinqM2MProvider<M, R, S>

定义

  1. /// <summary> 
  2. /// 维护LINQ to SQL的多对多关联关系  
  3. /// </summary> 
  4. /// <typeparam name="M">主表类型</typeparam> 
  5. /// <typeparam name="R">关系表类型</typeparam> 
  6. /// <typeparam name="S">从表类型</typeparam> 
  7. /// <remarks> 
  8. /// Sunny D.D at 2010-8-20  
  9. /// sunny19788989@gmail.com  
  10. /// </remarks> 
  11. public interface ILinqM2MProvider<M, R, S> 
  12.     where M : class  
  13.     where R : class  
  14.     where S : class  
  15. {  
  16.     /// <summary> 
  17.     /// LINQ to SQL入口  
  18.     /// </summary> 
  19.     DataContext DataContext { get; set; }  
  20.     /// <summary> 
  21.     /// 描述如何根据主表对象和从表对象获取中间关系表对象的行为  
  22.     /// </summary> 
  23.     Func<M, S, R> GetRelationHandler { get; set; }  
  24.     /// <summary> 
  25.     /// 描述如何根据主表对象和从表对象创建中间关系表对象的行为  
  26.     /// </summary> 
  27.     Func<M, S, R> CreateRelationHandler { get; set; }  
  28.     /// <summary> 
  29.     /// 描述如何根据主表对象获取获取中间关系表对象集合  
  30.     /// </summary> 
  31.     Func<M, EntitySet<R>> RelationSetHandler { get; set; }  
  32.     /// <summary> 
  33.     /// 获取将要删除的关系  
  34.     /// </summary> 
  35.     /// <param name="master">主表对象</param> 
  36.     /// <param name="slaves">从表对象</param> 
  37.     /// <returns></returns> 
  38.     IEnumerable<R> GetDeleting(M master, IEnumerable<S> slaves);  
  39.     /// <summary> 
  40.     /// 获取将要新建的关系  
  41.     /// </summary> 
  42.     /// <param name="master">主表对象</param> 
  43.     /// <param name="slaves">从表对象</param> 
  44.     /// <returns></returns> 
  45.     IEnumerable<R> GetAdding(M master, IEnumerable<S> slaves);  
  46.     /// <summary> 
  47.     /// 执行操作。默认在方法结束时不将变更提交至数据库,需要显式提交变更。  
  48.     /// </summary> 
  49.     /// <param name="master">主表对象</param> 
  50.     /// <param name="slaves">从表对象</param> 
  51.     void Execute(M master, IEnumerable<S> slaves);  
  52.     /// <summary> 
  53.     /// 执行操作  
  54.     /// </summary> 
  55.     /// <param name="master">主表对象</param> 
  56.     /// <param name="slaves">从表对象</param> 
  57.     /// <param name="isSubmitChanges">是否在方法结束时将变更提交至数据库</param> 
  58.     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属性

描述如何根据主表对象和从表对象创建中间关系表对象的行为。

在传递的从表集合中,发现有需要新建的关系时,就要用到这个操作。在本文的“测试用例”部分中,是这样赋值的:

  1. CreateRelationHandler = (m, s) => new UsersInRoles()  
  2. {  
  3.     UserInRoleID = Guid.NewGuid(),  
  4.     Users = m,  
  5.     Roles = s  

GetDeleting方法

获取将要删除的关系。

GetAdding方法

获取将要新建的关系。

Execute方法

执行操作。

接口的实现LinqM2MProvider<M, R, S>

至于接口的实现,各位肯定都有自己的方式,在这里我就不详细说明了,本文给出一个实现仅供参考。下载地址:https://docs.google.com/leaf?id=0B9T0APtVi1fyNGE3ODA5YjctZmIwZC00MjU3LTg5NmYtNGIxZWI0OGI1OTkz&hl=zh_CN

总结

使用本文中介绍的方式来管理多对多表间关系,就可以不关注操作到底是进行添加关系、删除关系、还是更改关系了,开发人员需要做的只是将三个委托GetRelationHandler、CreateRelationHandler、RelationSetHandler构造好,并传递正确的参数(主表对象、从表集合)即可,从而不再关注操作细节,提高开发效率。

原文标题:维护LINQ to SQL多对多表间关系

链接:http://www.cnblogs.com/sunnycoder/archive/2010/08/22/1805875.html

【编辑推荐】

  1. Linq匿名类型简单概述
  2. Linq随机读取数据浅析
  3. Linq Lambda表达式全面分析
  4. Linq扩展方法简单分析
  5. 初探Linq局部变量类型
责任编辑:彭凡 来源: 博客园
相关推荐

2009-09-17 17:34:23

linq to sql

2009-09-15 10:35:11

linq多表查询

2009-09-15 13:28:49

LINQ表间关系查询

2009-09-17 18:05:15

linq to sql

2009-09-17 17:14:54

linq to sql

2010-10-21 11:10:57

SQL Server查

2009-09-15 11:29:04

LINQ to SQL

2009-06-18 14:22:06

Hibernate多对Hibernate

2009-06-04 16:14:22

Hibernate一对Hibernate一对Hibernate多对

2009-06-03 16:27:27

Hibernate一对一关系

2021-03-16 09:23:25

VueMixin模块

2010-10-08 13:56:32

2010-07-01 12:56:07

SQL Server表

2009-09-09 16:07:16

Linq实体关系

2021-04-12 18:14:56

鸿蒙HarmonyOS应用开发

2009-09-16 09:56:42

LINQ to SQL

2009-09-08 14:45:24

Linq to SQL支持SQL Serve

2023-06-12 08:09:01

FlaskSQLAlchemy

2009-09-18 16:41:46

Linq to sql

2009-09-10 10:37:15

LINQ to SQL
点赞
收藏

51CTO技术栈公众号