Skip to content

Commit 2d9d70e

Browse files
committed
Do not inline total count by default into paging queries. (#7942)
1 parent 4eb11c3 commit 2d9d70e

11 files changed

+162
-68
lines changed

src/HotChocolate/Core/src/Abstractions/IQueryableExecutable.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ public interface IQueryableExecutable<T> : IExecutable<T>
2323
/// <returns>The new instance of an enumerable executable</returns>
2424
IQueryableExecutable<T> WithSource(IQueryable<T> source);
2525

26-
2726
/// <summary>
2827
/// Returns a new executable with the provided source
2928
/// </summary>

src/HotChocolate/Core/src/Types.CursorPagination/Extensions/CursorPagingRequestExecutorBuilderExtension.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ public static class CursorPagingRequestExecutorBuilderExtension
2222
/// <param name="defaultProvider">
2323
/// Defines if the registered provider shall be registered as the default provider.
2424
/// </param>
25+
/// <param name="inlineTotalCount">
26+
/// Specifies that the paging provider shall inline the total count query into the
27+
/// sliced query in order to have a single database call.
28+
/// Some database providers might not support this feature.
29+
/// </param>
2530
/// <returns>
2631
/// The request executor builder.
2732
/// </returns>
@@ -31,9 +36,11 @@ public static class CursorPagingRequestExecutorBuilderExtension
3136
public static IRequestExecutorBuilder AddQueryableCursorPagingProvider(
3237
this IRequestExecutorBuilder builder,
3338
string? providerName = null,
34-
bool defaultProvider = false)
39+
bool defaultProvider = false,
40+
bool? inlineTotalCount = null)
3541
=> AddCursorPagingProvider<QueryableCursorPagingProvider>(
3642
builder,
43+
_ => new QueryableCursorPagingProvider(inlineTotalCount),
3744
providerName,
3845
defaultProvider);
3946

src/HotChocolate/Core/src/Types.CursorPagination/QueryableCursorPagingHandler.cs

Lines changed: 40 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,19 @@
33

44
namespace HotChocolate.Types.Pagination;
55

