假如有这么一个需求,就是订单表,包含了多级的信息,比如这个订单包含了哪些商品,又有哪些货品信息等,以下是一个概览,模拟的是实际的一些层级关系
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Volo.Abp.Domain.Entities;
namespace LevelEntity.levelmodels
{
/// <summary>
///
/// </summary>
public class OrderDetail : Entity<int>
{
/// <summary>
/// 标题
/// </summary>
[MaxLength(128)]
public string Title { get; set; }
/// <summary>
/// 封面图
/// </summary>
[MaxLength(128)]
public string CoverImage { get; set; }
/// <summary>
/// 订单包含产品
/// </summary>
public ICollection<OrderProduct> Products { get; set; }
/// <summary>
/// 订单金额
/// </summary>
public OrderPrice Price { get; set; }
}
/// <summary>
///
/// </summary>
public class OrderProduct : Entity<int>
{
/// <summary>
///
/// </summary>
public int ProductId { get; set; }
/// <summary>
///
/// </summary>
public int BuyNum { get; set; }
/// <summary>
///
/// </summary>
public int ProductPrice { get; set; }
/// <summary>
/// 货品信息
/// </summary>
public SkuInfo Sku { get; set; }
}
/// <summary>
///
/// </summary>
public class SkuInfo : Entity<int>
{
/// <summary>
/// 货品名称
/// </summary>
[MaxLength(32)]
public string SkuName { get; set; }
}
/// <summary>
///
/// </summary>
public class OrderPrice : Entity<int>
{
/// <summary>
/// 订单总金额
/// </summary>
public int TotalAmount { get; set; }
/// <summary>
/// 优惠总金额
/// </summary>
public int FreeAmount { get; set; }
}
/// <summary>
/// 购买者信息
/// </summary>
public class OrderBuyer : Entity<int>
{
/// <summary>
/// 姓名
/// </summary>
[MaxLength(16)]
public string Name { get; set; }
/// <summary>
/// 年龄
/// </summary>
public int Age { get; set; }
}
}
如上所示,我把他们全部放于XXX.Domain/levelmodels的文件夹的OrderDetail.cs中
结构如下
确认了字段的注释等之后,右键OrderDetail.cs这个文件,使用PasteBuidler构建代码
可以生成对应的Dto和AppService模块
如上图所示,会在这些地方生成对应的代码,自动生成的代码有些需要调整,我们进行针对性的调整下
我们以新增OrderDetail为例子,对OrderDetailAddDto调整如下
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using LevelEntity.Application.Contracts;
using PasteFormHelper;
namespace LevelEntity.levelmodels
{
///<summary>
///
///</summary>
public class OrderDetailAddDto
{
///<summary>
///标题
///</summary>
[MaxLength(128)]
public string Title { get; set; }
///<summary>
///封面图
///</summary>
[MaxLength(128)]
public string CoverImage { get; set; }
///<summary>
///包含产品 这里会自动构建组,因为是list和directsun
///</summary>
[PasteDirectsun]
public List<OrderProductUpdateDto> Products { get; set; }
///<summary>
///订单金额
///</summary>
[PasteDirectsun]
public OrderPriceUpdateDto Price { get; set; }
}
///<summary>
///
///</summary>
public class OrderDetailUpdateDto : OrderDetailAddDto
{
/// <summary>
///
/// </summary>
public int Id { get; set; }
/////<summary>
/////标题
/////</summary>
//[MaxLength(128)]
//public string Title { get; set; }
/////<summary>
/////封面图
/////</summary>
//[MaxLength(128)]
//public string CoverImage { get; set; }
/////<summary>
/////订单包含产品
/////</summary>
//public List<OrderProductDto> Products { get; set; }
/////<summary>
/////订单金额
/////</summary>
//public OrderPriceDto Price { get; set; }
}
///<summary>
///
///</summary>
public class OrderDetailDto : OrderDetailUpdateDto
{
/////<summary>
/////标题
/////</summary>
//[MaxLength(128)]
//public string Title { get; set; }
/////<summary>
/////封面图
/////</summary>
//[MaxLength(128)]
//public string CoverImage { get; set; }
/////<summary>
/////订单包含产品
/////</summary>
//public List<OrderProductDto> Products { get; set; }
/////<summary>
/////订单金额
/////</summary>
//public OrderPriceDto Price { get; set; }
}
///<summary>
///
///</summary>
public class OrderDetailListDto
{
/// <summary>
/// ID
/// </summary>
public int Id { get; set; }
///<summary>
///标题
///</summary>
[MaxLength(128)]
[PasteClass]
public string Title { get; set; }
///<summary>
///封面图
///</summary>
[MaxLength(128)]
[PasteClass]
public string CoverImage { get; set; }
///<summary>
///订单包含产品
///</summary>
[PasteHidden]
public List<OrderProductDto> Products { get; set; }
///<summary>
///订单金额
///</summary>
[PasteDisplay("totalAmount")]
[PasteFenToYuan]
public OrderPriceDto Price { get; set; }
}
///<summary>
/// 查询
///</summary>
public class InputQueryOrderDetail : InputSearchBase
{
/// <summary>
/// 货品名称
/// </summary>
public string sku_name { get; set; }
/// <summary>
/// 商品ID
/// </summary>
public int pro_id { get; set; }
}
}
注意看OrderDetailAddDto的这个信息
///<summary>
///包含产品 这里会自动构建组,因为是list和directsun
///</summary>
[PasteDirectsun]
public List<OrderProductUpdateDto> Products { get; set; }
///<summary>
///订单金额
///</summary>
[PasteDirectsun]
public OrderPriceUpdateDto Price { get; set; }
我偷懒了下,直接用UpdateDto,其实应该用AddDto的,也就是
///<summary>
///包含产品 这里会自动构建组,因为是list和directsun
///</summary>
[PasteDirectsun]
public List<OrderProductAddDto> Products { get; set; }
///<summary>
///订单金额
///</summary>
[PasteDirectsun]
public OrderPriceAddDto Price { get; set; }
不过实际中影响不大,因为我直接把对应的Id隐藏了
比如对应的OrderProductUpdateDto的代码调整如下
///<summary>
///
///</summary>
public class OrderProductUpdateDto
{
/// <summary>
///
/// </summary>
[PasteHidden]
public int Id { get; set; }
///<summary>
///商品ID
///</summary>
public int ProductId { get; set; }
///<summary>
///购买数量
///</summary>
public int BuyNum { get; set; }
///<summary>
///售价
///</summary>
public int ProductPrice { get; set; }
///<summary>
///货品信息
///</summary>
[PasteDirectsun]
public SkuInfoUpdateDto Sku { get; set; }
}
而SkuInfoUpdateDto的代码修改如下
///<summary>
///
///</summary>
public class SkuInfoUpdateDto
{
/// <summary>
///
/// </summary>
[PasteHidden]
public int Id { get; set; }
///<summary>
///货品名称
///</summary>
[MaxLength(32)]
public string SkuName { get; set; }
}
由上面的代码可知,主要是在子Object中加入了[PasteDirectSun]
这个其实就是特性directsun的内容,具体的可以查看
https://soft.pastecode.cn/doc/form/classfunc/directsun
(如果没有,就是我文档还没有升级哈!)
至于其他字段就是正常的字段了,不做其他说明,一起来看看如何实现新增和更新
开发过ABP框架项目的都知道,新增走的是AddDto,这里就是OrderDetailAddDto
然后我们看下PasteForm在对于这个新增的时候做了什么操作
一起看下XXX.Application/levelmodels的OrderDetailAppService.cs中的
ReadAddModel
代码如下
/// <summary>
/// 读取AddDto的数据模型
/// </summary>
/// <returns></returns>
[HttpGet]
[TypeFilter(typeof(RoleAttribute), Arguments = new object[] { "data", "add" })]
public VoloModelInfo ReadAddModel()
{
var dto = new OrderDetailAddDto();
dto.Products = new List<OrderProductUpdateDto> { new OrderProductUpdateDto() { Sku = new SkuInfoUpdateDto { } } };
var _model = PasteBuilderHelper.DynamicReadModelProperty<OrderDetailAddDto>(dto);
return _model;
}
上面代码中要注意,当设计子对象的时候,需要先new一个对象出来,不然UI中没法解析对象!
估计这个问题后续会改版,就是初始化对象的问题!
这样让返回的数据格式中包含了比如Products的内容,也就是UI知道ProductUpdateDto的格式信息,以及对应的SkuInfoUpdateDto的内容!
注意,我上面解析对象用的反射是PasteBuilderHelper.DynamicReadModelProperty
修改完成以上代码之后,我们要做一个add-migration操作
因为修改了数据库表结构等
add-migration init_database -Context SqliteDbContext
由于我本地测试使用的是Sqlite数据库
运行后,需要添加菜单
点击保存!
理论上是没有错误的,提示成功,然后刷新表格页面!
点击编辑看看数据是否一致!!!
这里数据是否一致,要看查询接口,也就是
/// <summary>
/// 读取UpdateDto的数据模型
/// </summary>
/// <returns></returns>
[HttpGet]
[TypeFilter(typeof(RoleAttribute), Arguments = new object[] { "data", "edit" })]
public async Task<VoloModelInfo> ReadUpdateModel(int id)
{
var _info = await _dbContext.OrderDetail.Where(x => x.Id == id).Include(x => x.Products).ThenInclude(op => op.Sku).Include(x => x.Price).AsNoTracking().FirstOrDefaultAsync();
if (_info == null || _info == default)
{
throw new PasteCodeException("查询的信息不存在,无法执行编辑操作!");
}
var dto = ObjectMapper.Map<OrderDetail, OrderDetailUpdateDto>(_info);
var _dataModel = PasteBuilderHelper.DynamicReadModelProperty<OrderDetailUpdateDto>(dto);
return _dataModel;
}
看代码,是不是把Product.sku也包含进来了!!!
上面已经例举了新增多层级表单和编辑多层级表单,那么接下来就是查询表格数据,就是按页查询数据,这里演示下包括子集的查询!
我们知道PasteForm框架的查询也是依托于Dto的,默认是InputQueryXXXX
本次这里的就是
///<summary>
/// 查询
///</summary>
public class InputQueryOrderDetail : InputSearchBase
{
/// <summary>
/// 货品名称
/// </summary>
public string sku_name { get; set; }
/// <summary>
/// 商品ID
/// </summary>
public int pro_id { get; set; }
}
在默认基础上我加了2个字段查询
然后看看使用的地方
/// <summary>
/// 获取
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[HttpGet]
public async Task<PagedResultDto<OrderDetailListDto>> Page([FromQuery] InputQueryOrderDetail input)
{
var _query = _dbContext.OrderDetail.Where(t => 1 == 1)
.Include(x => x.Products).ThenInclude(op => op.Sku)
.Include(x => x.Price)
.WhereIf(!string.IsNullOrEmpty(input.word), x => x.Title.Contains(input.word))
.WhereIf(input.pro_id != 0, x => x.Products.Any(y => y.ProductId == input.pro_id))
.WhereIf(!string.IsNullOrEmpty(input.sku_name), x => x.Products.Any(y => y.Sku != null && y.Sku.SkuName.Contains(input.sku_name)));
var _pagedto = new PagedResultDto<OrderDetailListDto>();
if (input.page == 1)
{
_pagedto.TotalCount = await _query.CountAsync();
}
var dataList = await _query
.OrderByDescending(x => x.Id)
.Page(input.page, input.size)
.AsNoTracking()
.ToListAsync();
if (dataList == null || dataList.Count == 0)
{
throw new PasteCodeException("没有查询到数据", 204);
}
var temList = ObjectMapper.Map<List<OrderDetail>, List<OrderDetailListDto>>(dataList);
_pagedto.Items = temList;
return _pagedto;
}
一起来看看表格中是怎样的
如上所示,多了2个查询项,就是我们在InputQueryOrderDetail中添加的,所以说PasteForm添加查询就是这么简单!
测试下是否会按照输入值查询
对于前端来说就是F12看看提交的查询数据
对于后端来说,就是接受到查询条件,然后执行查询
我这测试OK!
由以上信息得知,对于多层级的Entity或者有集合的Entity的表单,对于PasteForm框架来说,可以在一个表单中完成!
需要注意的点是关于对象的实例信息,也就是进行反射Model的时候,那些对象的字段需要进行new一个默认的出来,反射才能获得对应的信息!
对于Collection来说,UI会支持+-的功能!
上面回顾其实还是有可圈可点的地方
1.对于字段是Object的,其实可以在解反射的时候自己new一个出来,然后解析!
2.其实上面有一个冲突的地方,就是directsun的地方不能用group特性,这样会覆盖,查看前端代码可知,group要支持+-的功能,需要是多层级模式!
3.?是否支持->obj->items->obj->items的模式呢?等待你的测试!!!