Skip to content

Commit 5198265

Browse files
author
Bart Koelman
committed
Fixed the number of resource definition callbacks for sparse fieldsets
1 parent 65b1f03 commit 5198265

File tree

15 files changed

+113
-67
lines changed

15 files changed

+113
-67
lines changed

benchmarks/Serialization/JsonApiSerializerBenchmarks.cs

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using JsonApiDotNetCore.Configuration;
44
using JsonApiDotNetCore.Middleware;
55
using JsonApiDotNetCore.Queries;
6+
using JsonApiDotNetCore.Queries.Internal;
67
using JsonApiDotNetCore.QueryStrings.Internal;
78
using JsonApiDotNetCore.Resources;
89
using JsonApiDotNetCore.Serialization;
@@ -27,33 +28,29 @@ public class JsonApiSerializerBenchmarks
2728
public JsonApiSerializerBenchmarks()
2829
{
2930
var options = new JsonApiOptions();
31+
var request = new JsonApiRequest();
32+
3033
IResourceGraph resourceGraph = _dependencyFactory.CreateResourceGraph(options);
31-
IFieldsToSerialize fieldsToSerialize = CreateFieldsToSerialize(resourceGraph);
34+
35+
var constraintProviders = new IQueryConstraintProvider[]
36+
{
37+
new SparseFieldSetQueryStringParameterReader(request, resourceGraph)
38+
};
39+
40+
IResourceDefinitionAccessor resourceDefinitionAccessor = new Mock<IResourceDefinitionAccessor>().Object;
41+
var sparseFieldSetCache = new SparseFieldSetCache(constraintProviders, resourceDefinitionAccessor);
3242

3343
IMetaBuilder metaBuilder = new Mock<IMetaBuilder>().Object;
3444
ILinkBuilder linkBuilder = new Mock<ILinkBuilder>().Object;
3545
IIncludedResourceObjectBuilder includeBuilder = new Mock<IIncludedResourceObjectBuilder>().Object;
3646

37-
var resourceObjectBuilder = new ResourceObjectBuilder(resourceGraph, options);
47+
IFieldsToSerialize fieldsToSerialize =
48+
new FieldsToSerialize(resourceGraph, constraintProviders, resourceDefinitionAccessor, request, sparseFieldSetCache);
3849

39-
IResourceDefinitionAccessor resourceDefinitionAccessor = new Mock<IResourceDefinitionAccessor>().Object;
50+
var resourceObjectBuilder = new ResourceObjectBuilder(resourceGraph, options);
4051

4152
_jsonApiSerializer = new ResponseSerializer<BenchmarkResource>(metaBuilder, linkBuilder, includeBuilder, fieldsToSerialize, resourceObjectBuilder,
42-
resourceDefinitionAccessor, options);
43-
}
44-
45-
private static FieldsToSerialize CreateFieldsToSerialize(IResourceGraph resourceGraph)
46-
{
47-
var request = new JsonApiRequest();
48-
49-
var constraintProviders = new IQueryConstraintProvider[]
50-
{
51-
new SparseFieldSetQueryStringParameterReader(request, resourceGraph)
52-
};
53-
54-
IResourceDefinitionAccessor accessor = new Mock<IResourceDefinitionAccessor>().Object;
55-
56-
return new FieldsToSerialize(resourceGraph, constraintProviders, accessor, request);
53+
resourceDefinitionAccessor, sparseFieldSetCache, options);
5754
}
5855

5956
[Benchmark]

src/JsonApiDotNetCore/AtomicOperations/OperationsProcessor.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
using System;
22
using System.Collections.Generic;
3-
using System.Net;
43
using System.Threading;
54
using System.Threading.Tasks;
65
using JetBrains.Annotations;
76
using JsonApiDotNetCore.Configuration;
87
using JsonApiDotNetCore.Errors;
98
using JsonApiDotNetCore.Middleware;
9+
using JsonApiDotNetCore.Queries.Internal;
1010
using JsonApiDotNetCore.Resources;
1111
using JsonApiDotNetCore.Serialization.Objects;
1212

@@ -22,24 +22,28 @@ public class OperationsProcessor : IOperationsProcessor
2222
private readonly IResourceGraph _resourceGraph;
2323
private readonly IJsonApiRequest _request;
2424
private readonly ITargetedFields _targetedFields;
25+
private readonly ISparseFieldSetCache _sparseFieldSetCache;
2526
private readonly LocalIdValidator _localIdValidator;
2627

