『贴代码』
技术分享
精选作品
优选问答
成长笔记
PasteForm框架开发之Entity多级嵌套的表单的实现
尘埃 2025-04-14 1786 73 0
如果你的Entity是多层级的,是否也支持PasteForm框架呢?一起来看看PasteForm框架是如何应对多层级的Entity的!

更多特性PasteForm的介绍,请前往PasteForm操作文档查看 PasteForm操作文档与介绍


需求假设

假如有这么一个需求,就是订单表,包含了多级的信息,比如这个订单包含了哪些商品,又有哪些货品信息等,以下是一个概览,模拟的是实际的一些层级关系

Entity定义

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中
结构如下
图片alt
确认了字段的注释等之后,右键OrderDetail.cs这个文件,使用PasteBuidler构建代码
可以生成对应的Dto和AppService模块
图片alt
如上图所示,会在这些地方生成对应的代码,自动生成的代码有些需要调整,我们进行针对性的调整下

调整Dto

我们以新增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; }

    }

Dto解读

由上面的代码可知,主要是在子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数据库
运行后,需要添加菜单

  • pasteform/index.html?path=orderDetail
    然后刷新页面后,可以点击新增,可以看到如下
    图片alt
    发现没有,字段已经对应上了,上面还有一个分组模块,叫包含产品
    这个包含产品后面还有+ -,这个对应的就是Products
    !!!注意,如果都点击-,一个都没有保留,那就需要刷新页面重新加载了!
    我们多点击+几次,然后输入信息,大概如下

表单效果

图片alt
点击保存!
理论上是没有错误的,提示成功,然后刷新表格页面!
点击编辑看看数据是否一致!!!
这里数据是否一致,要看查询接口,也就是

        /// <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;
        }

一起来看看表格中是怎样的

查询区域

图片alt
如上所示,多了2个查询项,就是我们在InputQueryOrderDetail中添加的,所以说PasteForm添加查询就是这么简单!
测试下是否会按照输入值查询
对于前端来说就是F12看看提交的查询数据
对于后端来说,就是接受到查询条件,然后执行查询
我这测试OK!

功能回顾

由以上信息得知,对于多层级的Entity或者有集合的Entity的表单,对于PasteForm框架来说,可以在一个表单中完成!
需要注意的点是关于对象的实例信息,也就是进行反射Model的时候,那些对象的字段需要进行new一个默认的出来,反射才能获得对应的信息!
对于Collection来说,UI会支持+-的功能!

上面回顾其实还是有可圈可点的地方
1.对于字段是Object的,其实可以在解反射的时候自己new一个出来,然后解析!
2.其实上面有一个冲突的地方,就是directsun的地方不能用group特性,这样会覆盖,查看前端代码可知,group要支持+-的功能,需要是多层级模式!
3.?是否支持->obj->items->obj->items的模式呢?等待你的测试!!!

评论列表
尘埃
73 1786 0
快捷注册
热门推荐更多
PasteBuilder
;
用户问答更多
07月份版本的内存占用比以前大了,也没有泄漏,啥情况?以前200MB,这个版本能到300MB
文档中的组织归属有些问题,啥时候看看,更新更新!
  • 已经升级了,主要是left join的查询的时候没有过滤,其实这个问题,多租户也是一样的!

PasteSpider的集群方式,我还是没太懂,啥时候出一个专题来讲解讲解?
  • b不难吧,就几个地址!

最新动态
  • 216.****.99 正在查看 文章列表页 !
  • 216.****.99 正在查看 Document:spider !
  • 216.****.99 正在查看 主页talk !
  • 54.****.42 正在查看 正在查看笔记页面 !
  • 176.****.30 正在查看 文章列表页 !
  • 216.****.99 正在查看 PasteSpider更新摘要,持续更新 !
  • 216.****.99 正在查看 关于PasteSpiderV2版本升级V5版本后,部署的服务会走一遍扩容的说明 !
  • 216.****.99 正在查看 PasteSpider内各种宏的规则和定义说明 !
  • 216.****.99 正在查看 PasteSpider之私有仓库的创建和使用 !
  • 216.****.99 正在查看 文章列表页 !
  • 216.****.99 正在查看 PasteSpider的V5正式版发布啦!(202504月版),更新说明一览 !
欢迎加入QQ讨论群 296245685 [PasteSpider]介绍 [PasteForm]介绍 @2022-2023 PasteCode.cn 版权所有 ICP证 闽ICP备2021013869号-2