ABP框架中一对多,多对多关系的处理以及功能界面的处理(2)

简介: ABP框架中一对多,多对多关系的处理以及功能界面的处理(2)

在我们开发业务的时候,一般数据库表都有相关的关系,除了单独表外,一般还包括一对多、多对多等常见的关系,在实际开发过程中,需要结合系统框架做对应的处理,本篇随笔介绍基于ABP框架对EF实体、DTO关系的处理,以及提供对应的接口进行相关的数据保存更新操作,这篇介绍多对多关系下的ABP框架的处理。

上篇随笔《ABP框架中一对多,多对多关系的处理以及功能界面的处理(1)》介绍了一对多关系下的主从表数据处理,包括ABP框架对EF实体、DTO等关系处理,以及应用层基类接口的调整和Apicaller调用层的封装,最后介绍了基于代码生成工具快速生成所需ABP框架代码和Winform界面代码的过程。

本篇基于ABP框架的基础上,继续介绍多对多关系的数据库设计、框架代码生成和调整,以实现常见多对多关系的数据处理。

1、多对多关系的数据库设计和界面关系

一般多对多的关系是指两个业务表之间存在关联关系,它们通过中间表(包含两个表的外键关系)建立多对多的关系,ABP框架除了两个外键关系外,一般还会增加几个系统字段,如下所示。

角色包含菜单资源也是多对多的关系,一般在角色新增或者编辑界面中进行维护。

或者

功能界面设计的时候,就需要考虑和这些表之间的关系维护,如商品类型中,基本信息里面和品牌关系进行绑定。

不管上面的树形列表,还是很后面的复选框组,都是先请求关联主表的数据,然后再请求对应角色或者商品类型下的关系数据,绑定到界面上。

如对于上面的树形列表,通过设置树列表的数据,以及选中的记录就可以实现对应关系的绑定。

<el-tree
      ref="tree"
      class="filter-tree"
      style="padding-top:10px"
     :data="treedata"
      node-key="id"
      icon-class="el-icon-price-tag"
      default-expand-all
      highlight-current
      :show-checkbox="showcheck"
      :filter-node-method="filterNode"
    :default-checked-keys="checkedList"
    >

因此,在树形列表绑定的时候,需要请求原有的全部菜单数据,以及属于该角色下的菜单数据,两相整合就可以实现复选框选中已有菜单的效果了。

