在咱們開發業務的時候,通常數據庫表都有相關的關係,除了單獨表外,通常還包括一對多、多對多等常見的關係,在實際開發過程當中,須要結合系統框架作對應的處理,本篇隨筆介紹基於ABP框架對EF實體、DTO關係的處理,以及提供對應的接口進行相關的數據保存更新操做。html
一對多,也能夠叫作主從表的關係,其中從表有一個外鍵和主表進行關聯,以下所示。前端
上圖是一個簡單的主從表關係,其中客戶信息表只有簡單的一兩個字段用於演示,從表用來記錄對應客戶的地址信息。vue
其中表中的CreateUserId、CreateTime、LastModifierUserId、LastModificationTime、DeleterUserId、IsDeleted、DeletionTime、TenantId字段,是咱們通常設計ABP表保留的字段。數據庫
咱們先從一個關係圖來了解下框架下的領域驅動模塊中的各個類之間的關係。後端
ABP框架,和應用服務層或者界面層打交道的數據對象是DTO對象,和數據庫打交道的是實體對象,鏈接鏈接起來是經過AutoMapper實現映射處理,映射是經過映射文件進行配置,通常咱們能夠根據數據庫表信息進行生成DTO、Entity,以及映射文件。app
以客戶及客戶地址表爲例,生成的DTO對象以下所示。echarts
/// <summary> /// 建立客戶信息,DTO對象 /// </summary> public class CreateCustomerDto : FullAuditedEntityDto<string> { /// <summary> /// 默認構造函數(須要初始化屬性的在此處理) /// </summary> public CreateCustomerDto() { this.Id = Guid.NewGuid().ToString(); } #region Property Members /// <summary> /// 姓名 /// </summary> [Required] public virtual string Name { get; set; } /// <summary> /// 年齡 /// </summary> //[Required] public virtual int? Age { get; set; } #endregion } /// <summary> /// 客戶信息,DTO對象 /// </summary> public class CustomerDto : CreateCustomerDto { }
/// <summary> /// 建立客戶地址簿,DTO對象 /// </summary> public class CreateCustomerAddressDto : CreationAuditedEntityDto<string> { /// <summary> /// 默認構造函數(須要初始化屬性的在此處理) /// </summary> public CreateCustomerAddressDto() { this.Id = System.Guid.NewGuid().ToString(); } #region Property Members /// <summary> /// 客戶ID /// </summary> //[Required] public virtual string Customer_ID { get; set; } /// <summary> /// 省份 /// </summary> //[Required] public virtual string Province { get; set; } /// <summary> /// 城市 /// </summary> //[Required] public virtual string City { get; set; } /// <summary> /// 區縣 /// </summary> //[Required] public virtual string District { get; set; } /// <summary> /// 詳細地址 /// </summary> //[Required] public virtual string DetailAddress { get; set; } /// <summary> /// 排序 /// </summary> //[Required] public virtual string SortCode { get; set; } #endregion } /// <summary> /// 客戶地址簿,DTO對象 /// </summary> public class CustomerAddressDto : CreateCustomerAddressDto { }
其表對應的實體類,也和DTO相似,不過是和數據庫打交道的數據對象框架
/// <summary> /// 客戶信息,領域對象 /// </summary> [Table("T_Customer")] public class Customer : FullAuditedEntity<string> { /// <summary> /// 默認構造函數(須要初始化屬性的在此處理) /// </summary> public Customer() { } #region Property Members /// <summary> /// 姓名 /// </summary> //[Required] public virtual string Name { get; set; } /// <summary> /// 年齡 /// </summary> //[Required] public virtual int? Age { get; set; } #endregion }
/// <summary> /// 客戶地址簿,領域對象 /// </summary> [Table("T_CustomerAddress")] public class CustomerAddress : CreationAuditedEntity<string> { /// <summary> /// 默認構造函數(須要初始化屬性的在此處理) /// </summary> public CustomerAddress() { } #region Property Members /// <summary> /// 客戶ID /// </summary> //[Required] public virtual string Customer_ID { get; set; } /// <summary> /// 省份 /// </summary> //[Required] public virtual string Province { get; set; } /// <summary> /// 城市 /// </summary> //[Required] public virtual string City { get; set; } /// <summary> /// 區縣 /// </summary> //[Required] public virtual string District { get; set; } /// <summary> /// 詳細地址 /// </summary> //[Required] public virtual string DetailAddress { get; set; } /// <summary> /// 排序 /// </summary> //[Required] public virtual string SortCode { get; set; } /// <summary> /// 客戶ID /// </summary> //[Required] [ForeignKey("Customer_ID")] public virtual Customer Customer { get; set; } #endregion }
映射文件以下所示。async
/// <summary> /// 客戶信息,映射文件 /// </summary> public class CustomerMapProfile : Profile { public CustomerMapProfile() { CreateMap<CustomerDto, Customer>(); CreateMap<Customer, CustomerDto>(); CreateMap<CreateCustomerDto, Customer>(); } }
/// <summary> /// 客戶地址簿,映射文件 /// </summary> public class CustomerAddressMapProfile : Profile { public CustomerAddressMapProfile() { CreateMap<CustomerAddressDto, CustomerAddress>(); CreateMap<CustomerAddress, CustomerAddressDto>(); CreateMap<CreateCustomerAddressDto, CustomerAddress>(); } }
而後在EFCore的上下文中添加對應的DBSet對象便可。ide
有了這些,基於ABP框架的基礎上就能夠實現數據的建立、更新提交了。
可是主從表之間的關係,咱們這裏尚未詳細說明,通常咱們在界面處理數據的時候,主表數據可能和從表數據一塊兒顯示,編輯的時候一塊兒保存,以下界面所示。
在編輯/新增界面中綁定GridView的相關顯示和處理事件。
咱們能夠在新增窗口中加載空地址列表,或者編輯窗口加載已有地址列表記錄
保存新增的記錄以下所示。
/// <summary> /// 新增狀態下的數據保存 /// </summary> /// <returns></returns> public async override Task<bool> SaveAddNew() { CustomerDto info = tempInfo;//必須使用存在的局部變量,由於部分信息可能被附件使用 SetInfo(info); try { #region 新增數據 tempInfo = await CustomerApiCaller.Instance.CreateAsync(info); if (tempInfo != null) { //可添加其餘關聯操做 var list = GetDetailList(); foreach(var detailInfo in list) { await CustomerAddressApiCaller.Instance.InsertOrUpdateAsync(detailInfo); } return true; } #endregion } catch (Exception ex) { LogTextHelper.Error(ex); MessageDxUtil.ShowError(ex.Message); } return false; }
其中GetDetailList是獲取編輯狀態下的數據記錄
/// <summary> /// 獲取明細列表 /// </summary> /// <returns></returns> private List<CustomerAddressDto> GetDetailList() { var list = new List<CustomerAddressDto>(); for (int i = 0; i < this.gridView1.RowCount; i++) { var detailInfo = gridView1.GetRow(i) as CustomerAddressDto; if (detailInfo != null) { list.Add(detailInfo); } } return list; }
若是數據更新的時候,操做也是相似
/// <summary> /// 編輯狀態下的數據保存 /// </summary> /// <returns></returns> public override async Task<bool> SaveUpdated() { CustomerDto info = await CustomerApiCaller.Instance.GetAsync(ID); if (info != null) { SetInfo(info); try { #region 更新數據 tempInfo = await CustomerApiCaller.Instance.UpdateAsync(info); if (tempInfo != null) { //可添加其餘關聯操做 var list = GetDetailList(); foreach(var detailInfo in list) { await CustomerAddressApiCaller.Instance.InsertOrUpdateAsync(detailInfo); } return true; } #endregion } catch (Exception ex) { LogTextHelper.Error(ex); MessageDxUtil.ShowError(ex.Message); } } return false; }
咱們這裏注意到無論更新仍是插入地址記錄,都用到了一個函數InsertOrUpdateAsync,這個是咱們後臺判斷記錄是新增或者更新,在寫入數據庫操做中的處理函數。
這個函數比較通用,咱們能夠考慮把它寫入公用的基類接口裏面便可。
一樣,客戶端的ApiCaller調用,也須要進行一個簡單的基類接口增長便可。
有了這些支持,Winform客戶端的處理就能夠直接調用這些簡單的接口進行主從表的數據提交了。
//可添加其餘關聯操做 var list = GetDetailList(); foreach(var detailInfo in list) { await CustomerAddressApiCaller.Instance.InsertOrUpdateAsync(detailInfo); }
另外,除了這種細粒度的接口處理,咱們還能夠把整個DTO對象包裝一下,在主表DTO對象中包含從代表細列表,而後重寫Create、Update的服務端應用服務層接口,接收從代表細,而後一個接口就能夠處理主從表的數據保存或者更新了。
具體如何選擇數據處理的方式,須要根據業務的場景進行衡量。
一旦業務規則肯定,咱們能夠運用代碼生成工具來提升開發效率了。因爲主從表關係的處理比較統一,所以咱們能夠按照他們的關係以及界面常見的處理方式來生成這些內容。
首先,咱們打開代碼生成工具,展開對應數據庫的表信息,以下界面所示。
選擇ABP框架代碼生成,能夠生成後臺框架代碼,其中包括DTO實體、實體對象、映射文件,服務端應用層接口和實現等內容。
生成Winform主從表界面的時候,選擇Winform代碼生成,以下界面所示。
而後在彈出的界面裏面選擇主從表界面的生成選項卡便可。
爲了方便讀者理解,我列出一下前面幾篇隨筆的鏈接,供參考:
按部就班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)