【Abp VNext】实战入门(十四):【1】应用层 —— 对象间循环递归引用,导致Json序列化异常


一、前言

应用层涉及到从数据库提取数据记录,转换成对应的DTO,然后框架自动Json序列化,返回前端;

在Domain领域层创建的领域对象DO通常带有关系绑定,1:1 或者1:N , 自然Application应用层的数据传输对象 DTO也会带有与之对应关系的DTO;

当查询A记录 想要把A记录关联的B记录也一并查询并返回的时候,记录查询虽然成功,但是在进行Json序列化的时候会提示要转换的对象出现循环递归引用,且超过最大32层循环嵌套深度,直接报JsonException异常。

二、对象递归引用Json序列化异常案例

1、领域层对象关系:

//巡航类 Cruise.cs
public class Cruise : Entity<int>, IHasCreationTime, IHasModificationTime
    {      
        /// <summary>
        /// 区域巡检点列表:1条路径多个巡检点
        /// </summary>
        public virtual List<Cruise_Points> Cruise_Points { get; set; }      
    }
    
 //巡检点:Cruise_Points.cs
 public class Cruise_Points : Entity<int>, IHasModificationTime, IHasCreationTime
    {
        /// <summary>
        /// 巡检点对应的巡检路径ID 数据库自动生成主外键
        /// </summary>
        public virtual int Cruise_Id { get; set; }
        [ForeignKey("Cruise_Id")]
        public virtual Cruise Cruise { get; set; }       
    }

2、应用层对象关系:

//CruiseDto.cs:巡检路径
 public class CruiseDto : EntityDto<int>
    {      
        /// <summary>
        /// 多个巡检点
        /// </summary>
        public List<CruisePointDto> Cruise_Points { get; set; }  
    }

//CruisePointDto.cs:巡检点位
public class CruisePointDto : EntityDto<int>
    {
        /// <summary>
        /// 关联的巡检路径
        /// </summary>
        public virtual int Cruise_Id { get; set; }
        public CruiseDto Cruise { get; set; }
    }

3.1、关联查询方式:AbpVnext

public class CruiseAppService : CrudAppService<Cruise, CruiseDto, int, CruisePagedResultRequestDto, CreateCruiseDto, CruiseDto>, ICruiseAppService
    {
        public CruiseAppService(IRepository<Cruise, int> repository) : base(repository)
        {
        }
      
        /// <summary>
        /// 重新翻页查询
        /// </summary>
        protected override IQueryable<Device_Warns> CreateFilteredQuery(CruisePagedResultRequestDto input)
        {
        	//查询巡检路径 Cruise 的时候 把对应的多个巡检点位也查询出来
            return this.ReadOnlyRepository.WithDetails(p=>p.Cruise_Point)
                .WhereIf(p=>p.id=20);
        }  
    }
}
//注意代码中的 this.ReadOnlyRepository.WithDetails

3.2、关联查询方式:Abp

//类似这种写法
 return await _context.Cruise.Include(P.Cruise_Point).FirstOrDefaultAsync(p=>p.id=20);

4、查询结果Json序列化异常:

提示递归应用嵌套,超过Json序列化最大嵌套深度;

System.Text.Json.JsonException: A possible object cycle was detected which is not supported. This can either be due to a cycle or if the object depth is larger than the maximum allowed depth of 32.
at System.Text.Json.ThrowHelper.ThrowInvalidOperationException_SerializerCycleDetected(Int32 maxDepth)
at System.Text.Json.JsonSerializer.Write(Utf8JsonWriter writer, Int32 originalWriterDepth, Int32 flushThreshold, JsonSerializerOptions options, WriteStack& state)
at System.Text.Json.JsonSerializer.WriteAsyncCore(Stream utf8Json, Object value, Type inputType, JsonSerializerOptions options, CancellationToken cancellationToken)
at Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonOutputFormatter.WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
at Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonOutputFormatter.WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|29_0[TFilter,TFilterAsync](ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResultExecutedContextSealed context)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext[TFilter,TFilterAsync](State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeResultFilters()
— End of stack trace from previous location where exception was thrown —
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|24_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
at Microsoft.AspNetCore.Routing.EndpointMiddleware.g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

5、异常分析:

最终CruiseDto中包含 List ,但是每个CruisePointDto中又包含与之对应的 CruiseDto 所以造成了巡航嵌套引用,导致Json序列化失败;

6、解决方案1:

在应用层 GasMonitoringApplicationAutoMapperProfile.cs 文件中添加实体类转换配置

public class GasMonitoringApplicationAutoMapperProfile : Profile
    {
        public GasMonitoringApplicationAutoMapperProfile()
        {
            CreateMap<Cruise, CruiseDto>()
                .ForMember(x=>x.Cruise_Points,opt=>opt.Ignore());
            CreateMap<CruiseDto, Cruise>(MemberList.Source); //以Dto数据为准进行转换
            CreateMap<CreateCruiseDto, Cruise>(MemberList.Source); //只检测Dto中属性 Validate
        }
    }
}

.ForMember(x=>x.Cruise_Points,opt=>opt.Ignore()); 可有效解决循环递归引用问题
但是也会存在问题 在Cruise查询的时候 就无法关联查询出对应的 Cruise_Points 列表了:不过这种查询需求一般不会有

7、解决方案2:

解决方案6中的方法比较麻烦,简单的方是 在 CruisePointDto中取消 CruiseDto 对象,这样从数据库查询 CruisePoint=》CruisePointDto转换就没有问题,也不会存在嵌套引用;
从实际业务情况来讲 通常不会出现独立使用CruisePointDto的情况,CruisePointDto更多是依赖于CruiseDto关联查询出现,所以基本可以满足业务需求;

//CruiseDto.cs:巡检路径
 public class CruiseDto : EntityDto<int>
    {      
        /// <summary>
        /// 多个巡检点
        /// </summary>
        public List<CruisePointDto> Cruise_Points { get; set; }  
    }

//CruisePointDto.cs:巡检点位
public class CruisePointDto : EntityDto<int>
    {
        /// <summary>
        /// 关联的巡检路径
        /// </summary>
        public virtual int Cruise_Id { get; set; }
        //public CruiseDto Cruise { get; set; } 注释掉这层声明**********************************
    }

8、解决方案3:

如果希望保留上面的关系,还可以这样做。

//CruiseDto.cs:巡检路径
 public class CruiseDto : EntityDto<int>
    {      
        /// <summary>
        /// 多个巡检点
        /// </summary>
        public List<CruisePointDto> Cruise_Points { get; set; }  
    }

//CruisePointDto.cs:巡检点位
public class CruisePointDto : EntityDto<int>
    {
        /// <summary>
        /// 关联的巡检路径
        /// </summary>
        public virtual int Cruise_Id { get; set; }
        //添加[JsonIgnore] 这样查询 及后台使用不会出现问题 
        //必须引用:using System.Text.Json.Serialization;
        //引用异常:using NewtonsoftJson.Json;无效************************
        [JsonIgnore] 
        public CruiseDto Cruise { get; set; }
    }

三、总结

网上还有引入Microsoft.AspNetCore.Mvc.NewtonsoftJson 包,然后Startup中添加 Json序列化忽略递归引用,经测试无效;

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值