在 Cosmos 数据库上使用 LINQ Skip Take 是否支持服务器端分页?
Is Server Side Pagination supported using LINQ Skip Take on Cosmos database?
使用 Cosmos SDK V3.
Does Cosmos support LINQ skip and Take for server side pagination, in following example?
根据我的分析,虽然我能够检索数据,但似乎查询没有进行服务器端分页。
为什么这么说:
我尝试使用 fiddler 并将断点放在 while 循环的开头,以查看 cosmos db 是通过 skip and take 调用的。但是没有服务器端调用,似乎所有数据都是在调用 Count 本身时获取的。
private static async Task ExportAsync<T>(Database database, string paritionKeyName, string partitionKeyPath)
{
IOrderedQueryable<T> query = database
.GetContainer(SourceContainerName)
.GetItemLinqQueryable<T>(allowSynchronousQueryExecution: true);
var totalCount = query.Count();
int skip = 0;
int take = MAX_BATCH_SIZE;
int taken = 0;
while (taken < totalCount)
{
//breakpoint
var itemsToInsert = query.Skip(skip).Take(take).ToList().AsReadOnly();
await ProcessBatchAsync(database, paritionKeyName, partitionKeyPath, itemsToInsert);
taken += take;
skip++;
}
}
它受支持,可以在可查询对象上使用 ToString()
进行测试,以查看发送到数据库的查询。
var query = container.GetItemLinqQueryable<Dictionary<string, object>>()
.OrderBy(x => x["_ts"])
.Skip(50)
.Take(10)
.ToString();
//result:
//{"query":"SELECT VALUE root FROM root ORDER BY root[\"_ts\"] ASC OFFSET 50 LIMIT 10"}
使用 OFFSET
以线性方式增加 RU 使用率。当你有很多页面时,对后面的页面使用这种类型的查询会变得非常昂贵。如果可能,您最好使用延续标记或 WHERE
子句来过滤结果。
除了回答中提到的@404,Cosmos DB 确实通过在查询中使用 OFFSET
和 LIMIT
子句来支持 skip
和 take
,但是使用这个出于以下原因,不建议这样做:
- 就 RU 消耗而言,它会导致昂贵的操作。
- 它仍然不提供服务器端分页,当您使用
OFFSET
和 LIMIT
执行查询时,您根据 LIMIT
和 LIMIT
的值获得的文档数它不会告诉您是否还有更多文档可用。
可在此处找到有关 OFFSET 和 LIMIT 子句的更多信息:https://docs.microsoft.com/en-us/azure/cosmos-db/sql-query-offset-limit。
在您的场景中,建议使用延续令牌(如@mjwills 所建议)。使用连续令牌,您可以在请求一定数量的项目(使用 QueryRequestOptions
指定)时实现服务器端分页。当查询执行时,你会得到两件事:
- 与您的查询相匹配的文件
- 如果有更多文档与您的查询匹配,则继续标记。
您可以处理收到的文件。如果您收到继续令牌,您将向 Cosmos DB 服务发送另一个查询(但这次包括继续令牌),该服务将 return 下一组文档。
请看示例代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Azure.Cosmos;
using Microsoft.Azure.Cosmos.Linq;
namespace SO67263501
{
class Program
{
static string connectionString = "connection-string";
static string databaseName = "database-name";
static string containerName = "container-name";
static async Task Main(string[] args)
{
string continuationToken = null;
int pageSize = 100;//Let's fetch 100 items at a time
CosmosClient cosmosClient = new CosmosClient(connectionString);
Container container = cosmosClient.GetContainer(databaseName, containerName);
QueryRequestOptions requestOptions = new QueryRequestOptions()
{
MaxItemCount = pageSize
};
do
{
FeedIterator<dynamic> queryResult = container.GetItemLinqQueryable<dynamic>(true, continuationToken, requestOptions).ToFeedIterator();
FeedResponse<dynamic> feedResponse = await queryResult.ReadNextAsync();
List<dynamic> documents = feedResponse.Resource.ToList();
continuationToken = feedResponse.ContinuationToken;
//Do something with the documents...
} while (continuationToken != null);
Console.WriteLine("All done...");
Console.WriteLine("Press any key to terminate the application.");
Console.ReadKey();
}
}
}
使用 Cosmos SDK V3.
Does Cosmos support LINQ skip and Take for server side pagination, in following example?
根据我的分析,虽然我能够检索数据,但似乎查询没有进行服务器端分页。
为什么这么说:
我尝试使用 fiddler 并将断点放在 while 循环的开头,以查看 cosmos db 是通过 skip and take 调用的。但是没有服务器端调用,似乎所有数据都是在调用 Count 本身时获取的。
private static async Task ExportAsync<T>(Database database, string paritionKeyName, string partitionKeyPath)
{
IOrderedQueryable<T> query = database
.GetContainer(SourceContainerName)
.GetItemLinqQueryable<T>(allowSynchronousQueryExecution: true);
var totalCount = query.Count();
int skip = 0;
int take = MAX_BATCH_SIZE;
int taken = 0;
while (taken < totalCount)
{
//breakpoint
var itemsToInsert = query.Skip(skip).Take(take).ToList().AsReadOnly();
await ProcessBatchAsync(database, paritionKeyName, partitionKeyPath, itemsToInsert);
taken += take;
skip++;
}
}
它受支持,可以在可查询对象上使用 ToString()
进行测试,以查看发送到数据库的查询。
var query = container.GetItemLinqQueryable<Dictionary<string, object>>()
.OrderBy(x => x["_ts"])
.Skip(50)
.Take(10)
.ToString();
//result:
//{"query":"SELECT VALUE root FROM root ORDER BY root[\"_ts\"] ASC OFFSET 50 LIMIT 10"}
使用 OFFSET
以线性方式增加 RU 使用率。当你有很多页面时,对后面的页面使用这种类型的查询会变得非常昂贵。如果可能,您最好使用延续标记或 WHERE
子句来过滤结果。
除了回答中提到的@404,Cosmos DB 确实通过在查询中使用 OFFSET
和 LIMIT
子句来支持 skip
和 take
,但是使用这个出于以下原因,不建议这样做:
- 就 RU 消耗而言,它会导致昂贵的操作。
- 它仍然不提供服务器端分页,当您使用
OFFSET
和LIMIT
执行查询时,您根据LIMIT
和LIMIT
的值获得的文档数它不会告诉您是否还有更多文档可用。
可在此处找到有关 OFFSET 和 LIMIT 子句的更多信息:https://docs.microsoft.com/en-us/azure/cosmos-db/sql-query-offset-limit。
在您的场景中,建议使用延续令牌(如@mjwills 所建议)。使用连续令牌,您可以在请求一定数量的项目(使用 QueryRequestOptions
指定)时实现服务器端分页。当查询执行时,你会得到两件事:
- 与您的查询相匹配的文件
- 如果有更多文档与您的查询匹配,则继续标记。
您可以处理收到的文件。如果您收到继续令牌,您将向 Cosmos DB 服务发送另一个查询(但这次包括继续令牌),该服务将 return 下一组文档。
请看示例代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Azure.Cosmos;
using Microsoft.Azure.Cosmos.Linq;
namespace SO67263501
{
class Program
{
static string connectionString = "connection-string";
static string databaseName = "database-name";
static string containerName = "container-name";
static async Task Main(string[] args)
{
string continuationToken = null;
int pageSize = 100;//Let's fetch 100 items at a time
CosmosClient cosmosClient = new CosmosClient(connectionString);
Container container = cosmosClient.GetContainer(databaseName, containerName);
QueryRequestOptions requestOptions = new QueryRequestOptions()
{
MaxItemCount = pageSize
};
do
{
FeedIterator<dynamic> queryResult = container.GetItemLinqQueryable<dynamic>(true, continuationToken, requestOptions).ToFeedIterator();
FeedResponse<dynamic> feedResponse = await queryResult.ReadNextAsync();
List<dynamic> documents = feedResponse.Resource.ToList();
continuationToken = feedResponse.ContinuationToken;
//Do something with the documents...
} while (continuationToken != null);
Console.WriteLine("All done...");
Console.WriteLine("Press any key to terminate the application.");
Console.ReadKey();
}
}
}