You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ASP.NET Core 3.1 OData序列化NetTopologySuite几何类型循环报错问题

问题描述

我正在ASP.NET Core中搭配使用OData和NetTopologySuite,当前使用的PostgreSQL数据库包含Geometry类型的字段,通过Npgsql发起的请求运行正常,但OData的序列化结果完全不符合预期,这个问题已经卡了好几天了,恳请大家帮忙。

看起来OData出现了循环序列化的问题,每个返回项按理仅应返回一组XYZ坐标,但实际返回结果出现了无限嵌套的情况,如下所示:

,"shape":{"X":-9.129147,"Y":41.133908999999996,"Z":"NaN","M":"NaN","SRID":4326,"Coordinates":[{"X":-9.129147,"Y":41.133908999999996,"Z":"NaN","M":"NaN","CoordinateValue":{"X":-9.129147,"Y":41.133908999999996,"Z":"NaN","M":"NaN","CoordinateValue":{"X":-9.129147,"Y":41.133908999999996,"Z":"NaN","M":"NaN","CoordinateValue":...无限嵌套...

相关代码

模型(Model)

public class Model_bs_age_depth {
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.None)]
    public string age_depth_id { get; set; }
    public string site_id { get; set; }
    public string site_type { get; set; }
    public string site_name { get; set; }
    public decimal? latitude { get; set; }
    public decimal? longitude { get; set; }
    public string country { get; set; }
    public NetTopologySuite.Geometries.Point shape { get; set; }
}

控制器(Controller)

public async Task<ActionResult<IEnumerable<Model_bs_age_depth>>> Getbs_age_depth() {
    var optionsBuilder = new DbContextOptionsBuilder<StorageBroker>()
        .UseNpgsql(_connectionString, x => 
            x.UseNetTopologySuite())
        .UseLoggerFactory(factoryLogger)
        .Options;
    StorageBroker context = new StorageBroker(optionsBuilder);
    var query = await context.bs_age_depth.ToListAsync();
    if (query == null) {
        // 省略空处理逻辑
    }
    return query;
}

Storage Broker

public class StorageBroker : DbContext {
    private static readonly ILoggerFactory factoryLogger = LoggerFactory.Create(builder => builder.AddConsole())!;
    public StorageBroker(DbContextOptions<StorageBroker> options)
        : base(options) {
    }
    public DbSet<Model_bs_age_depth> bs_age_depth { get; set; }
    public DbSet<bs_biozone> bs_biozone { get; set; }
    public DbSet<DBModel> cd_site { get; set; }
}

错误日志

Entity Framework Core 3.1.20 initialized 'StorageBroker' using provider 'Npgsql.EntityFrameworkCore.PostgreSQL' with options: using NetTopologySuite
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (39ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
SELECT b.age_depth_id, b.age_epoch_top, b.age_ma, b.age_period_base, b.age_period_top, b.age_stage_base, b.basin_id, b.basin_name, b.boundary, b.country, b.depth, b.depth_f, b.field_id, b.field_name, b.interpretation_version, b.latitude, b.lithostrat_formation, b.lithostrat_group, b.lithostrat_member, b.location, b.longitude, b.product_id, b.product_title, b.product_year, b.province_state, b.region_id, b.region_name, b.shape, b.site_id, b.site_name, b.site_type, b.table_meta_id, b.tvd_base, b.tvd_base_f, b.tvd_top, b.tvd_top_f
FROM bs_age_depth AS b
fail: Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware[1]
An unhandled exception has occurred while executing the request.
Microsoft.OData.ODataException: The depth limit for entries in nested expanded navigation links was reached. The number of nested expanded entries cannot exceed 100.


解决方案

这个问题的根本原因是NetTopologySuite的Point类型内置了自引用属性CoordinateValue,OData默认序列化时会遍历类型的所有公共属性,触发无限递归,最终超出嵌套深度限制报错。你可以根据自己的需求选以下任意一种方案解决:

方案1:使用DTO做字段投影(最简单,兼容性最高)

不要直接把数据库实体返回给OData,额外定义一个传输用的DTO类,只保留你需要暴露的字段,避免直接序列化Point对象:

public class Model_bs_age_depth_Dto {
    public string age_depth_id { get; set; }
    public string site_id { get; set; }
    public string site_type { get; set; }
    public string site_name { get; set; }
    public decimal? latitude { get; set; }
    public decimal? longitude { get; set; }
    public string country { get; set; }
    // 直接暴露你需要的坐标字段,不返回完整Point对象
    public double? ShapeX { get; set; }
    public double? ShapeY { get; set; }
    public int? ShapeSRID { get; set; }
}

查询时做投影转换:

var query = await context.bs_age_depth
    .Select(p => new Model_bs_age_depth_Dto {
        age_depth_id = p.age_depth_id,
        site_id = p.site_id,
        // 其他字段逐一赋值,也可以用AutoMapper自动映射
        ShapeX = p.shape.X,
        ShapeY = p.shape.Y,
        ShapeSRID = p.shape.SRID
    }).ToListAsync();
return query;

方案2:配置OData EDM模型忽略自引用属性

如果你不想额外维护DTO,可以在构建OData EDM模型时,手动忽略Point类型的自引用属性,避免OData序列化时遍历到循环字段:

// 在你的OData服务配置代码中,构建EDM模型时添加以下逻辑
var modelBuilder = new ODataConventionModelBuilder();
modelBuilder.EntitySet<Model_bs_age_depth>("bs_age_depth");
// 找到Point类型的配置,忽略会触发循环的属性
var pointTypeConfig = modelBuilder.StructuralTypes
    .First(t => t.ClrType == typeof(NetTopologySuite.Geometries.Point));
pointTypeConfig.Ignore(p => p.CoordinateValue);
pointTypeConfig.Ignore(p => p.Coordinates);
pointTypeConfig.Ignore(p => p.Geometries);
// 用配置好的模型初始化OData
var edmModel = modelBuilder.GetEdmModel();

方案3:配置OData空间类型映射

如果你需要返回标准的OData空间格式,可以直接安装Microsoft.AspNetCore.OData.Spatial包启用空间类型支持,OData会自动将NetTopologySuite的Geometry类型映射到内置的空间类型,按照标准规则序列化,不会遍历内部属性。


内容的提问来源于stack exchange,提问作者Mike

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.28 17:36:04