【问题标题】:Azure Cosmos C# SDK v3 Returning Invalid Continuation TokenAzure Cosmos C# SDK v3 返回无效的继续令牌
【发布时间】:2021-02-12 19:26:54
【问题描述】:

我正在将应用程序从 Cosmos C# SDK v2 迁移到 v3。我遇到了分页障碍,似乎无法弄清楚。

我有一个请求,它将搜索特定容器并通过请求进行分页。我的代码设置为处理页面大小(maxItemCount)和延续令牌。此外,前几个请求效果很好。但是当我喜欢第四页时,我在 Cosmos 的响应中得到的继续令牌是无效的。它看起来像这样:

[{\"range\":{\"min\":\"05C1DFFFFFFFFC\",\"max\":\"FF\"}}]

并且,当我将它传递给下一个请求以获取以下页面时,我收到以下错误:

Response status code does not indicate success: BadRequest (400); Substatus: 0; ActivityId: ; Reason: (Response status code does not indicate success: BadRequest (400); Substatus: 0; ActivityId: ; Reason: (Response status code does not indicate success: BadRequest (400); Substatus: 0; ActivityId: ; Reason: (Response status code does not indicate success: BadRequest (400); Substatus: 0; ActivityId: ; Reason: (Response status code does not indicate success: BadRequest (400); Substatus: 0; ActivityId: ; Reason: (Response status code does not indicate success: BadRequest (400); Substatus: 0; ActivityId: ; Reason: (Response status code does not indicate success: BadRequest (400); Substatus: 0; ActivityId: ; Reason: (Response status code does not indicate success: BadRequest (400); Substatus: 0; ActivityId: ; Reason: (CompositeContinuationToken is missing field: 'token': {"range":{"min":"05C1DFFFFFFFFC","max":"FF"}}););););););););

现在,我发现如果我在发送之前修改延续令牌,我可以获得请求成功。这需要我在 JSON 字符串中添加一个属性,您猜对了,token

以下是处理此请求的相关代码:

            IQueryable<LeadEntity> query;
            var queryResults = new List<LeadEntity>();

            var requestOptions = new QueryRequestOptions
            {
                MaxItemCount = input.Page?.Size ?? Core.Shared.Constants.PageSize.Default
            };

            if (input.Page?.ContinuationToken == "" || input.Page?.ContinuationToken == null) 
            {
                query = Container.GetItemLinqQueryable<LeadEntity>(false, null, requestOptions);
            }
            else 
            {
                query = Container.GetItemLinqQueryable<LeadEntity>(false, input.Page.ContinuationToken, requestOptions);
            }

            if (input.Criteria?.CallKeys.IsActive ?? false)
            {
                var callKeys = (input.Criteria.CallKeys?.Value ?? Enumerable.Empty<Guid>())
                    .Select(a => a.ToString());

                query = query.Where(entity => callKeys.Contains(entity.CallKey));
            }

            if (input.Criteria?.AdvisorOids.IsActive ?? false)
            {
                var advisorOids = (input.Criteria.AdvisorOids?.Value ?? Enumerable.Empty<Guid>())
                    .Select(a => a.ToString());

                query = query.Where(entity => advisorOids.Contains(entity.AdvisorOid));
            }

            if (input.Criteria?.CreatedOn.IsActive ?? false)
            {
                var start = input.Criteria.CreatedOn.Value.Start;
                var duration = input.Criteria.CreatedOn.Value.Duration;
                var end = input.Criteria.CreatedOn.Value.End;

                if (start.HasValue && end.HasValue)
                {
                    query = query.Where(entity => start.Value <= entity.Created.On && entity.Created.On <= end.Value);
                }
                else if (start.HasValue)
                {
                    query = query.Where(entity => start.Value <= entity.Created.On && entity.Created.On <= start.Value.Add(+duration));
                }
                else if (end.HasValue)
                {
                    query = query.Where(entity => end.Value >= entity.Created.On && entity.Created.On >= end.Value.Add(-duration));
                }
                else
                {
                    query = query.Where(entity => false);
                }
            }

            if (input.Criteria?.HasPortalKey.IsActive ?? false)
            {
                query = query.Where(entity => input.Criteria.HasPortalKey.Value
                        && entity.PortalKey != null
                        && entity.PortalKey != default(Guid).ToString()
                );
            }

            query = query.Where(a => a.IsRemoved.IsDefined() ? !a.IsRemoved : true);

            var totalCount = await query.CountAsync(ct);

            query = input.Orderings?.Any() ?? false
                ? query.OrderBy(string.Join(",", input.Orderings))
                : query;

            query = input.Limit.HasValue
                ? query.Take(input.Limit.Value)
                : query;

            var feedIterator = query.ToFeedIterator();

            FeedResponse<LeadEntity> feedResults = await feedIterator.ReadNextAsync(ct);
            
            queryResults.AddRange(feedResults);

            Console.WriteLine($"[LeadSearchAsync] total operation cost: {feedResults.RequestCharge} RUs");

            var output = new PageOutput<LeadOutput>
            {
                Items = Mapper.Map<IEnumerable<LeadOutput>>(queryResults),
                ContinuationToken = feedResults.ContinuationToken,
                TotalCount = totalCount
            };

            return output;

我一直在谷歌上搜索,只发现发生这种情况的几个实例,它通常在 v2 sdk 上,而不是 v3。我的一部分想认为我做错了分页,但它似乎在大多数情况下都有效。

任何解决此问题的帮助将不胜感激。我没有在 v3 SDK 上找到很多关于分页的文档,它与 v2 明显不同。

【问题讨论】:

    标签: c# pagination azure-cosmosdb azure-cosmosdb-sqlapi


    【解决方案1】:

    好的。今天又在谷歌上搜索了一下,我发现了这个Github issue

    基本上,我在启动时有这段代码将默认 JSONSerializerSettings 设置为忽略空值:

    var JsonSerializerSettings = new JsonSerializerSettings 
    {
        DateTimeZoneHandling = DateTimeZoneHandling.RoundtripKind,
        DateParseHandling = DateParseHandling.DateTimeOffset,             
        NullValueHandling = NullValueHandling.Ignore,             
        ReferenceLoopHandling = ReferenceLoopHandling.Ignore
    };
    
    JsonSerializerSettings.Converters.Add(new StringEnumConverter());
    JsonConvert.DefaultSettings = () => JsonSerializerSettings;
    

    这导致我的延续令牌字符串删除字段token,因为它为空。显然,在您的延续令牌中有一个空令牌字段是完全正常的。

    如果我对上述假设有误,请告诉我。因为在分页的中途,我得到一个看起来像这样的令牌:

    [{\"token\":null,\"range\":{\"min\":\"05C1DFFFFFFFFC\",\"max\":\"FF\"}}]
    

    无论如何,我将此标记为自行解决。希望它可以帮助其他有同样问题的人。

    【讨论】:

    • 这最终帮助了我。谢谢!
    【解决方案2】:
     IQueryable<returnVModel> query;
     var requestOptions = new QueryRequestOptions
     {
         MaxItemCount = 20
     };
     if (Token == "" || Token == null)
     {
            query = Container.GetItemLinqQueryable<returnVModel>(false, null, requestOptions).Where(x => x.id == id);
     }
     else
     {
            query = Container.GetItemLinqQueryable<returnVModel>(false, Token, requestOptions).Where(x => x.id == id);
     }
     var ct = new CancellationTokenSource();
     var totalCount = await query.CountAsync(ct.Token); //Total Count
     var feedIterator = query.ToFeedIterator();
     var queryResults = new List<returnVModel>();
     FeedResponse<returnVModel> feedResults = await feedIterator.ReadNextAsync(ct.Token);
     queryResults.AddRange(feedResults); // Output
     var PaginationToken = feedResults.ContinuationToken //Token
    

    @Nate 我参考了您的代码并实现了分页,它在我的开发中运行良好。迁移到生产环境后我是否会遇到任何问题。

    【讨论】:

    • @Nate 当我们转向生产时,我们是否会遇到类似的性能问题?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-12-03
    • 2019-02-15
    • 2021-11-12
    • 2021-11-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多