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属性:是在维护关联关系的时候起作用的 ...
 
随机推荐
- c++中sprintf和sprintf_s的区别
			
参考:https://blog.csdn.net/qq_37221466/article/details/81140901 sprintf_s是sprintf的安全版本,指定缓冲区长度来避免sprin ...
 - 编程体系结构(06):Java面向对象
			
本文源码:GitHub·点这里 || GitEE·点这里 一.基础概念 1.面向对象概念 面向对象编程的主要思想是把构成问题的各个事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙一 ...
 - Java变量命名前俩个字母仅含有一个大写字母的坑
			
背景 前几周在做项目fetch切换,即将HttpUtils调用改成使用Feign调用.大概代码如下: // 原代码 String resultJson = HttpUtil.get(url + &qu ...
 - fio硬盘测速windows+linux
			
一.FIO工具简介 Fio工具的介绍网上有很多,都是可以通用的,这里就不做太多个人描述了,直接借鉴一下 fio是一种I / O工具,用于基准测试和压力/硬件验证.它支持19种不同类型的I / O引擎( ...
 - DM9000裸机驱动程序设计
			
对于任何一个硬件模块的设计,首先第一步都是要先了解硬件本身后,再开始程序的软件设计.而由于DM9000的芯片文档内容很多,要驱动好网卡,需要很长时间,特别对于新手比较困难,所以可以参考linux内核代 ...
 - 多测师讲解python_os模块_高级讲师肖sir
			
#os.path.isfile()#:判断当前是否为文件,返回布尔值是文件则True否者Falsea_path='F:\cms搭建.rar' #lesson包b_path=r'D:\bao\kk '# ...
 - 《Kafka笔记》1、Kafka初识
			
目录 一.初识Kafka 1 apache kafka简介 2 消息中间件kafka的使用场景 2.1 订阅与发布队列 2.2 流处理 3 kafka对数据的管理形式 4 kafka基础架构 5 Ka ...
 - Python 从入门到精通:一个月就够了
			
毫无疑问,Python 是当下最火的编程语言之一.对于许多未曾涉足计算机编程的领域「小白」来说,深入地掌握 Python 看似是一件十分困难的事.其实,只要掌握了科学的学习方法并制定了合理的学习计划, ...
 - CentOS 环境变量编辑、保存、立即生效的方法
			
方法一: 该方法只能修改临时配置文件,当每次系统重启后,配置文件将失效 假如我的安装路径如下:/home/oracle/app/oracle/product/11.2.0/dbhome_1/bin 那 ...
 - Docker 也是本地开发的一神器:部署单机版 Pulsar 和集群架构 Redis
			
原文链接:Docker 也是本地开发的一神器:部署单机版 Pulsar 和集群架构 Redis 一.前言: 现在互联网的技术架构中,不断出现各种各样的中间件,例如 MQ.Redis.Zookeeper ...