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

.NET 6 WebAPI OData v4分页异常:缺失总条数与下页链接

.NET 6 OData 8.0.10 集成问题解决方案

1. 恢复@odata.context、$count及分页链接

服务配置修正(Program.cs)

确保OData服务启用元数据、计数和分页支持,正确绑定EDM模型与路由前缀:

builder.Services.AddControllers().AddOData(options =>
{
    // 启用所需OData特性,包含SkipToken用于分页令牌
    options.Select().Filter().OrderBy().Count().SkipToken().SetMaxTop(100);
    // 注册OData路由组件,指定前缀与EDM模型
    options.AddRouteComponents("odata", GetEdmModel());
});

// 构建EDM模型示例
private static IEdmModel GetEdmModel()
{
    ODataConventionModelBuilder modelBuilder = new();
    // 注册实体集,名称需与控制器对应
    modelBuilder.EntitySet<Module>("Modules");
    return modelBuilder.GetEdmModel();
}

控制器与接口修正

控制器继承ODataController,使用[ODataRoute]确保走OData路由管道:

public class ModulesController : ODataController
{
    private readonly YourDbContext _dbContext;

    public ModulesController(YourDbContext dbContext)
    {
        _dbContext = dbContext;
    }

    // 启用查询与分页,PageSize可根据需求调整
    [EnableQuery(PageSize = 5)]
    // 路由对应EDM模型中的实体集名称
    [ODataRoute("Modules")]
    public IQueryable<Module> GetModuleFilter()
    {
        return _dbContext.Modules.AsQueryable();
    }
}

调用时需通过OData前缀访问(如/odata/Modules?$count=true),此时响应会自动包含@odata.context、@odata.count和@odata.nextLink。

2. 允许前端动态设置PageSize

方案1:使用OData原生$top参数

前端直接通过$top=N指定每页条数,结合$skip实现分页,接口无需硬编码PageSize:

[EnableQuery(MaxTop = 100)] // 限制最大每页条数,避免性能问题
[ODataRoute("Modules")]
public IQueryable<Module> GetModuleFilter()
{
    return _dbContext.Modules.AsQueryable();
}

请求示例:/odata/Modules?$count=true&$top=10&$skip=20

方案2:自定义PageSize参数

若需前端通过非OData参数指定,可手动处理分页并返回包含总条数的包装类:

// 分页结果包装类
public class PagedResult<T>
{
    public int TotalCount { get; set; }
    public IEnumerable<T> Data { get; set; }
}

[HttpGet("custom/Modules")]
public async Task<IActionResult> GetModuleFilter(int pageSize = 5, int pageIndex = 1)
{
    var query = _dbContext.Modules.AsQueryable();
    var totalCount = await query.CountAsync();
    var data = await query.Skip((pageIndex - 1) * pageSize).Take(pageSize).ToListAsync();
    
    return Ok(new PagedResult<Module> { TotalCount = totalCount, Data = data });
}

注意:此方案为自定义接口,无法自动生成OData上下文与链接,需自行维护分页逻辑。

3. $skiptoken用法

$skiptoken是OData基于主键的高性能分页机制,配置与使用步骤:

  1. 在OData服务配置中启用SkipToken()(见第1节配置代码)。
  2. 确保实体类标注[Key]属性(EDM模型会自动识别主键)。
  3. 设置PageSize后,接口返回的@odata.nextLink会包含$skiptoken参数,示例:

@odata.nextLink": "http://localhost:5000/odata/Modules?$skiptoken=X'0000000000000C41'"

  1. 前端直接请求该链接即可获取下一页数据,无需手动计算$skip值。

4. 配置多个OData端点

通过多次调用AddRouteComponents注册不同前缀与模型的OData端点:

builder.Services.AddControllers().AddOData(options =>
{
    // 第一个端点:v1版本路由
    options.Select().Filter().OrderBy().Count().SkipToken()
           .AddRouteComponents("odata/v1", GetV1EdmModel());
    
    // 第二个端点:v2版本路由
    options.Select().Filter().OrderBy().Count().SkipToken()
           .AddRouteComponents("odata/v2", GetV2EdmModel());
});

// 不同版本的EDM模型
private static IEdmModel GetV1EdmModel()
{
    var builder = new ODataConventionModelBuilder();
    builder.EntitySet<ModuleV1>("Modules");
    return builder.GetEdmModel();
}

private static IEdmModel GetV2EdmModel()
{
    var builder = new ODataConventionModelBuilder();
    builder.EntitySet<ModuleV2>("Modules");
    return builder.GetEdmModel();
}

对应控制器需匹配各自路由前缀:

[ODataRoutePrefix("odata/v1/Modules")]
public class ModulesV1Controller : ODataController { /* 实现逻辑 */ }

[ODataRoutePrefix("odata/v2/Modules")]
public class ModulesV2Controller : ODataController { /* 实现逻辑 */ }

5. 自定义路径保持OData上下文生效

若需使用自定义路径(如/api/modules/list),需将路径整合到OData路由模板中:

[EnableQuery]
// 自定义OData路由路径
[ODataRoute("api/modules/list")]
public IQueryable<Module> GetModuleFilter()
{
    return _dbContext.Modules.AsQueryable();
}

也可采用默认前缀+自定义路径的形式(如/odata/api/modules/list),确保请求走OData管道。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 05:06:25