ABP框架中一对多,多对多关系的处理以及功能界面的处理(1)
在我们开发业务的时候,一般数据库表都有相关的关系,除了单独表外,一般还包括一对多、多对多等常见的关系,在实际开发过程中,需要结合系统框架做对应的处理,本篇随笔介绍基于ABP框架对EF实体、DTO关系的处理,以及提供对应的接口进行相关的数据保存更新操作。
1、一对多关系的数据处理
一对多,也可以叫做主从表的关系,其中从表有一个外键和主表进行关联,如下所示。

上图是一个简单的主从表关系,其中客户信息表只有简单的一两个字段用于演示,从表用来记录对应客户的地址信息。
其中表中的CreateUserId、CreateTime、LastModifierUserId、LastModificationTime、DeleterUserId、IsDeleted、DeletionTime、TenantId字段,是我们一般设计ABP表保留的字段。
我们先从一个关系图来了解下框架下的领域驱动模块中的各个类之间的关系。

ABP框架,和应用服务层或者界面层打交道的数据对象是DTO对象,和数据库打交道的是实体对象,连接连接起来是通过AutoMapper实现映射处理,映射是通过映射文件进行配置,一般我们可以根据数据库表信息进行生成DTO、Entity,以及映射文件。
以客户及客户地址表为例,生成的DTO对象如下所示。
/// <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 }
映射文件如下所示。
/// <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对象即可。

有了这些,基于ABP框架的基础上就可以实现数据的创建、更新提交了。
2、一对多关系的界面处理和服务端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的服务端应用服务层接口,接收从表明细,然后一个接口就可以处理主从表的数据保存或者更新了。
具体如何选择数据处理的方式,需要根据业务的场景进行衡量。
3、结合代码生成工具实现后台代码和主从表界面代码的快速生成
一旦业务规则确定,我们可以运用代码生成工具来提高开发效率了。由于主从表关系的处理比较统一,因此我们可以按照他们的关系以及界面常见的处理方式来生成这些内容。
首先,我们打开代码生成工具,展开对应数据库的表信息,如下界面所示。

选择ABP框架代码生成,可以生成后台框架代码,其中包括DTO实体、实体对象、映射文件,服务端应用层接口和实现等内容。
生成Winform主从表界面的时候,选择Winform代码生成,如下界面所示。

然后在弹出的界面里面选择主从表界面的生成选项卡即可。

ABP框架中一对多,多对多关系的处理以及功能界面的处理(1)的更多相关文章
- hibernate中一对多多对一关系设计的理解
1.单向多对一和双向多对一的区别? 只需要从一方获取另一方的数据时 就使用单向关联双方都需要获取对方数据时 就使用双向关系 部门--人员 使用人员时如果只需要获取对应部门信息(user.getdept ...
- SSAS中事实表中的数据如果因为一对多或多对多关系复制了多份,在维度上聚合的时候还是只算一份
SSAS事实表中的数据,有时候会因为一对多或多对多关系发生复制变成多份,如下图所示: 图1 我们可以从上面图片中看到,在这个例子中,有三个事实表Fact_People_Money(此表用字段Money ...
- JPA实体关系映射:@ManyToMany多对多关系、@OneToMany@ManyToOne一对多多对一关系和@OneToOne的深度实例解析
JPA实体关系映射:@ManyToMany多对多关系.@OneToMany@ManyToOne一对多多对一关系和@OneToOne的深度实例解析 今天程序中遇到的错误一 org.hibernate.A ...
- AutoMapper在ABP框架中的使用说明
为了说明AutoMapper如何使用,我专门开设了一个专题来讲,如果您还没有查看该专题,请点击这里.既然系统地学习了AutoMapper,那么接下来就是该用它实战的时候了.今天,我们就来揭开AutoM ...
- SSAS中CUBE的多对多关系既可以出现在中间事实表上也可以出现在中间维度表上
开发过SSAS中CUBE的朋友,肯定都知道维度用法中的多对多关系, 这篇文章不想详细阐述多对多关系在CUBE中的结构,详情请在网上寻找CUBE多对多关系的介绍资料. 下面是是一个典型的CUBE中多对多 ...
- 2018.11.4 Hibernate中一对、多对多的关系
简单总结一下 多表关系 一对多/多对一 O 对象 一的一方使用集合. 多的一方直接引用一的一方. R 关系型数据库 多的一方使用外键引用一的一方主键. M 映射文件 一: 多: 操作: 操作管理级别属 ...
- Hibernate框架学习(六)——一对多&多对一关系
一.关系表达 1.表中的表达 2.实体中的表达 3.orm元数据中的表达 一对多:(在Customer.hbm.xml中添加) 多对一:(在LinkMan.hbm.xml中添加) 最后别忘了在hibe ...
- SQLAlchemy_定义(一对一/一对多/多对多)关系
目录 Basic Relationship Patterns One To Many One To One Many To Many Basic Relationship Patterns 基本关系模 ...
- hibernate中一对多 多对多 inverse cascade
----------------------------一对多------------------------------------------- inverse属性:是在维护关联关系的时候起作用的 ...
随机推荐
- python双向链表的实现
python双向链表和单链表类似,只不过是增加了一个指向前面一个元素的指针,下面的代码实例了python双向链表的方法 示意图: python双向链表实现代码: # -*- coding: utf-8 ...
- Win10桌面不见了只显示开始菜单该怎么办?
来源:http://www.w10zj.com/Win10xy/Win10xf_4256.html 在Win10系统中,有用户反应桌面不见了,只显示开始菜单的情况,该怎么办呢?出现这样的情况一般由于桌 ...
- Flink深入浅出: 应用部署与原理图解(v1.11)
往期推荐: Flink深入浅出:内存模型 Flink深入浅出:JDBC Source从理论到实战 Flink深入浅出:Sql Gateway源码分析 Flink深入浅出:JDBC Connector源 ...
- 11 . Nginx核心原理讲解
应用场景优缺点 应用场景 // 1.静态请求 // 2.反向代理 // 3.负载均衡 // 4.资源缓存 // 5.安全防护 // 6.访问限制IP // 7.访问认证 /* 核心主要是以下三个应用: ...
- ConcurrentHashMap源码解析,多线程扩容
前面一篇已经介绍过了 HashMap 的源码: HashMap源码解析.jdk7和8之后的区别.相关问题分析 HashMap并不是线程安全的,他就一个普通的容器,没有做相关的同步处理,因此线程不安全主 ...
- git冲突的表现
<<<<<<< HEAD b789 ======= b45678910 >>>>>>> 6853e5ff961e68 ...
- Prometheus 入门教程(一):Prometheus 快速入门
文章首发于[陈树义]公众号,点击跳转到原文:https://mp.weixin.qq.com/s/ZXlBPHGcWeYh2hjBzacc3A Prometheus 是任何一个高级工程师必须要掌握的技 ...
- goland 注册
注意:本教程已过期 请使用其他人教程激活最新版 https://www.789zhao.com/blog/JC08EIFBS9TM.html https://shimo.im/docs/dKYC ...
- js 如何获取浏览器的高度?
<SCRIPT LANGUAGE="JavaScript"><!--var s = ""; s += " 网页可见区域宽:" ...
- Python之集合详解
定义: 1.不同元素组成 2.无序 3.集合中的元素必须是不可变类型 创建集合 s = {1,2,3,4,5,6,7,8} 1.定义可变集合 >>> set_test = set(' ...