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

如何使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 03:35:31