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)