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)

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM