我很难从MS中找到一个v3 SDK的代码示例,用于分页查询,它们提供了V2的示例,但该SDK是一个完全不同的代码库,使用";CreateDocumentQuery";方法
我尝试在GitHub中搜索:https://github.com/Azure/azure-cosmos-dotnet-v3/blob/master/Microsoft.Azure.Cosmos.Samples/Usage/Queries/Program.cs
我相信我正在寻找一个使用延续令牌的方法示例,假设如果我在web应用程序中缓存以前使用的延续令牌,那么我可以向后翻页也可以向前翻页?
我也不太理解MS的解释,因为MaxItemCount实际上并不意味着它只会尝试返回X个项目,而是简单地限制了每个分区中每次搜索的项目数量,令人困惑!
有人能给我指一个代码示例的正确位置吗?我还试着搜索https://learn.microsoft.com/en-us/azure/cosmos-db/sql-query-pagination但似乎将我们引向了旧的SDK(我相信是V2)
更新(以下是Gaurav的评论)
public async Task<(List<T>, string)> QueryWithPagingAsync(string query, int pageSize, string continuationToken)
{
try
{
Container container = GetContainer();
List<T> entities = new(); // Create a local list of type <T> objects.
QueryDefinition queryDefinition = new QueryDefinition(query);
using FeedIterator<T> resultSetIterator = container.GetItemQueryIterator<T>(
query, // SQL Query passed to this method.
continuationToken, // Value is always null for the first run.
requestOptions: new QueryRequestOptions()
{
// Optional if we already know the partition key value.
// Not relevant here becuase we're passing <T> which could
// be any model class passed to the generic method.
//PartitionKey = new PartitionKey("MyParitionKeyValue"),
// This does not actually limit how many documents are returned if
// what we're querying resides across multiple partitions.
// If we set the value to 1, then control the number of times
// the loop below performs the ReadNextAsync, then we can control
// the number of items we return from this method. I'm not sure
// whether this is best way to go, it seems we'd be calling
// the API X no. times by the number of items to return?
MaxItemCount = 1
});
// Set var i to zero, we'll use this to control the number of iterations in
// the loop, then once i is equal to the pageSize then we exit the loop.
// This allows us to limit the number of documents to return (hope this is the best way to do it)
var i = 0;
while (resultSetIterator.HasMoreResults & i < pageSize)
{
FeedResponse<T> response = await resultSetIterator.ReadNextAsync();
entities.AddRange(response);
continuationToken = response.ContinuationToken;
i++; // Add 1 to var i in each iteration.
}
return (entities, continuationToken);
}
catch (CosmosException ex)
{
//Log.Error($"Entities was not retrieved successfully - error details: {ex.Message}");
if (ex.StatusCode == HttpStatusCode.NotFound)
{
return (null, null);
}
else { throw; }
}
}
以上方法是我的最新尝试,虽然我可以使用并返回延续代币,但下一个挑战是如何控制从Cosmos返回的物品数量。在我的环境中,您可能会注意到上面的方法在repo中使用,我们从不同的调用方法传入模型类,因此对分区键进行硬编码是不可行的,并且我很难配置返回的项的数量。事实上,上面的方法控制了我返回到调用方法的项目数量,但我担心我的方法会导致多次调用Cosmos,即如果我将页面大小设置为1000个项目,我是否会对Cosmos进行1000次HTTP调用?
我在看一根线https://stackoverflow.com/questions/54140814/maxitemcount-feed-options-property-in-cosmos-db-doesnt-work但不确定该线程中的答案是一个解决方案,并且考虑到我使用的是V3 SDK;PageSize";可在请求选项中使用的参数。
然而,我也在这里找到了一个官方的Cosmos代码示例:https://github.com/Azure/azure-cosmos-dotnet-v3/blob/master/Microsoft.Azure.Cosmos.Samples/Usage/Queries/Program.cs#L154-L186(参见示例方法"QueryItemsInPartitionAsStreams"第171行),并且看起来它们使用了类似的模式,即将MaxItemCount变量设置为1,然后在退出之前控制循环中返回的项目数。我想我只是想更好地理解,如果这可能会对RU和API对Cosmos的调用产生任何影响的话?
解决方案:
摘要:
从我的问题中提出的担忧和Gaurav Mantri的评论中注意到,如果我们在循环中从Cosmos获取项目,那么MaxItemCount实际上并没有限制返回的结果总数,而只是限制每个请求的结果数。如果我们继续在循环中获取更多的项目,那么我们最终返回的结果将超过用户可能想要检索的结果。
在我的情况下,分页的原因是使用剃刀列表视图将项目显示回web应用程序,但我们希望能够设置每页返回的最大结果数。
下面的解决方案基于捕获循环每次迭代中项目计数的信息,因此,如果我们检查循环每次迭代返回的项目计数,并且如果我们获得的值小于或等于MaxItemCount,那么我们将使用设置的最大项目数和我们可以在下一次方法运行中使用的continuationToken来脱离循环。
我已经用延续令牌测试了该方法,并且能够有效地前后翻页,但与我最初问题中的代码示例的关键区别在于,我们只调用Cosmos DB一次,以获得所需数量的结果,而不是将请求限制为每次运行一个项目,并且必须运行多个请求。
public async Task<(List<T>, string)> QueryWithPagingAsync(string query, int pageSize, string continuationToken)
{
string unescapedContinuationToken = null;
if (!String.IsNullOrEmpty(continuationToken)) // Check if null before unescaping.
{
unescapedContinuationToken = Regex.Unescape(continuationToken); // Needed in my case...
}
try
{
Container container = GetContainer();
List<T> entities = new(); // Create a local list of type <T> objects.
QueryDefinition queryDefinition = new(query); // Create the query definition.
using FeedIterator<T> resultSetIterator = container.GetItemQueryIterator<T>(
query, // SQL Query passed to this method.
unescapedContinuationToken, // Value is always null for the first run.
requestOptions: new QueryRequestOptions()
{
// MaxItemCount does not actually limit how many documents are returned
// from Cosmos, if what we're querying resides across multiple partitions.
// However this parameter will control the max number of items
// returned on 'each request' to Cosmos.
// In the loop below, we check the Count of the items returned
// on each iteration of the loop and if we have achieved less than or
// equal to the MaxItemCount value then we break from the loop with
// our set maximum number of items and the continuationToken
// that we can use on the next method run.
// 'pageSize' is the max no. items we want to return for each page in our list view.
MaxItemCount = pageSize,
});
while (resultSetIterator.HasMoreResults)
{
FeedResponse<T> response = await resultSetIterator.ReadNextAsync();
entities.AddRange(response);
continuationToken = response.ContinuationToken;
// After the first iteration, we get the count of items returned.
// Now we'll either return the exact number of items that was set
// by the MaxItemCount, OR we may find there were less results than
// the MaxItemCount, but either way after the first run, we should
// have the number of items returned that we want, or at least
// the maximum number of items we want to return, so we break from the loop.
if (response.Count <= pageSize) { break; }
}
return (entities, continuationToken);
}
catch (CosmosException ex)
{
//Log.Error($"Entities was not retrieved successfully - error details: {ex.Message}");
if (ex.StatusCode == HttpStatusCode.NotFound)
{
return (null, null);
}
else { throw; }
}
}
请尝试以下代码。它从一个容器中获取所有文档,一个请求中最多可以获取100个文档。
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Azure.Cosmos;
namespace CosmosDbSQLAPISamples
{
class Program
{
private static string connectionString =
"AccountEndpoint=https://account-name.documents.azure.com:443/;AccountKey=account-key==;";
private static string databaseName = "database-name";
private static string containerName = "container-name";
static async Task Main(string[] args)
{
CosmosClient client = new CosmosClient(connectionString);
Container container = client.GetContainer(databaseName, containerName);
string query = "Select * From Root r";
string continuationToken = null;
int pageSize = 100;
do
{
var (entities, item2) = await GetDataPage(container, query, continuationToken, pageSize);
continuationToken = item2;
Console.WriteLine($"Total entities fetched: {entities.Count}; More entities available: {!string.IsNullOrWhiteSpace(continuationToken)}");
} while (continuationToken != null);
}
private static async Task<(List<dynamic>, string)> GetDataPage(Container container, string query, string continuationToken, int pageSize)
{
List<dynamic> entities = new(); // Create a local list of type <T> objects.
QueryDefinition queryDefinition = new QueryDefinition(query);
QueryRequestOptions requestOptions = new QueryRequestOptions()
{
MaxItemCount = pageSize
};
FeedIterator<dynamic> resultSetIterator = container.GetItemQueryIterator<dynamic>(query, continuationToken, requestOptions);
FeedResponse<dynamic> response = await resultSetIterator.ReadNextAsync();
entities.AddRange(response);
continuationToken = response.ContinuationToken;
return (entities, continuationToken);
}
}
}
更新
我想我现在理解你的担忧了。本质上有两件事你需要考虑:
MaxItemCount
-这是Cosmos DB在单个请求中返回的最大文档数。请注意,您可以从0到为此参数指定的值。例如,如果将100指定为MaxItemCount,则可以在单个请求中获得0到100个文档FeedIterator
-它在内部跟踪延续令牌。根据接收到的响应,如果找到延续令牌,则将HasMoreResults
设置为true或false。HasMoreResults
的默认值为true
现在来谈谈您的代码,当您执行以下操作时:
while (resultSetIterator.HasMoreResults)
{
//some code here...
}
由于FeedIterator
会跟踪延续令牌,因此此循环将返回与查询匹配的所有文档。如果您注意到,在我的代码中,我没有使用这个逻辑。我只需发送一次请求,然后返回结果。
我认为将MaxItemCount
设置为1
是个坏主意。如果你想获得100,那么你至少要向你的Cosmos DB账户发出100个请求。如果您非常需要从API中获取100个(或任何固定数量)文档,那么您可以实现自己的分页逻辑。例如,请参阅下面的代码。它总共获取1000个文档,单个请求最多可获取100个文档。
static async Task Main(string[] args)
{
CosmosClient client = new CosmosClient(connectionString);
Container container = client.GetContainer(databaseName, containerName);
string query = "Select * From Root r";
string continuationToken = null;
int pageSize = 100;
int maxDocumentsToFetch = 1000;
List<dynamic> documents = new List<dynamic>();
do
{
var numberOfDocumentsToFetch = Math.Min(pageSize, maxDocumentsToFetch);
var (entities, item2) = await GetDataPage(container, query, continuationToken, numberOfDocumentsToFetch);
continuationToken = item2;
Console.WriteLine($"Total entities fetched: {entities.Count}; More entities available: {!string.IsNullOrWhiteSpace(continuationToken)}");
maxDocumentsToFetch -= entities.Count;
documents.AddRange(entities);
} while (maxDocumentsToFetch > 0 && continuationToken != null);
}
在代码中:var sqlQueryText=$"SELECT*FROM c WHERE OFFSET{OFFSET}LIMIT{LIMIT}";;
但是这比使用continuationToken更昂贵(更多RU/s)。
当使用Offset/Limit continuationToken时,Azure Cosmos SDK将在后台使用它来获取所有结果。