在咱们开发业务的时候,通常数据库表都有相关的关系,除了单独表外,通常还包括一对多、多对多等常见的关系,在实际开发过程当中,须要结合系统框架作对应的处理,本篇随笔介绍基于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)