2728
public OperationsProcessor(IOperationProcessorAccessor operationProcessorAccessor, IOperationsTransactionFactory operationsTransactionFactory,
28-
ILocalIdTracker localIdTracker, IResourceGraph resourceGraph, IJsonApiRequest request, ITargetedFields targetedFields)
29+
ILocalIdTracker localIdTracker, IResourceGraph resourceGraph, IJsonApiRequest request, ITargetedFields targetedFields,
30+
ISparseFieldSetCache sparseFieldSetCache)
2931
{
3032
ArgumentGuard.NotNull(operationProcessorAccessor, nameof(operationProcessorAccessor));
3133
ArgumentGuard.NotNull(operationsTransactionFactory, nameof(operationsTransactionFactory));
3234
ArgumentGuard.NotNull(localIdTracker, nameof(localIdTracker));
3335
ArgumentGuard.NotNull(resourceGraph, nameof(resourceGraph));
3436
ArgumentGuard.NotNull(request, nameof(request));
3537
ArgumentGuard.NotNull(targetedFields, nameof(targetedFields));
38+
ArgumentGuard.NotNull(sparseFieldSetCache, nameof(sparseFieldSetCache));
3639

3740
_operationProcessorAccessor = operationProcessorAccessor;
3841
_operationsTransactionFactory = operationsTransactionFactory;
3942
_localIdTracker = localIdTracker;
4043
_resourceGraph = resourceGraph;
4144
_request = request;
4245
_targetedFields = targetedFields;
46+
_sparseFieldSetCache = sparseFieldSetCache;
4347
_localIdValidator = new LocalIdValidator(_localIdTracker, _resourceGraph);
4448
}
4549

@@ -67,6 +71,8 @@ public virtual async Task<IList<OperationContainer>> ProcessAsync(IList<Operatio
6771
results.Add(result);
6872

6973
await transaction.AfterProcessOperationAsync(cancellationToken);
74+
75+
_sparseFieldSetCache.Reset();
7076
}
7177

7278
await transaction.CommitAsync(cancellationToken);

src/JsonApiDotNetCore/Configuration/JsonApiApplicationBuilder.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ public void ConfigureServiceContainer(ICollection<Type> dbContextTypes)
157157
_services.AddScoped(typeof(IResourceChangeTracker<>), typeof(ResourceChangeTracker<>));
158158
_services.AddScoped<IPaginationContext, PaginationContext>();
159159
_services.AddScoped<IEvaluatedIncludeCache, EvaluatedIncludeCache>();
160+
_services.AddScoped<ISparseFieldSetCache, SparseFieldSetCache>();
160161
_services.AddScoped<IQueryLayerComposer, QueryLayerComposer>();
161162
_services.AddScoped<IInverseNavigationResolver, InverseNavigationResolver>();
162163
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
using System.Collections.Immutable;
2+
using JsonApiDotNetCore.Configuration;
3+
using JsonApiDotNetCore.Resources;
4+
using JsonApiDotNetCore.Resources.Annotations;
5+
6+
namespace JsonApiDotNetCore.Queries.Internal
7+
{
8+
/// <summary>
9+
/// Takes sparse fieldsets from <see cref="IQueryConstraintProvider" />s and invokes
10+
/// <see cref="IResourceDefinition{TResource,TId}.OnApplySparseFieldSet" /> on them.
11+
/// </summary>
12+
/// <remarks>
13+
/// This cache ensures that for each request (or operation per request), the resource definition callback is executed only twice per resource type. The
14+
/// first invocation is used to obtain the fields to retrieve from the underlying data store, while the second invocation is used to determine which
15+
/// fields to write to the response body.
16+
/// </remarks>
17+
public interface ISparseFieldSetCache
18+
{
19+
/// <summary>
20+
/// Gets the set of sparse fields to retrieve from the underlying data store. Returns an empty set to retrieve all fields.
21+
/// </summary>
22+
IImmutableSet<ResourceFieldAttribute> GetSparseFieldSetForQuery(ResourceContext resourceContext);
23+
24+
/// <summary>
25+
/// Gets the set of attributes to retrieve from the underlying data store for relationship endpoints. This always returns 'id', along with any additional
26+
/// attributes from resource definition callback.
27+
/// </summary>
28+
IImmutableSet<AttrAttribute> GetIdAttributeSetForRelationshipQuery(ResourceContext resourceContext);
29+
30+
/// <summary>
31+
/// Gets the evaluated set of sparse fields to serialize into the response body.
32+
/// </summary>
33+
IImmutableSet<ResourceFieldAttribute> GetSparseFieldSetForSerializer(ResourceContext resourceContext);
34+
35+
/// <summary>
36+
/// Resets the cached results from resource definition callbacks.
37+
/// </summary>
38+
void Reset();
39+
}
40+
}

