.NET AWS SDK中AsyncSearch类访问DynamoDB分页令牌的问题
问题描述
我正在研究AWS SDK for .NET中的DynamoDB操作,想用AsyncSearch<T>类执行表查询并实现分页功能。根据SDK文档,分页令牌存储在底层的Search对象中,该对象通过AsyncSearch<T>的DocumentSearch属性访问,且会在调用GetNextSetAsync()后被设置。但问题是DocumentSearch是私有属性,无法直接访问。
我尝试包装AsyncSearch<T>来暴露分页令牌,但因DocumentSearch私有,代码无法编译:
public interface IAsyncSearch<T> { bool IsDone { get; } string PaginationToken { get; } Dictionary<string, AttributeValue> NextKey { get; } Task<List<T>> GetRemainingAsync(CancellationToken token = default); Task<List<T>> GetNextSetAsync(CancellationToken token = default); } public class CustomAsyncSearch<T> : IAsyncSearch<T> { private readonly AsyncSearch<T> _search; public CustomAsyncSearch(AsyncSearch<T> search) { _search = search; } async Task<List<T>> IAsyncSearch<T>.GetRemainingAsync(CancellationToken token) { return await _search.GetRemainingAsync(token); } async Task<List<T>> IAsyncSearch<T>.GetNextSetAsync(CancellationToken token) { return await _search.GetNextSetAsync(token); } public bool IsDone => _search.IsDone; public string PaginationToken => _search.DocumentSearch.PaginationToken?.ToString(); public Dictionary<string, AttributeValue> NextKey => _search.DocumentSearch.NextKey; }
附AWS SDK中AsyncSearch<T>的源码:
namespace Amazon.DynamoDBv2.DataModel; public class AsyncSearch<T> { private Search DocumentSearch { get; set; } private DynamoDBContext SourceContext { get; set; } private DynamoDBFlatConfig Config { get; set; } public bool IsDone => DocumentSearch.IsDone; internal AsyncSearch(DynamoDBContext source, DynamoDBContext.ContextSearch contextSearch) { SourceContext = source; DocumentSearch = contextSearch.Search; Config = contextSearch.FlatConfig; } public async Task<List<T>> GetNextSetAsync(CancellationToken cancellationToken = default(CancellationToken)) { List<Document> documents = await DocumentSearch.GetNextSetHelperAsync(cancellationToken).ConfigureAwait(false); return SourceContext.FromDocumentsHelper<T>(documents, Config).ToList(); } public async Task<List<T>> GetRemainingAsync(CancellationToken cancellationToken = default(CancellationToken)) { List<Document> documents = await DocumentSearch.GetRemainingHelperAsync(cancellationToken).ConfigureAwait(false); return SourceContext.FromDocumentsHelper<T>(documents, Config).ToList(); } }
我需要在不修改AsyncSearch<T>内部结构的前提下,获取分页令牌,或者找到正确实现分页的方法。
可行解决方案
方案1:反射访问私有DocumentSearch属性
通过反射获取AsyncSearch<T>的私有DocumentSearch属性,进而提取分页令牌和NextKey。修改CustomAsyncSearch<T>类如下:
public class CustomAsyncSearch<T> : IAsyncSearch<T> { private readonly AsyncSearch<T> _search; private readonly Search _documentSearch; public CustomAsyncSearch(AsyncSearch<T> search) { _search = search; // 反射获取私有DocumentSearch属性 var documentSearchProperty = typeof(AsyncSearch<T>).GetProperty( "DocumentSearch", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); _documentSearch = documentSearchProperty?.GetValue(search) as Search; } public async Task<List<T>> GetRemainingAsync(CancellationToken token) { return await _search.GetRemainingAsync(token); } public async Task<List<T>> GetNextSetAsync(CancellationToken token) { var result = await _search.GetNextSetAsync(token); // 调用GetNextSetAsync后,DocumentSearch的分页令牌会自动更新 return result; } public bool IsDone => _search.IsDone; public string PaginationToken => _documentSearch?.PaginationToken?.ToString(); public Dictionary<string, AttributeValue> NextKey => _documentSearch?.NextKey; }
注意:反射依赖SDK内部实现,若AWS后续修改AsyncSearch<T>的私有成员结构,代码会失效。
方案2:直接使用低级API
跳过AsyncSearch<T>的封装,直接用AmazonDynamoDBClient的QueryAsync/ScanAsync方法,这些方法返回的响应包含LastEvaluatedKey,可直接用于分页:
private readonly AmazonDynamoDBClient _dynamoDBClient; private Dictionary<string, AttributeValue> _lastEvaluatedKey; public async Task<List<T>> QueryWithPaginationAsync(string tableName, int pageSize = 10) { var request = new QueryRequest { TableName = tableName, // 设置查询条件、ProjectionExpression等 ExclusiveStartKey = _lastEvaluatedKey, Limit = pageSize }; var response = await _dynamoDBClient.QueryAsync(request); // 自行实现AttributeValue到强类型T的转换逻辑 var items = response.Items.Select(ConvertToT).ToList(); // 保存下一页的起始键 _lastEvaluatedKey = response.LastEvaluatedKey; return items; } private T ConvertToT(Dictionary<string, AttributeValue> item) { // 实现对象转换逻辑 }
这种方法稳定性更高,不依赖SDK内部封装,但需要自行处理对象与AttributeValue的转换。
方案3:复用AsyncSearch实例作为分页状态
如果是内存内保持状态的场景(如单会话分页),可直接保存AsyncSearch<T>实例,它内部已维护分页状态,每次调用GetNextSetAsync()会自动加载下一页:
private AsyncSearch<MyItem> _currentSearch; private readonly DynamoDBContext _context; public async Task<List<MyItem>> GetFirstPageAsync() { _currentSearch = _context.QueryAsync<MyItem>(/* 传入查询参数 */); return await _currentSearch.GetNextSetAsync(); } public async Task<List<MyItem>> GetNextPageAsync() { if (_currentSearch == null || _currentSearch.IsDone) return new List<MyItem>(); return await _currentSearch.GetNextSetAsync(); }
这种方法最简单,但不适合跨请求保存分页状态的场景(如Web应用前后端分页)。
内容的提问来源于stack exchange,提问作者viking

