如何使用Continuation Token在Cosmos DB中跳转到最后一页
Angular Material 实现 Cosmos DB 分页跳转到最后一页
后端C# API改造
由于Cosmos DB的Continuation Token分页是基于查询上下文的,没法直接通过计算偏移量跳转到最后一页,得给后端加个支持获取最后一页数据的逻辑,这里提供两种可行方案:
方案1:总记录数+按页跳转(适合数据量不大的场景)
如果数据量可控,可以先查总记录数,再计算最后一页的页码,然后遍历到对应位置返回数据:
// 获取总记录数 public async Task<int> GetTotalCountAsync(string queryText) { var query = _container.GetItemQueryIterator<MyModel>( new QueryDefinition(queryText), requestOptions: new QueryRequestOptions { MaxItemCount = -1 }); int totalCount = 0; while (query.HasMoreResults) { var response = await query.ReadNextAsync(); totalCount += response.Count; } return totalCount; } // 获取指定页的数据(包含最后一页) public async Task<(List<MyModel> Items, string ContinuationToken)> GetPageAsync(string queryText, int pageSize, int pageNumber) { if (pageNumber == 1) { var query = _container.GetItemQueryIterator<MyModel>( new QueryDefinition(queryText), requestOptions: new QueryRequestOptions { MaxItemCount = pageSize }); var response = await query.ReadNextAsync(); return (response.ToList(), response.ContinuationToken); } // 遍历到目标页 string continuationToken = null; List<MyModel> items = new List<MyModel>(); for (int i = 1; i <= pageNumber; i++) { var query = _container.GetItemQueryIterator<MyModel>( new QueryDefinition(queryText), continuationToken: continuationToken, requestOptions: new QueryRequestOptions { MaxItemCount = pageSize }); var response = await query.ReadNextAsync(); continuationToken = response.ContinuationToken; if (i == pageNumber) { items = response.ToList(); } } return (items, continuationToken); }
方案2:直接返回最后一页(适合数据量大但不需要总计数的场景)
循环读取所有页直到最后一页,直接返回最后一页的内容和对应的Continuation Token,避免计算总条数的开销:
public async Task<(List<MyModel> Items, string ContinuationToken)> GetLastPageAsync(string queryText, int pageSize) { string continuationToken = null; List<MyModel> lastPageItems = new List<MyModel>(); while (true) { var query = _container.GetItemQueryIterator<MyModel>( new QueryDefinition(queryText), continuationToken: continuationToken, requestOptions: new QueryRequestOptions { MaxItemCount = pageSize }); var response = await query.ReadNextAsync(); lastPageItems = response.ToList(); continuationToken = response.ContinuationToken; if (string.IsNullOrEmpty(continuationToken)) { break; } } return (lastPageItems, continuationToken); }
前端Angular Material实现
1. 配置MatPaginator
在组件模板里设置分页器,监听page事件来处理分页跳转:
<mat-paginator [length]="totalCount" [pageSize]="pageSize" [pageSizeOptions]="[10, 20, 50]" (page)="onPageChange($event)"> </mat-paginator>
2. 组件逻辑处理
在组件里判断用户是否点击最后一页,调用对应的后端接口:
import { Component, ViewChild } from '@angular/core'; import { MatPaginator, PageEvent } from '@angular/material/paginator'; import { DataService } from './data.service'; @Component({ selector: 'app-data-list', templateUrl: './data-list.component.html', styleUrls: ['./data-list.component.css'] }) export class DataListComponent { @ViewChild(MatPaginator) paginator!: MatPaginator; dataItems: any[] = []; totalCount = 0; pageSize = 10; private currentContinuationToken: string | null = null; constructor(private dataService: DataService) {} ngOnInit() { this.loadFirstPage(); } loadFirstPage() { this.dataService.getPage(1, this.pageSize).subscribe(response => { this.dataItems = response.items; this.currentContinuationToken = response.continuationToken; }); // 加载总记录数(方案1需要) this.dataService.getTotalCount().subscribe(count => { this.totalCount = count; }); } onPageChange(event: PageEvent) { const targetPage = event.pageIndex + 1; // MatPaginator的pageIndex从0开始 const totalPages = Math.ceil(this.totalCount / this.pageSize); if (targetPage === totalPages) { // 点击最后一页,调用专用接口 this.dataService.getLastPage(this.pageSize).subscribe(response => { this.dataItems = response.items; this.currentContinuationToken = response.continuationToken; }); } else { // 普通分页请求 this.dataService.getPage(targetPage, this.pageSize).subscribe(response => { this.dataItems = response.items; this.currentContinuationToken = response.continuationToken; }); } } }
3. 数据服务封装
把后端API调用封装成服务:
import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class DataService { private apiUrl = 'https://your-api-endpoint.com/api/data'; constructor(private http: HttpClient) {} getPage(pageNumber: number, pageSize: number): Observable<any> { return this.http.get(`${this.apiUrl}/page?pageNumber=${pageNumber}&pageSize=${pageSize}`); } getLastPage(pageSize: number): Observable<any> { return this.http.get(`${this.apiUrl}/last-page?pageSize=${pageSize}`); } getTotalCount(): Observable<number> { return this.http.get<number>(`${this.apiUrl}/total-count`); } }
注意事项
- 如果数据量极大,方案1的遍历到目标页会有性能瓶颈,优先用方案2,或者考虑用Cosmos DB的
Change Feed维护一个实时的总计数表,避免每次全量查询统计条数。 - 因为Cosmos DB数据可能实时更新,总计数和最后一页内容可能有短暂不一致,这是分页场景的正常情况。
- 前端要正确设置MatPaginator的
length属性为总记录数,分页器才会显示最后一页的按钮。
内容的提问来源于stack exchange,提问作者VENKAT SUBRAMANIYAM
相关产品推荐
相关产品推荐

