1. 程式人生 > >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)