『贴代码』
我的博文
个人作品
优选问答
「搜索」
【本期话题】更多
三人寄语更多
能通过内网IP访问的,尽量不要使用域名访问!
点赞:1
抛开需求谈架构是无意义的事情!
点赞:0
测试没问题的不一定没问题,测试有问题的那肯定有问题!
点赞:2
往往会为了一个项目,搭建适合他专属的脚手架!
点赞:1
慎用redis的同步我的意见是redis都走异步!!!
点赞:0
对于答案来说,更重要的是找到答案的这个过程而不是答案本身!
点赞:0
由于时间的问题,我们往往会给自己埋坑,等着后续或者下一任来填!
点赞:0
没有最好的语言,只有更合适的语言!
点赞:0
时好时坏的结果,往往是多线程引起的逻辑混乱导致的!
点赞:0
抛开需求讲架构,和纸上谈兵无差!
点赞:1
PasteForm框架开发之Entity多级嵌套的表单的实现
尘埃 2025-04-14 10 0 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的模式呢?等待你的测试!!!

评论列表
尘埃
0 10 0
快捷注册
热门推荐更多
PasteTalk
是在线客服系统更是在线营销系统,引入特有的页面话序机制,能够针对不同访客实现丰富的营销话语,提高开发效率!引入分词功能提升关键字命中概率,提高服务质量!;
最新动态
  • 62.****.9 正在查看 PasteSpider之私有仓库的创建和使用 !
  • 220.****.114 正在查看 记PasteSpider部署工具的Windows.IIS版本开发过程之草稿-动态表单(2) !
  • 99.****.192 正在查看 PasteSpider之占位符,宏,对象属性遍历的说明 !
  • 99.****.192 正在查看 PasteSpider之占位符,宏,对象属性遍历的说明 !
  • 41.****.51 正在查看 PasteSpider中关于Nginx的配置,安装PasteSpider之后查阅 !
  • 41.****.51 正在查看 PasteSpider中关于Nginx的配置,安装PasteSpider之后查阅 !
  • 158.****.213 正在查看 PasteSpider中如何同步文件到服务器包含PasteSpiderFile的下载 !
  • 158.****.213 正在查看 PasteSpider中如何同步文件到服务器包含PasteSpiderFile的下载 !
  • 197.****.106 正在查看 记PasteSpider部署工具的Windows.IIS版本开发过程之草稿-效果展示(4) !
  • 197.****.106 正在查看 记PasteSpider部署工具的Windows.IIS版本开发过程之草稿-效果展示(4) !
  • 35.****.200 正在查看 Serilog在appsettings.json中的配置 !
欢迎加入QQ讨论群 296245685 [PasteSpider]介绍 [PasteForm]介绍 @2022-2023 PasteCode.cn 版权所有 ICP证 闽ICP备2021013869号-2