一起谈.NET技术,维护LINQ to SQL多对多表间关系

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

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

  名词解释

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

clip_image004

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

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

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

  行为描述

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

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

  测试用例

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

clip_image006

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

建表脚本下载

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

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

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

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

/// <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>

至于接口的实现,各位肯定都有自己的方式,在这里我就不详细说明了,本文给出一个实现仅供参考。点击下载

  总结

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

  本文资源下载

建表脚本:下载

页面代码: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

目录
相关文章
|
11月前
|
SQL 数据可视化 关系型数据库
MCP与PolarDB集成技术分析:降低SQL门槛与简化数据可视化流程的机制解析
阿里云PolarDB与MCP协议融合,打造“自然语言即分析”的新范式。通过云原生数据库与标准化AI接口协同,实现零代码、分钟级从数据到可视化洞察,打破技术壁垒,提升分析效率99%,推动企业数据能力普惠化。
892 3
|
10月前
|
监控 Cloud Native 测试技术
.NET技术深度解析:现代企业级开发指南
每日激励:“不要一直责怪过去的自己,他曾经站在雾里也很迷茫”。我是蒋星熠Jaxonic,一名在代码宇宙中探索的极客旅人。从.NET Framework到.NET 8,我深耕跨平台、高性能、云原生开发,践行领域驱动设计与微服务架构,用代码书写技术诗篇。分享架构演进、性能优化与AI融合前沿,助力开发者在二进制星河中逐光前行。关注我,共探技术无限可能!
.NET技术深度解析:现代企业级开发指南
|
SQL 存储 关系型数据库
【SQL技术】不同数据库引擎 SQL 优化方案剖析
不同数据库系统(MySQL、PostgreSQL、Doris、Hive)的SQL优化策略。存储引擎特点、SQL执行流程及常见操作(如条件查询、排序、聚合函数)的优化方法。针对各数据库,索引使用、分区裁剪、谓词下推等技术,并提供了具体的SQL示例。通用的SQL调优技巧,如避免使用`COUNT(DISTINCT)`、减少小文件问题、慎重使用`SELECT *`等。通过合理选择和应用这些优化策略,可以显著提升数据库查询性能和系统稳定性。
826 9
|
SQL 小程序 API
如何运用C#.NET技术快速开发一套掌上医院系统?
本方案基于C#.NET技术快速构建掌上医院系统,结合模块化开发理念与医院信息化需求。核心功能涵盖用户端的预约挂号、在线问诊、报告查询等,以及管理端的排班管理和数据统计。采用.NET Core Web API与uni-app实现前后端分离,支持跨平台小程序开发。数据库选用SQL Server 2012,并通过读写分离与索引优化提升性能。部署方案包括Windows Server与负载均衡设计,确保高可用性。同时针对API差异、数据库老化及高并发等问题制定应对措施,保障系统稳定运行。推荐使用Postman、Redgate等工具辅助开发,提升效率与质量。
685 0
|
开发框架 算法 .NET
C#/.NET/.NET Core技术前沿周刊 | 第 15 期(2024年11.25-11.30)
C#/.NET/.NET Core技术前沿周刊 | 第 15 期(2024年11.25-11.30)
347 6
|
开发框架 Cloud Native .NET
C#/.NET/.NET Core技术前沿周刊 | 第 16 期(2024年12.01-12.08)
C#/.NET/.NET Core技术前沿周刊 | 第 16 期(2024年12.01-12.08)
389 6
|
机器学习/深度学习 人工智能 Cloud Native
在数字化时代,.NET 技术凭借其跨平台兼容性、丰富的类库和工具集以及卓越的性能与效率,成为软件开发的重要平台
在数字化时代,.NET 技术凭借其跨平台兼容性、丰富的类库和工具集以及卓越的性能与效率,成为软件开发的重要平台。本文深入解析 .NET 的核心优势,探讨其在企业级应用、Web 开发及移动应用等领域的应用案例,并展望未来在人工智能、云原生等方面的发展趋势。
527 3
|
敏捷开发 缓存 中间件
.NET技术的高效开发模式,涵盖面向对象编程、良好架构设计及高效代码编写与管理三大关键要素
本文深入探讨了.NET技术的高效开发模式,涵盖面向对象编程、良好架构设计及高效代码编写与管理三大关键要素,并通过企业级应用和Web应用开发的实践案例,展示了如何在实际项目中应用这些模式,旨在为开发者提供有益的参考和指导。
244 3
|
Oracle 网络协议 关系型数据库
SQLNET跟踪tnsping过程
内容介绍 sqlnet是oracle提供的与网络层面交互的一个工具,比如如何解析客户端发起的连接,如何对客户端发起的连接进行辨别,如何对客户端连接进行阻隔限制,或者启用日志及跟踪(log and trace)功能等等一系列的功能,在以往的几期技术通讯录里面,我们也曾提到过通过sqlnet工具跟踪并分析oracle空闲回话中断的原因,本次我们继续通过sqlnet工具来详细的分析一次通过对tnsping的跟踪结果进行分析,查找为何外部网络无法连接数据库的原因。
1740 0
|
监控 前端开发 API
一款基于 .NET MVC 框架开发、功能全面的MES系统
一款基于 .NET MVC 框架开发、功能全面的MES系统
837 5