Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit cd8ae40

Browse files
Enable to force-disable pagination per relationship (#1750)
* Add assertion method to clarify intent * Remove redundant nullability suppressions * Enable to enforce disabling pagination per relationship
1 parent f4980c3 commit cd8ae40

File tree

27 files changed

+351
-30
lines changed

27 files changed

+351
-30
lines changed

‎docs/build-dev.ps1

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ function EnsureHttpServerIsInstalled {
1818
throw "Unable to find npm in your PATH. please install Node.js first."
1919
}
2020

21+
# If this command fails with ENOENT after installing Node.js on Windows, manually create the directory %APPDATA%\npm.
2122
npm list --depth 1 --global httpserver >$null
2223

2324
if ($LastExitCode -eq 1) {

‎docs/usage/options.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ options.IncludeTotalResourceCount = true;
5959
To retrieve the total number of resources on secondary and relationship endpoints, the reverse of the relationship must to be available. For example, in `GET /customers/1/orders`, both the relationships `[HasMany] Customer.Orders` and `[HasOne] Order.Customer` must be defined.
6060
If `IncludeTotalResourceCount` is set to `false` (or the inverse relationship is unavailable on a non-primary endpoint), best-effort pagination links are returned instead. This means no `last` link and the `next` link only occurs when the current page is full.
6161

62+
> [!TIP]
63+
> Since v5.8, pagination can be [turned off per relationship](~/usage/resources/relationships.md#disable-pagination).
64+
6265
## Relative Links
6366

6467
All links are absolute by default. However, you can configure relative links:

‎docs/usage/reading/pagination.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,6 @@ GET /api/blogs/1/articles?include=revisions&page[size]=10,revisions:5&page[numbe
2525
## Configuring Default Behavior
2626

2727
You can configure the global default behavior as described [here](~/usage/options.md#pagination).
28+
29+
> [!TIP]
30+
> Since v5.8, pagination can be [turned off per relationship](~/usage/resources/relationships.md#disable-pagination).

‎docs/usage/resources/relationships.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,17 @@ public class Person : Identifiable<int>
213213

214214
The left side of this relationship is of type `Person` (public name: "persons") and the right side is of type `TodoItem` (public name: "todoItems").
215215

216+
### Disable pagination
217+
218+
_since v5.8_
219+
220+
Pagination can be turned off per to-many relationship by setting `DisablePagination` to `true`.
221+
When doing so, it overrules the global pagination settings in options, and any pagination used in the query string
222+
for the relationship.
223+
224+
This feature exists for cases where the number of *related* resources is typically small.
225+
For example, while the number of products is usually high, the number of products *in a shopping basket* is not.
226+
216227
## HasManyThrough
217228

218229
_removed since v5.0_

‎src/JsonApiDotNetCore.Annotations/Resources/Annotations/HasManyAttribute.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,18 @@ public HasManyCapabilities Capabilities
5050
set => _capabilities = value;
5151
}
5252

53+
/// <summary>
54+
/// When set to <c>true</c>, overrules the default page size, the page size from a resource definition, and the
55+
/// <c>
56+
/// page[size]
57+
/// </c>
58+
/// query string parameter by forcibly turning off pagination on the related resources for this relationship.
59+
/// </summary>
60+
/// <remarks>
61+
/// Caution: only use this when the number of related resources (along with their nested includes) is known to always be small.
62+
/// </remarks>
63+
public bool DisablePagination { get; set; }
64+
5365
public HasManyAttribute()
5466
{
5567
_lazyIsManyToMany = new Lazy<bool>(EvaluateIsManyToMany, LazyThreadSafetyMode.PublicationOnly);

‎src/JsonApiDotNetCore.Annotations/Resources/Annotations/HasManyAttribute.netstandard.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,7 @@ public sealed class HasManyAttribute : RelationshipAttribute
1111
{
1212
/// <summary />
1313
public HasManyCapabilities Capabilities { get; set; }
14+
15+
/// <summary />
16+
public bool DisablePagination { get; set; }
1417
}

‎src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,8 @@ public interface IJsonApiOptions
105105
bool IncludeTotalResourceCount { get; }
106106

107107
/// <summary>
108-
/// The page size (10 by default) that is used when not specified in query string. Set to <c>null</c> to not use pagination by default.
108+
/// The page size (10 by default) that is used when not specified in query string. Set to <c>null</c> to not use pagination by default. This setting can
109+
/// be overruled per relationship by setting <see cref="HasManyAttribute.DisablePagination" /> to <c>true</c>.
109110
/// </summary>
110111
PageSize? DefaultPageSize { get; }
111112

‎src/JsonApiDotNetCore/Queries/Expressions/QueryExpressionRewriter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ public override QueryExpression VisitIsType(IsTypeExpression expression, TArgume
124124

125125
if (newElements.Count != 0)
126126
{
127-
var newExpression = new SortExpression(newElements);
127+
var newExpression = new SortExpression(newElements,expression.IsAutoGenerated);
128128
return newExpression.Equals(expression) ? expression : newExpression;
129129
}
130130

‎src/JsonApiDotNetCore/Queries/Expressions/SortExpression.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,27 @@ namespace JsonApiDotNetCore.Queries.Expressions;
1313
[PublicAPI]
1414
public class SortExpression : QueryExpression
1515
{
16+
/// <summary>
17+
/// Indicates whether this expression was generated by JsonApiDotNetCore to ensure a deterministic order.
18+
/// </summary>
19+
internal bool IsAutoGenerated { get; }
20+
1621
/// <summary>
1722
/// One or more elements to sort on.
1823
/// </summary>
1924
public IImmutableList<SortElementExpression> Elements { get; }
2025

2126
public SortExpression(IImmutableList<SortElementExpression> elements)
27+
: this(elements, false)
28+
{
29+
}
30+
31+
internal SortExpression(IImmutableList<SortElementExpression> elements, bool isAutoGenerated)
2232
{
2333
ArgumentGuard.NotNullNorEmpty(elements);
2434

2535
Elements = elements;
36+
IsAutoGenerated = isAutoGenerated;
2637
}
2738

2839
public override TResult Accept<TArgument, TResult>(QueryExpressionVisitor<TArgument, TResult> visitor, TArgument argument)
@@ -37,7 +48,7 @@ public override string ToString()
3748

3849
public override string ToFullString()
3950
{
40-
return string.Join(",", Elements.Select(child => child.ToFullString()));
51+
return $"{string.Join(",", Elements.Select(child => child.ToFullString()))}{(IsAutoGenerated?" (auto-generated)":"")}";
4152
}
4253

4354
public override bool Equals(object? obj)
@@ -54,12 +65,13 @@ public override bool Equals(object? obj)
5465

5566
var other = (SortExpression)obj;
5667

57-
return Elements.SequenceEqual(other.Elements);
68+
return IsAutoGenerated==other.IsAutoGenerated&&Elements.SequenceEqual(other.Elements);
5869
}
5970

6071
public override int GetHashCode()
6172
{
6273
var hashCode = new HashCode();
74+
hashCode.Add(IsAutoGenerated);
6375

6476
foreach (SortElementExpression element in Elements)
6577
{

‎src/JsonApiDotNetCore/Queries/QueryLayerComposer.cs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -243,12 +243,13 @@ private IImmutableSet<IncludeElementExpression> ProcessIncludeSet(IImmutableSet<
243243

244244
ResourceType resourceType = includeElement.Relationship.RightType;
245245
bool isToManyRelationship = includeElement.Relationship is HasManyAttribute;
246+
bool allowPagination = includeElement.Relationship is HasManyAttribute { DisablePagination: false };
246247

247248
var subLayer = new QueryLayer(resourceType)
248249
{
249250
Filter = isToManyRelationship ? GetFilter(expressionsInCurrentScope, resourceType) : null,
250251
Sort = isToManyRelationship ? GetSort(expressionsInCurrentScope, resourceType) : null,
251-
Pagination = isToManyRelationship ? GetPagination(expressionsInCurrentScope, resourceType) : null,
252+
Pagination = allowPagination ? GetPagination(expressionsInCurrentScope, resourceType) : null,
252253
Selection = GetSelectionForSparseAttributeSet(resourceType)
253254
};
254255

@@ -384,12 +385,26 @@ public QueryLayer WrapLayerForSecondaryEndpoint<TId>(QueryLayer secondaryLayer,
384385
FilterExpression? primaryFilter = GetFilter(Array.Empty<QueryExpression>(), primaryResourceType);
385386
AttrAttribute primaryIdAttribute = GetIdAttribute(primaryResourceType);
386387

387-
return new QueryLayer(primaryResourceType)
388+
varprimaryLayer= new QueryLayer(primaryResourceType)
388389
{
389390
Include = RewriteIncludeForSecondaryEndpoint(innerInclude, relationship),
390391
Filter = CreateFilterByIds([primaryId], primaryIdAttribute, primaryFilter),
391392
Selection = primarySelection
392393
};
394+
395+
if (relationship is HasManyAttribute { DisablePagination: true } && secondaryLayer.Pagination != null)
396+
{
397+
// Undo pagination/sort. At the time secondaryLayer was being built, we were not yet aware that it needed to be turned off.
398+
secondaryLayer.Pagination = null;
399+
_paginationContext.PageSize = null;
400+
401+
if (secondaryLayer.Sort is { IsAutoGenerated: true })
402+
{
403+
secondaryLayer.Sort = null;
404+
}
405+
}
406+
407+
return primaryLayer;
393408
}
394409

395410
private IncludeExpression RewriteIncludeForSecondaryEndpoint(IncludeExpression? relativeInclude, RelationshipAttribute secondaryRelationship)
@@ -554,7 +569,7 @@ private SortExpression CreateSortById(ResourceType resourceType)
554569
{
555570
AttrAttribute idAttribute = GetIdAttribute(resourceType);
556571
var idAscendingSort = new SortElementExpression(new ResourceFieldChainExpression(idAttribute), true);
557-
return new SortExpression(ImmutableArray.Create(idAscendingSort));
572+
return new SortExpression(ImmutableArray.Create(idAscendingSort),true);
558573
}
559574

560575
protected virtual PaginationExpression GetPagination(IReadOnlyCollection<QueryExpression> expressionsInScope, ResourceType resourceType)

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /