.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基于主键的高性能分页机制,配置与使用步骤:
- 在OData服务配置中启用
SkipToken()(见第1节配置代码)。 - 确保实体类标注
[Key]属性(EDM模型会自动识别主键)。 - 设置
PageSize后,接口返回的@odata.nextLink会包含$skiptoken参数,示例:
@odata.nextLink": "http://localhost:5000/odata/Modules?$skiptoken=X'0000000000000C41'"
- 前端直接请求该链接即可获取下一页数据,无需手动计算
$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
相关产品推荐
相关产品推荐