6-
internal sealed class QueryableCursorPagingHandler<TEntity>(PagingOptions options)
7-
: CursorPagingHandler<IQueryable<TEntity>, TEntity>(options)
6+
internal sealed class QueryableCursorPagingHandler<TEntity> : CursorPagingHandler<IQueryable<TEntity>, TEntity>
87
{
8+
private readonly bool? _inlineTotalCount;
9+
10+
public QueryableCursorPagingHandler(PagingOptions options) : this(options, false)
11+
{
12+
}
13+
14+
public QueryableCursorPagingHandler(PagingOptions options, bool? inlineTotalCount) : base(options)
15+
{
16+
_inlineTotalCount = inlineTotalCount;
17+
}
18+
919
private static readonly QueryableCursorPaginationAlgorithm<TEntity> _paginationAlgorithm =
1020
QueryableCursorPaginationAlgorithm<TEntity>.Instance;
1121

@@ -18,7 +28,7 @@ public ValueTask<Connection<TEntity>> SliceAsync(
1828
source.Source,
1929
arguments,
2030
_paginationAlgorithm,
21-
new QueryExecutor(source),
31+
new QueryExecutor(source, _inlineTotalCount),
2232
context.RequestAborted);
2333

2434
protected override ValueTask<Connection> SliceAsync(
@@ -42,11 +52,11 @@ private async ValueTask<Connection> SliceAsyncInternal(
4252
source.Source,
4353
arguments,
4454
_paginationAlgorithm,
45-
new QueryExecutor(source),
55+
new QueryExecutor(source, _inlineTotalCount),
4656
context.RequestAborted)
4757
.ConfigureAwait(false);
4858

49-
private sealed class QueryExecutor(IQueryableExecutable<TEntity> executable)
59+
private sealed class QueryExecutor(IQueryableExecutable<TEntity> executable, bool? allowInlining)
5060
: ICursorPaginationQueryExecutor<IQueryable<TEntity>, TEntity>
5161
{
5262
public ValueTask<int> CountAsync(
@@ -66,17 +76,33 @@ public async ValueTask<CursorPaginationData<TEntity>> QueryAsync(
6676

6777
if (includeTotalCount)
6878
{
69-
var combinedQuery = slicedQuery.Select(t => new { TotalCount = originalQuery.Count(), Item = t });
70-
totalCount = 0;
79+
if (allowInlining ?? false)
80+
{
81+
var combinedQuery = slicedQuery.Select(t => new { TotalCount = originalQuery.Count(), Item = t });
82+
totalCount = 0;
7183

72-
var index = offset;
73-
await foreach (var item in executable
74-
.WithSource(combinedQuery)
75-
.ToAsyncEnumerable(cancellationToken)
76-
.ConfigureAwait(false))
84+
var index = offset;
85+
await foreach (var item in executable
86+
.WithSource(combinedQuery)
87+
.ToAsyncEnumerable(cancellationToken)
88+
.ConfigureAwait(false))
89+
{
90+
edges.Add(IndexEdge<TEntity>.Create(item.Item, index++));
91+
totalCount = item.TotalCount;
92+
}
93+
}
94+
else
7795
{
78-
edges.Add(IndexEdge<TEntity>.Create(item.Item, index++));
79-
totalCount = item.TotalCount;
96+
var index = offset;
97+
await foreach (var item in executable
98+
.WithSource(slicedQuery)
99+
.ToAsyncEnumerable(cancellationToken)
100+
.ConfigureAwait(false))
101+
{
102+
edges.Add(IndexEdge<TEntity>.Create(item, index++));
103+
}
104+
105+
totalCount = await executable.CountAsync(cancellationToken).ConfigureAwait(false);
80106
}
81107
}
82108
else

src/HotChocolate/Core/src/Types.CursorPagination/QueryableCursorPagingProvider.cs

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Collections.Concurrent;
12
using System.Reflection;
23
using HotChocolate.Internal;
34

@@ -8,11 +9,21 @@ namespace HotChocolate.Types.Pagination;
89
/// </summary>
910
public class QueryableCursorPagingProvider : CursorPagingProvider
1011
{
12+
private static readonly ConcurrentDictionary<Type, MethodInfo> _factoryCache = new();
13+
private readonly bool? _inlineTotalCount;
14+
1115
private static readonly MethodInfo _createHandler =
1216
typeof(QueryableCursorPagingProvider).GetMethod(
1317
nameof(CreateHandlerInternal),
1418
BindingFlags.Static | BindingFlags.NonPublic)!;
1519

20+
public QueryableCursorPagingProvider() { }
21+
22+
public QueryableCursorPagingProvider(bool? inlineTotalCount)
23+
{
24+
_inlineTotalCount = inlineTotalCount;
25+
}
26+
1627
/// <inheritdoc />
1728
public override bool CanHandle(IExtendedType source)
1829
{
@@ -34,12 +45,12 @@ protected override CursorPagingHandler CreateHandler(
3445
throw new ArgumentNullException(nameof(source));
3546
}
3647

37-
return (CursorPagingHandler)_createHandler
38-
.MakeGenericMethod(source.ElementType?.Source ?? source.Source)
39-
.Invoke(null, [options,])!;
48+
var key = source.ElementType?.Source ?? source.Source;
49+
var factory = _factoryCache.GetOrAdd(key, static type => _createHandler.MakeGenericMethod(type));
50+
return (CursorPagingHandler)factory.Invoke(null, [options, _inlineTotalCount])!;
4051
}
4152

4253
private static QueryableCursorPagingHandler<TEntity> CreateHandlerInternal<TEntity>(
43-
PagingOptions options) =>
44-
new(options);
54+
PagingOptions options, bool? inlineTotalCount)
55+
=> new(options, inlineTotalCount);
4556
}

src/HotChocolate/Data/test/Data.EntityFramework.Tests/SqlLiteCursorTestBase.cs

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,10 @@ private DatabaseContext<TResult> BuildContext<TResult>(
3232
protected IRequestExecutor CreateSchema<TEntity>(TEntity[] entities)
3333
where TEntity : class
3434
{
35-
var builder = SchemaBuilder.New()
35+
return new ServiceCollection()
36+
.AddDbContextPool<DatabaseContext<TEntity>>(
37+
b => b.UseSqlite($"Data Source={Guid.NewGuid():N}.db"))
38+
.AddGraphQL()
3639
.AddQueryType(
3740
c =>
3841
{
@@ -64,10 +67,7 @@ protected IRequestExecutor CreateSchema<TEntity>(TEntity[] entities)
6467
}
6568
})
6669
.UsePaging<ObjectType<TEntity>>(
67-
options: new()
68-
{
69-
IncludeTotalCount = true,
70-
});
70+
options: new() { IncludeTotalCount = true, });
7171

7272
c.Field("root1")
7373
.Resolve(
@@ -94,17 +94,8 @@ protected IRequestExecutor CreateSchema<TEntity>(TEntity[] entities)
9494
}
9595
}
9696
});
97-
});
98-
99-
var schema = builder.Create();
100-
101-
return new ServiceCollection()
102-
.Configure<RequestExecutorSetup>(
103-
Schema.DefaultName,
104-
o => o.Schema = schema)
105-
.AddDbContextPool<DatabaseContext<TEntity>>(
106-
b => b.UseSqlite($"Data Source={Guid.NewGuid():N}.db"))
107-
.AddGraphQL()
97+
})
98+
.AddQueryableCursorPagingProvider(inlineTotalCount: true)
10899
.UseDefaultPipeline()
109100
.Services
110101
.BuildServiceProvider()

0 commit comments

Comments
 (0)