src/JsonApiDotNetCore/Queries/Internal/QueryLayerComposer.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@ public class QueryLayerComposer : IQueryLayerComposer
2323
private readonly IPaginationContext _paginationContext;
2424
private readonly ITargetedFields _targetedFields;
2525
private readonly IEvaluatedIncludeCache _evaluatedIncludeCache;
26-
private readonly SparseFieldSetCache _sparseFieldSetCache;
26+
private readonly ISparseFieldSetCache _sparseFieldSetCache;
2727

2828
public QueryLayerComposer(IEnumerable<IQueryConstraintProvider> constraintProviders, IResourceGraph resourceGraph,
2929
IResourceDefinitionAccessor resourceDefinitionAccessor, IJsonApiOptions options, IPaginationContext paginationContext,
30-
ITargetedFields targetedFields, IEvaluatedIncludeCache evaluatedIncludeCache)
30+
ITargetedFields targetedFields, IEvaluatedIncludeCache evaluatedIncludeCache, ISparseFieldSetCache sparseFieldSetCache)
3131
{
3232
ArgumentGuard.NotNull(constraintProviders, nameof(constraintProviders));
3333
ArgumentGuard.NotNull(resourceGraph, nameof(resourceGraph));
@@ -36,6 +36,7 @@ public QueryLayerComposer(IEnumerable<IQueryConstraintProvider> constraintProvid
3636
ArgumentGuard.NotNull(paginationContext, nameof(paginationContext));
3737
ArgumentGuard.NotNull(targetedFields, nameof(targetedFields));
3838
ArgumentGuard.NotNull(evaluatedIncludeCache, nameof(evaluatedIncludeCache));
39+
ArgumentGuard.NotNull(sparseFieldSetCache, nameof(sparseFieldSetCache));
3940

4041
_constraintProviders = constraintProviders;
4142
_resourceGraph = resourceGraph;
@@ -44,7 +45,7 @@ public QueryLayerComposer(IEnumerable<IQueryConstraintProvider> constraintProvid
4445
_paginationContext = paginationContext;
4546
_targetedFields = targetedFields;
4647
_evaluatedIncludeCache = evaluatedIncludeCache;
47-
_sparseFieldSetCache = new SparseFieldSetCache(_constraintProviders, resourceDefinitionAccessor);
48+
_sparseFieldSetCache = sparseFieldSetCache;
4849
}
4950

5051
/// <inheritdoc />

src/JsonApiDotNetCore/Queries/Internal/SparseFieldSetCache.cs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,15 @@
22
using System.Collections.Generic;
33
using System.Collections.Immutable;
44
using System.Linq;
5-
using JetBrains.Annotations;
65
using JsonApiDotNetCore.Configuration;
76
using JsonApiDotNetCore.Queries.Expressions;
87
using JsonApiDotNetCore.Resources;
98
using JsonApiDotNetCore.Resources.Annotations;
109

1110
namespace JsonApiDotNetCore.Queries.Internal
1211
{
13-
/// <summary>
14-
/// Takes sparse fieldsets from <see cref="IQueryConstraintProvider" />s and invokes
15-
/// <see cref="IResourceDefinition{TResource,TId}.OnApplySparseFieldSet" /> on them.
16-
/// </summary>
17-
[PublicAPI]
18-
public sealed class SparseFieldSetCache
12+
/// <inheritdoc />
13+
public sealed class SparseFieldSetCache : ISparseFieldSetCache
1914
{
2015
private readonly IResourceDefinitionAccessor _resourceDefinitionAccessor;
2116
private readonly Lazy<IDictionary<ResourceContext, IImmutableSet<ResourceFieldAttribute>>> _lazySourceTable;
@@ -73,6 +68,7 @@ private static void AddSparseFieldsToSet(IImmutableSet<ResourceFieldAttribute> s
7368
}
7469
}
7570

71+
/// <inheritdoc />
7672
public IImmutableSet<ResourceFieldAttribute> GetSparseFieldSetForQuery(ResourceContext resourceContext)
7773
{
7874
ArgumentGuard.NotNull(resourceContext, nameof(resourceContext));
@@ -95,6 +91,7 @@ public IImmutableSet<ResourceFieldAttribute> GetSparseFieldSetForQuery(ResourceC
9591
return _visitedTable[resourceContext];
9692
}
9793

94+
/// <inheritdoc />
9895
public IImmutableSet<AttrAttribute> GetIdAttributeSetForRelationshipQuery(ResourceContext resourceContext)
9996
{
10097
ArgumentGuard.NotNull(resourceContext, nameof(resourceContext));
@@ -113,6 +110,7 @@ public IImmutableSet<AttrAttribute> GetIdAttributeSetForRelationshipQuery(Resour
113110
return outputAttributes;
114111
}
115112

113+
/// <inheritdoc />
116114
public IImmutableSet<ResourceFieldAttribute> GetSparseFieldSetForSerializer(ResourceContext resourceContext)
117115
{
118116
ArgumentGuard.NotNull(resourceContext, nameof(resourceContext));

src/JsonApiDotNetCore/Serialization/AtomicOperationsResponseSerializer.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ public sealed class AtomicOperationsResponseSerializer : BaseSerializer, IJsonAp
2323
private readonly IFieldsToSerialize _fieldsToSerialize;
2424
private readonly IResourceDefinitionAccessor _resourceDefinitionAccessor;
2525
private readonly IEvaluatedIncludeCache _evaluatedIncludeCache;
26+
private readonly ISparseFieldSetCache _sparseFieldSetCache;
2627
private readonly IJsonApiRequest _request;
2728
private readonly IJsonApiOptions _options;
2829

@@ -31,14 +32,15 @@ public sealed class AtomicOperationsResponseSerializer : BaseSerializer, IJsonAp
3132

3233
public AtomicOperationsResponseSerializer(IResourceObjectBuilder resourceObjectBuilder, IMetaBuilder metaBuilder, ILinkBuilder linkBuilder,
3334
IFieldsToSerialize fieldsToSerialize, IResourceDefinitionAccessor resourceDefinitionAccessor, IEvaluatedIncludeCache evaluatedIncludeCache,
34-
IJsonApiRequest request, IJsonApiOptions options)
35+
ISparseFieldSetCache sparseFieldSetCache, IJsonApiRequest request, IJsonApiOptions options)
3536
: base(resourceObjectBuilder)
3637
{
3738
ArgumentGuard.NotNull(metaBuilder, nameof(metaBuilder));
3839
ArgumentGuard.NotNull(linkBuilder, nameof(linkBuilder));
3940
ArgumentGuard.NotNull(fieldsToSerialize, nameof(fieldsToSerialize));
4041
ArgumentGuard.NotNull(resourceDefinitionAccessor, nameof(resourceDefinitionAccessor));
4142
ArgumentGuard.NotNull(evaluatedIncludeCache, nameof(evaluatedIncludeCache));
43+
ArgumentGuard.NotNull(sparseFieldSetCache, nameof(sparseFieldSetCache));
4244
ArgumentGuard.NotNull(request, nameof(request));
4345
ArgumentGuard.NotNull(options, nameof(options));
4446

@@ -47,6 +49,7 @@ public AtomicOperationsResponseSerializer(IResourceObjectBuilder resourceObjectB
4749
_fieldsToSerialize = fieldsToSerialize;
4850
_resourceDefinitionAccessor = resourceDefinitionAccessor;
4951
_evaluatedIncludeCache = evaluatedIncludeCache;
52+
_sparseFieldSetCache = sparseFieldSetCache;
5053
_request = request;
5154
_options = options;
5255
}
@@ -117,7 +120,7 @@ private AtomicResultObject SerializeOperation(OperationContainer operation)
117120
if (operation != null)
118121
{
119122
_request.CopyFrom(operation.Request);
120-
_fieldsToSerialize.ResetCache();
123+
_sparseFieldSetCache.Reset();
121124
_evaluatedIncludeCache.Set(null);
122125

123126
_resourceDefinitionAccessor.OnSerialize(operation.Resource);

src/JsonApiDotNetCore/Serialization/Building/IncludedResourceObjectBuilder.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,25 +22,26 @@ public class IncludedResourceObjectBuilder : ResourceObjectBuilder, IIncludedRes
2222
private readonly ILinkBuilder _linkBuilder;
2323
private readonly IResourceDefinitionAccessor _resourceDefinitionAccessor;
2424
private readonly IRequestQueryStringAccessor _queryStringAccessor;
25-
private readonly SparseFieldSetCache _sparseFieldSetCache;
25+
private readonly ISparseFieldSetCache _sparseFieldSetCache;
2626

2727
public IncludedResourceObjectBuilder(IFieldsToSerialize fieldsToSerialize, ILinkBuilder linkBuilder, IResourceGraph resourceGraph,
2828
IEnumerable<IQueryConstraintProvider> constraintProviders, IResourceDefinitionAccessor resourceDefinitionAccessor,
29-
IRequestQueryStringAccessor queryStringAccessor, IJsonApiOptions options)
29+
IRequestQueryStringAccessor queryStringAccessor, IJsonApiOptions options, ISparseFieldSetCache sparseFieldSetCache)
3030
: base(resourceGraph, options)
3131
{
3232
ArgumentGuard.NotNull(fieldsToSerialize, nameof(fieldsToSerialize));
3333
ArgumentGuard.NotNull(linkBuilder, nameof(linkBuilder));
3434
ArgumentGuard.NotNull(constraintProviders, nameof(constraintProviders));
3535
ArgumentGuard.NotNull(resourceDefinitionAccessor, nameof(resourceDefinitionAccessor));
3636
ArgumentGuard.NotNull(queryStringAccessor, nameof(queryStringAccessor));
37+
ArgumentGuard.NotNull(sparseFieldSetCache, nameof(sparseFieldSetCache));
3738

3839
_included = new HashSet<ResourceObject>(ResourceIdentityComparer.Instance);
3940
_fieldsToSerialize = fieldsToSerialize;
4041
_linkBuilder = linkBuilder;
4142
_resourceDefinitionAccessor = resourceDefinitionAccessor;
4243
_queryStringAccessor = queryStringAccessor;
43-
_sparseFieldSetCache = new SparseFieldSetCache(constraintProviders, resourceDefinitionAccessor);
44+
_sparseFieldSetCache = sparseFieldSetCache;
4445
}
4546

4647
/// <inheritdoc />

src/JsonApiDotNetCore/Serialization/Building/ResponseResourceObjectBuilder.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,26 +21,27 @@ public class ResponseResourceObjectBuilder : ResourceObjectBuilder
2121
private readonly IIncludedResourceObjectBuilder _includedBuilder;
2222
private readonly IResourceDefinitionAccessor _resourceDefinitionAccessor;
2323
private readonly IEvaluatedIncludeCache _evaluatedIncludeCache;
24-
private readonly SparseFieldSetCache _sparseFieldSetCache;
24+
private readonly ISparseFieldSetCache _sparseFieldSetCache;
2525

2626
private RelationshipAttribute _requestRelationship;
2727

2828
public ResponseResourceObjectBuilder(ILinkBuilder linkBuilder, IIncludedResourceObjectBuilder includedBuilder,
2929
IEnumerable<IQueryConstraintProvider> constraintProviders, IResourceGraph resourceGraph, IResourceDefinitionAccessor resourceDefinitionAccessor,
30-
IJsonApiOptions options, IEvaluatedIncludeCache evaluatedIncludeCache)
30+
IJsonApiOptions options, IEvaluatedIncludeCache evaluatedIncludeCache, ISparseFieldSetCache sparseFieldSetCache)
3131
: base(resourceGraph, options)
3232
{
3333
ArgumentGuard.NotNull(linkBuilder, nameof(linkBuilder));
3434
ArgumentGuard.NotNull(includedBuilder, nameof(includedBuilder));
3535
ArgumentGuard.NotNull(constraintProviders, nameof(constraintProviders));
3636
ArgumentGuard.NotNull(resourceDefinitionAccessor, nameof(resourceDefinitionAccessor));
3737
ArgumentGuard.NotNull(evaluatedIncludeCache, nameof(evaluatedIncludeCache));
38+
ArgumentGuard.NotNull(sparseFieldSetCache, nameof(sparseFieldSetCache));
3839

3940
_linkBuilder = linkBuilder;
4041
_includedBuilder = includedBuilder;
4142
_resourceDefinitionAccessor = resourceDefinitionAccessor;
4243
_evaluatedIncludeCache = evaluatedIncludeCache;
43-
_sparseFieldSetCache = new SparseFieldSetCache(constraintProviders, resourceDefinitionAccessor);
44+
_sparseFieldSetCache = sparseFieldSetCache;
4445
}
4546

4647
public RelationshipObject Build(IIdentifiable resource, RelationshipAttribute requestRelationship)

0 commit comments

Comments
 (0)