async getlist() { // 树列表数据获取
      // 获取全部功能列表
      var param = { SkipCount: 0, MaxResultCount: 1000, Tag: 'web' }
      var treeList = [] // 所有功能列表
      await menu.GetAll(param).then(data => {
        treeList = data.result.items
      })
      // console.log(treeList)
      // 获取角色菜单列表
      var grantedList = []
      if (this.roleId && typeof (this.roleId) !== 'undefined') {
        param = { RoleId: this.roleId, MaxResultCount: 1000, MenuTag: 'web' }
        await role.GetMenusInRole(param).then(data => {
          grantedList = data.result.items
        })
      }
      // console.log(grantedList)

当然我们也可以把角色包含菜单数据放在角色对象的DTO里面,然后一次性就可以获得菜单集合了,如我这里介绍的商品类型中的包含的品牌列表做法一样。

 

2、ABP后端对于多对多关系的处理

多对多关系,是我们业务表常见的一种关系,如果是只读的展示,我们直接通过关联关系获得记录展示即可;如果是进行编辑的处理,那么需要获取关联主表的全部记录进行展示,然后根据关联关系,显示复选框勾中的记录展示。

刚才说到,我们商品类型中对于多对多的关系,可以通过后端直接返回对应的数据记录集合的,这种做法可以避免细粒度API的请求过程,不过对于太大的数据集合,建议还是通过单独的API进行获取。

我们为了在商品类型中返回相关品牌信息,那么需要定义一个简单的对象用来承载品牌信息,如下DTO所示。

/// <summary>
    /// 品牌简单信息
    /// </summary>
    public class BrandItemDto
    {
        /// <summary>
        /// 品牌ID
        /// </summary>
        public virtual long Id { get; set; }
        /// <summary>
        /// 品牌编码
        /// </summary>
        public virtual string BrandCode { get; set; }
        /// <summary>
        /// 品牌名称
        /// </summary>
        public virtual string BrandName { get; set; }
    }

这个DTO是我们自定义的,我们需要映射常规的品牌DTO对象到这个自定义的DTO里面,那么我们可以通过映射文件中加入对应的映射关系来处理,避免属性的一一复制,如下所示。

然后,就是我们在商品类型中使用这个DTO的集合了,如下所示。

我们知道,我们所有业务对象提供服务,都是通过对应的应用层服务接口提供,而商品类型这里对应的应用服务层对象是ProductTypeAppService,它继承自MyAsyncServiceBase基类对象,MyAsyncServiceBase基类对象重写了一些常规的方法,以便提供更方便的服务接口。

其中为了数据对象的转换方便,我们重写了Get和GetAll的方法,并提供一个通用的模板方法用来修改对象DTO的关系,如下代码所示。

其中ConvertDto方法就是我们给子类重写,以便实现数据转换关系的。例如,我们在子类ProductTypeAppService里面重写了ConvertDto方法。

/// <summary>
        /// 对记录进行转义
        /// </summary>
        /// <param name="item">dto数据对象</param>
        /// <returns></returns>
        protected override void ConvertDto(ProductTypeDto item)
        {
            //重写ConvertDto方法,返回其他关系数据
            var bindedBrands = GetBindedBrands(item.Id).Result.Items;
            //获取关联品牌的ID列表
            var brandIds = bindedBrands.Select(s => s.Id).ToArray();
            //获取关联品牌的对象列表
            var brandDtos = bindedBrands.Select(ObjectMapper.Map<BrandItemDto>).ToList();
            item.BindBrands = brandIds;     //纯ID集合
            item.BindBrandItems = brandDtos;//ID,BrandName,BrandCode 信息集合
        }

弄好了这些,我们测试接口,可以正确获得对应的记录列表了。

这样我们就可以在列表或者编辑界面里都展示对应的关系了。

在列表展示界面中绑定已有关系代码如下所示。

<el-table-column align="center" label="绑定品牌列表">
    <template slot-scope="scope">
      <el-tag
        v-for="opt in scope.row.bindBrandItems"
        :key="opt.id"
        type="primary"
        :disable-transitions="false"
      >
        {{ opt.brandName }}
      </el-tag>
    </template>
  </el-table-column>

在编辑界面中绑定已有关系代码如下所示。

<el-form-item label="品牌关联" prop="bindBrands">
    <el-checkbox-group v-model="editForm.bindBrands" style="padding:10px;box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1)">
      <el-checkbox v-for="(item, i) in brandList" :key="i" :label="item.id">{{ item.brandName }}</el-checkbox>
    </el-checkbox-group>
  </el-form-item>

其中editForm.bindBrands是我们包含的关系,而brandList这是所有品牌列表,这个需要在页面创建的时候,单独获取。

 

最后,需要介绍一下数据提交的时候,我们需要根据绑定列表关系,修改数据库已有的关联记录,这样实现关联关系的更新。

我们来看看创建商品类型和更新商品类型的时候,对关系数据的处理。

/// <summary>
        /// 重写创建操作,写入额外的信息
        /// </summary>
        /// <param name="input">商品类型对象DTO</param>
        /// <returns></returns>
        public override async Task<ProductTypeDto> CreateAsync(CreateProductTypeDto input)
        {
            CheckCreatePermission();
            var entity = MapToEntity(input);
            await Repository.InsertAsync(entity);
            await CurrentUnitOfWork.SaveChangesAsync();
            //写入中间表关系
            if (input.BindBrands != null)
            {
                foreach (var brandId in input.BindBrands)
                {
                    //增加新增的
                    await _brandTypeRepository.InsertAsync(new BrandType(AbpSession.TenantId, brandId, entity.Id));
                }
            }
            return MapToEntityDto(entity);
        }
        /// <summary>
        /// 重写更新操作,更新新的关系数据
        /// </summary>
        /// <param name="input">商品类型对象DTO</param>
        /// <returns></returns>
        public override async Task<ProductTypeDto> UpdateAsync(ProductTypeDto input)
        {
            //保存主记录
            var dto = await base.UpdateAsync(input);
            //写入中间表关系
            if (input.BindBrands != null)
            {
                var brandsDto = new BrandsToProductTypeDto() { BrandIds = input.BindBrands, ProductTypeId = input.Id };
                await AddBrandToType(brandsDto);
            }
            return dto;
        }

其中 AddBrandToType 就是修改已有的品牌关系,在介绍这个函数开始前,先来看看商品类型应用服务层的定义,引入了商品类型、品牌、商品类型和品牌关系表三者的仓储对象作为参数的。

/// <summary>
    /// 商品类型,应用层服务接口实现
    /// </summary>
    [AbpAuthorize]
    public class ProductTypeAppService : MyAsyncServiceBase<ProductType, ProductTypeDto, long, ProductTypePagedDto, CreateProductTypeDto, ProductTypeDto>, IProductTypeAppService
    {
        private readonly IRepository<ProductType, long> _repository;//业务对象仓储对象
        private readonly IRepository<User, long> _userRepository;//用户信息仓储对象
        private readonly IRepository<BrandType, long> _brandTypeRepository;//品牌分类中间表对象仓储对象
        private readonly IRepository<Brand, long> _brandRepository;//业务对象仓储对象
        public ProductTypeAppService(IRepository<ProductType, long> repository, IRepository<BrandType, long> brandTypeRepository, IRepository<Brand, long> brandRepository, IRepository<User, long> userRepository) : base(repository)
        {
            _repository = repository;
            _brandTypeRepository = brandTypeRepository;
            _brandRepository = brandRepository;
            _userRepository = userRepository;
        }

其中 AddBrandToType 需要修改关系,那么它的逻辑就是:如果不在新列表中的,移除数据库中的关系;如果新列表记录已在数据库中存在则跳过,否则写入关系。

详细代码如下所示,这个也是我们处理中间表之间关系的常见处理逻辑了。

/// <summary>
        /// 添加品牌到分类
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        public async Task AddBrandToType(BrandsToProductTypeDto input)
        {
            var typeInfo = Repository.GetAsync(input.ProductTypeId);
            if (typeInfo != null)
            {
                //获取与中间表联合的查询表达式
                var query = from cb in _brandTypeRepository.GetAll()
                            join b in _brandRepository.GetAll() on cb.Brand_ID equals b.Id
                            where cb.ProductType_ID == input.ProductTypeId
                            select b;
                var oldNotInNewList = query.Where(p => !input.BrandIds.Contains(p.Id)).ToList();
                foreach (var info in oldNotInNewList)
                {
                    //移除已有,但不在添加列表中的
                    await _brandTypeRepository.DeleteAsync(m => m.ProductType_ID == input.ProductTypeId && m.Brand_ID == info.Id);
                }
                if (input.BrandIds != null)
                {
                    //获取已有绑定列表
                    var currentBrands = query.ToList();
                    foreach (var brandid in input.BrandIds)
                    {
                        if (currentBrands.Any(cr => cr.Id == brandid))
                        {
                            continue; //已有重复的跳过
                        }
                        //否则增加新增的
                        await _brandTypeRepository.InsertAsync(new BrandType(AbpSession.TenantId, brandid, input.ProductTypeId));
                    }
                }
            }
        }

这样我们在商品类型编辑界面中可以随时变更关联关系了。

以上就是关于中间表的常见处理操作,希望对你学习ABP框架或者Element前端界面有所帮助。

 

为了方便读者理解,我列出一下前面几篇随笔的连接,供参考:

循序渐进VUE+Element 前端应用开发(1)--- 开发环境的准备工作

循序渐进VUE+Element 前端应用开发(2)--- Vuex中的API、Store和View的使用

循序渐进VUE+Element 前端应用开发(3)--- 动态菜单和路由的关联处理

循序渐进VUE+Element 前端应用开发(4)--- 获取后端数据及产品信息页面的处理

循序渐进VUE+Element 前端应用开发(5)--- 表格列表页面的查询,列表展示和字段转义处理

循序渐进VUE+Element 前端应用开发(6)--- 常规Element 界面组件的使用

循序渐进VUE+Element 前端应用开发(7)--- 介绍一些常规的JS处理函数

循序渐进VUE+Element 前端应用开发(8)--- 树列表组件的使用

循序渐进VUE+Element 前端应用开发(9)--- 界面语言国际化的处理

循序渐进VUE+Element 前端应用开发(10)--- 基于vue-echarts处理各种图表展示

循序渐进VUE+Element 前端应用开发(11)--- 图标的维护和使用

循序渐进VUE+Element 前端应用开发(12)--- 整合ABP框架的前端登录处理

循序渐进VUE+Element 前端应用开发(13)--- 前端API接口的封装处理

循序渐进VUE+Element 前端应用开发(14)--- 根据ABP后端接口实现前端界面展示

循序渐进VUE+Element 前端应用开发(15)--- 用户管理模块的处理

循序渐进VUE+Element 前端应用开发(16)--- 组织机构和角色管理模块的处理

循序渐进VUE+Element 前端应用开发(17)--- 菜单管理

循序渐进VUE+Element 前端应用开发(18)--- 功能点管理及权限控制  

循序渐进VUE+Element 前端应用开发(19)--- 后端查询接口和Vue前端的整合

循序渐进VUE+Element 前端应用开发(20)--- 使用组件封装简化界面代码  

循序渐进VUE+Element 前端应用开发(21)--- 省市区县联动处理的组件使用

循序渐进VUE+Element 前端应用开发(22)--- 简化main.js处理代码,抽取过滤器、全局界面函数、组件注册等处理逻辑到不同的文件中

循序渐进VUE+Element 前端应用开发(23)--- 基于ABP实现前后端的附件上传,图片或者附件展示管理

循序渐进VUE+Element 前端应用开发(24)--- 修改密码的前端界面和ABP后端设置处理

循序渐进VUE+Element 前端应用开发(25)--- 各种界面组件的使用(1)

循序渐进VUE+Element 前端应用开发(26)--- 各种界面组件的使用(2)

ABP框架中一对多,多对多关系的处理以及功能界面的处理(1)

电商商品数据库的设计和功能界面的处理

ABP框架中一对多,多对多关系的处理以及功能界面的处理(2)

 

专注于代码生成工具、.Net/.NetCore 框架架构及软件开发,以及各种Vue.js的前端技术应用。著有Winform开发框架/混合式开发框架、微信开发框架、Bootstrap开发框架、ABP开发框架、SqlSugar开发框架等框架产品。
 转载请注明出处:撰写人:伍华聪  http://www.iqidi.com

相关文章
|
5月前
|
开发框架 前端开发 JavaScript
在ABP VNext框架中处理和用户相关的多对多的关系
在ABP VNext框架中处理和用户相关的多对多的关系
|
5月前
|
开发框架 前端开发 JavaScript
ABP框架中一对多,多对多关系的处理以及功能界面的处理(1)
ABP框架中一对多,多对多关系的处理以及功能界面的处理(1)
|
5月前
|
设计模式 数据建模 测试技术
领域模型问题之领域模式多对多关联如何解决
领域模型问题之领域模式多对多关联如何解决
|
SQL 架构师 Java
SpringBoot从入门到精通(二十八) JPA 的实体映射关系,轻松一对一,一对多,多对多关系映射!
前面讲了Spring Boot 使用 JPA,实现JPA 的增、删、改、查的功能,同时也介绍了JPA的一些查询,自定义SQL查询等使用。JPA使用非常简单,功能非常强大的ORM框架,无需任何数据访问层和sql语句即可实现完整的数据操作方法。但是,之前都是介绍的单表的增删改查等操作,多表多实体的数据操作怎么实现呢?接下来聊一聊 JPA 的一对一,一对多,多对一,多对多等实体映射关系。
SpringBoot从入门到精通(二十八) JPA 的实体映射关系,轻松一对一,一对多,多对多关系映射!
|
7月前
|
数据采集 自然语言处理 Python
在 Django 中设计爬虫系统的数据模型与多对多关系
在构建爬虫系统时,设计合理的数据模型和多对多关系对系统的性能和可维护性至关重要。本文将探讨如何使用 Django 来设计爬虫系统的数据模型。
|
7月前
|
Python
使用Django时,如何设计模型关系(一对一、一对多、多对多)?
Django支持三种模型关联:ForeignKey(一对多),OneToOneField(一对一)和ManyToManyField(多对多)。ForeignKey示例:`Article`有一个指向`Author`的外键。OneToOneField示例:`UserProfile`与`User`一对一关联。ManyToManyField示例:`Student`和`Course`之间多对多关系。这些关联字段便于反向查询,如`article.author`获取作者,`author.article_set.all()`获取作者所有文章。
97 1
【Django实战】创建多对多关系表的三种模式及优缺点分析
【Django实战】创建多对多关系表的三种模式及优缺点分析
|
7月前
|
SQL 前端开发 Java
利用bladex+avue实现一对多的关系
利用bladex+avue实现一对多的关系
|
数据可视化 uml
UML图讲解(关联关系,单向关联,双向关联,自关联,组合关系,依赖关系,继承关系,实现关系)
UML图讲解,关联关系,单向关联,双向关联,自关联,组合关系,依赖关系,继承关系,实现关系。
3794 0
UML图讲解(关联关系,单向关联,双向关联,自关联,组合关系,依赖关系,继承关系,实现关系)
|
SQL Java 数据库连接
MyBatis 的关联关系配置 一对多,一对一,多对多 关系的映射处理
MyBatis 的关联关系配置 一对多,一对一,多对多 关系的映射处理
149 0