Skip to content

Commit a257c08

Browse files
committed
Make CollectionConverter a singleton
1 parent 55f671c commit a257c08

File tree

16 files changed

+64
-56
lines changed

16 files changed

+64
-56
lines changed

src/Examples/DapperExample/Repositories/DapperRepository.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,6 @@ public sealed partial class DapperRepository<TResource, TId> : IResourceReposito
103103
private readonly SqlCaptureStore _captureStore;
104104
private readonly ILoggerFactory _loggerFactory;
105105
private readonly ILogger<DapperRepository<TResource, TId>> _logger;
106-
private readonly CollectionConverter _collectionConverter = new();
107106
private readonly ParameterFormatter _parameterFormatter = new();
108107
private readonly DapperFacade _dapperFacade;
109108

@@ -270,12 +269,12 @@ private async Task ApplyTargetedFieldsAsync(TResource resourceFromRequest, TReso
270269

271270
if (relationship is HasManyAttribute hasManyRelationship)
272271
{
273-
HashSet<IIdentifiable> rightResourceIds = _collectionConverter.ExtractResources(rightValue).ToHashSet(IdentifiableComparer.Instance);
272+
HashSet<IIdentifiable> rightResourceIds = CollectionConverter.Instance.ExtractResources(rightValue).ToHashSet(IdentifiableComparer.Instance);
274273

275274
await _resourceDefinitionAccessor.OnSetToManyRelationshipAsync(leftResource, hasManyRelationship, rightResourceIds, writeOperation,
276275
cancellationToken);
277276

278-
return _collectionConverter.CopyToTypedCollection(rightResourceIds, relationship.Property.PropertyType);
277+
return CollectionConverter.Instance.CopyToTypedCollection(rightResourceIds, relationship.Property.PropertyType);
279278
}
280279

281280
return rightValue;
@@ -464,7 +463,9 @@ public async Task AddToToManyRelationshipAsync(TResource? leftResource, [Disallo
464463
leftPlaceholderResource.Id = leftId;
465464

466465
await _resourceDefinitionAccessor.OnAddToRelationshipAsync(leftPlaceholderResource, relationship, rightResourceIds, cancellationToken);
467-
relationship.SetValue(leftPlaceholderResource, _collectionConverter.CopyToTypedCollection(rightResourceIds, relationship.Property.PropertyType));
466+
467+
relationship.SetValue(leftPlaceholderResource,
468+
CollectionConverter.Instance.CopyToTypedCollection(rightResourceIds, relationship.Property.PropertyType));
468469

469470
await _resourceDefinitionAccessor.OnWritingAsync(leftPlaceholderResource, WriteOperationKind.AddToRelationship, cancellationToken);
470471

@@ -500,7 +501,7 @@ public async Task RemoveFromToManyRelationshipAsync(TResource leftResource, ISet
500501
var relationship = (HasManyAttribute)_targetedFields.Relationships.Single();
501502

502503
await _resourceDefinitionAccessor.OnRemoveFromRelationshipAsync(leftResource, relationship, rightResourceIds, cancellationToken);
503-
relationship.SetValue(leftResource, _collectionConverter.CopyToTypedCollection(rightResourceIds, relationship.Property.PropertyType));
504+
relationship.SetValue(leftResource, CollectionConverter.Instance.CopyToTypedCollection(rightResourceIds, relationship.Property.PropertyType));
504505

505506
await _resourceDefinitionAccessor.OnWritingAsync(leftResource, WriteOperationKind.RemoveFromRelationship, cancellationToken);
506507

src/Examples/DapperExample/Repositories/ResourceChangeDetector.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ namespace DapperExample.Repositories;
1212
/// </summary>
1313
internal sealed class ResourceChangeDetector
1414
{
15-
private readonly CollectionConverter _collectionConverter = new();
1615
private readonly IDataModelService _dataModelService;
1716

1817
private Dictionary<string, object?> _currentColumnValues = [];
@@ -69,7 +68,7 @@ private Dictionary<RelationshipAttribute, HashSet<IIdentifiable>> CaptureRightRe
6968
foreach (RelationshipAttribute relationship in ResourceType.Relationships)
7069
{
7170
object? rightValue = relationship.GetValue(resource);
72-
HashSet<IIdentifiable> rightResources = _collectionConverter.ExtractResources(rightValue).ToHashSet(IdentifiableComparer.Instance);
71+
HashSet<IIdentifiable> rightResources = CollectionConverter.Instance.ExtractResources(rightValue).ToHashSet(IdentifiableComparer.Instance);
7372

7473
relationshipValues[relationship] = rightResources;
7574
}

src/JsonApiDotNetCore.Annotations/CollectionConverter.cs

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,24 @@ internal sealed class CollectionConverter
1515
typeof(IEnumerable<>)
1616
];
1717

18+
public static CollectionConverter Instance { get; } = new();
19+
20+
private CollectionConverter()
21+
{
22+
}
23+
1824
/// <summary>
1925
/// Creates a collection instance based on the specified collection type and copies the specified elements into it.
2026
/// </summary>
2127
/// <param name="source">
2228
/// Source to copy from.
2329
/// </param>
2430
/// <param name="collectionType">
25-
/// Target collection type, for example: typeof(List{Article}) or typeof(ISet{Person}).
31+
/// Target collection type, for example: <code><![CDATA[
32+
/// typeof(List<Article>)
33+
/// ]]></code> or <code><![CDATA[
34+
/// typeof(ISet<Person>)
35+
/// ]]></code>.
2636
/// </param>
2737
public IEnumerable CopyToTypedCollection(IEnumerable source, Type collectionType)
2838
{
@@ -41,7 +51,12 @@ public IEnumerable CopyToTypedCollection(IEnumerable source, Type collectionType
4151
}
4252

4353
/// <summary>
44-
/// Returns a compatible collection type that can be instantiated, for example IList{Article} -> List{Article} or ISet{Article} -> HashSet{Article}
54+
/// Returns a compatible collection type that can be instantiated, for example: <code><![CDATA[
55+
/// IList<Article> -> List<Article>
56+
/// ]]></code> or
57+
/// <code><![CDATA[
58+
/// ISet<Article> -> HashSet<Article>
59+
/// ]]></code>.
4560
/// </summary>
4661
private Type ToConcreteCollectionType(Type collectionType)
4762
{
@@ -80,7 +95,12 @@ public IReadOnlyCollection<IIdentifiable> ExtractResources(object? value)
8095
}
8196

8297
/// <summary>
83-
/// Returns the element type if the specified type is a generic collection, for example: IList{string} -> string or IList -> null.
98+
/// Returns the element type if the specified type is a generic collection, for example: <code><![CDATA[
99+
/// IList<string> -> string
100+
/// ]]></code> or
101+
/// <code><![CDATA[
102+
/// IList -> null
103+
/// ]]></code>.
84104
/// </summary>
85105
public Type? FindCollectionElementType(Type? type)
86106
{
@@ -96,8 +116,12 @@ public IReadOnlyCollection<IIdentifiable> ExtractResources(object? value)
96116
}
97117

98118
/// <summary>
99-
/// Indicates whether a <see cref="HashSet{T}" /> instance can be assigned to the specified type, for example IList{Article} -> false or ISet{Article} ->
100-
/// true.
119+
/// Indicates whether a <see cref="HashSet{T}" /> instance can be assigned to the specified type, for example:
120+
/// <code><![CDATA[
121+
/// IList<Article> -> false
122+
/// ]]></code> or <code><![CDATA[
123+
/// ISet<Article> -> true
124+
/// ]]></code>.
101125
/// </summary>
102126
public bool TypeCanContainHashSet(Type collectionType)
103127
{

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ private bool EvaluateIsManyToMany()
5959
{
6060
if (InverseNavigationProperty != null)
6161
{
62-
Type? elementType = CollectionConverter.FindCollectionElementType(InverseNavigationProperty.PropertyType);
62+
Type? elementType = CollectionConverter.Instance.FindCollectionElementType(InverseNavigationProperty.PropertyType);
6363
return elementType != null;
6464
}
6565

@@ -103,14 +103,14 @@ public void AddValue(object resource, IIdentifiable resourceToAdd)
103103
ArgumentGuard.NotNull(resourceToAdd);
104104

105105
object? rightValue = GetValue(resource);
106-
List<IIdentifiable> rightResources = CollectionConverter.ExtractResources(rightValue).ToList();
106+
List<IIdentifiable> rightResources = CollectionConverter.Instance.ExtractResources(rightValue).ToList();
107107

108108
if (!rightResources.Exists(nextResource => nextResource == resourceToAdd))
109109
{
110110
rightResources.Add(resourceToAdd);
111111

112112
Type collectionType = rightValue?.GetType() ?? Property.PropertyType;
113-
IEnumerable typedCollection = CollectionConverter.CopyToTypedCollection(rightResources, collectionType);
113+
IEnumerable typedCollection = CollectionConverter.Instance.CopyToTypedCollection(rightResources, collectionType);
114114
base.SetValue(resource, typedCollection);
115115
}
116116
}

src/JsonApiDotNetCore.Annotations/Resources/Annotations/HasOneAttribute.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ private bool EvaluateIsOneToOne()
5757
{
5858
if (InverseNavigationProperty != null)
5959
{
60-
Type? elementType = CollectionConverter.FindCollectionElementType(InverseNavigationProperty.PropertyType);
60+
Type? elementType = CollectionConverter.Instance.FindCollectionElementType(InverseNavigationProperty.PropertyType);
6161
return elementType == null;
6262
}
6363

src/JsonApiDotNetCore.Annotations/Resources/Annotations/RelationshipAttribute.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@ namespace JsonApiDotNetCore.Resources.Annotations;
1212
[PublicAPI]
1313
public abstract class RelationshipAttribute : ResourceFieldAttribute
1414
{
15-
private protected static readonly CollectionConverter CollectionConverter = new();
16-
1715
// This field is definitely assigned after building the resource graph, which is why its public equivalent is declared as non-nullable.
1816
private ResourceType? _rightType;
1917

src/JsonApiDotNetCore/AtomicOperations/Processors/SetRelationshipProcessor.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ namespace JsonApiDotNetCore.AtomicOperations.Processors;
1010
public class SetRelationshipProcessor<TResource, TId> : ISetRelationshipProcessor<TResource, TId>
1111
where TResource : class, IIdentifiable<TId>
1212
{
13-
private readonly CollectionConverter _collectionConverter = new();
1413
private readonly ISetRelationshipService<TResource, TId> _service;
1514

1615
public SetRelationshipProcessor(ISetRelationshipService<TResource, TId> service)
@@ -40,7 +39,7 @@ public SetRelationshipProcessor(ISetRelationshipService<TResource, TId> service)
4039

4140
if (relationship is HasManyAttribute)
4241
{
43-
IReadOnlyCollection<IIdentifiable> rightResources = _collectionConverter.ExtractResources(rightValue);
42+
IReadOnlyCollection<IIdentifiable> rightResources = CollectionConverter.Instance.ExtractResources(rightValue);
4443
return rightResources.ToHashSet(IdentifiableComparer.Instance);
4544
}
4645

src/JsonApiDotNetCore/Errors/InvalidModelStateException.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -319,8 +319,6 @@ private sealed class ArrayIndexerSegment(
319319
Func<Type, int, Type?>? getCollectionElementTypeCallback)
320320
: ModelStateKeySegment(modelType, isInComplexType, nextKey, sourcePointer, parent, getCollectionElementTypeCallback)
321321
{
322-
private static readonly CollectionConverter CollectionConverter = new();
323-
324322
public int ArrayIndex { get; } = arrayIndex;
325323

326324
public Type GetCollectionElementType()
@@ -333,7 +331,7 @@ private Type GetDeclaredCollectionElementType()
333331
{
334332
if (ModelType != typeof(string))
335333
{
336-
Type? elementType = CollectionConverter.FindCollectionElementType(ModelType);
334+
Type? elementType = CollectionConverter.Instance.FindCollectionElementType(ModelType);
337335

338336
if (elementType != null)
339337
{

src/JsonApiDotNetCore/Queries/QueryLayerComposer.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ namespace JsonApiDotNetCore.Queries;
1414
[PublicAPI]
1515
public class QueryLayerComposer : IQueryLayerComposer
1616
{
17-
private readonly CollectionConverter _collectionConverter = new();
1817
private readonly IQueryConstraintProvider[] _constraintProviders;
1918
private readonly IResourceDefinitionAccessor _resourceDefinitionAccessor;
2019
private readonly IJsonApiOptions _options;
@@ -413,7 +412,7 @@ public QueryLayer ComposeForUpdate<TId>([DisallowNull] TId id, ResourceType prim
413412
foreach (RelationshipAttribute relationship in _targetedFields.Relationships)
414413
{
415414
object? rightValue = relationship.GetValue(primaryResource);
416-
HashSet<IIdentifiable> rightResourceIds = _collectionConverter.ExtractResources(rightValue).ToHashSet(IdentifiableComparer.Instance);
415+
HashSet<IIdentifiable> rightResourceIds = CollectionConverter.Instance.ExtractResources(rightValue).ToHashSet(IdentifiableComparer.Instance);
417416

418417
if (rightResourceIds.Count > 0)
419418
{

src/JsonApiDotNetCore/Queries/QueryableBuilding/SelectClauseBuilder.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ public class SelectClauseBuilder : QueryClauseBuilder, ISelectClauseBuilder
1414
{
1515
private static readonly MethodInfo TypeGetTypeMethod = typeof(object).GetMethod("GetType")!;
1616
private static readonly MethodInfo TypeOpEqualityMethod = typeof(Type).GetMethod("op_Equality")!;
17-
private static readonly CollectionConverter CollectionConverter = new();
1817
private static readonly ConstantExpression NullConstant = Expression.Constant(null);
1918

2019
private readonly IResourceFactory _resourceFactory;
@@ -206,7 +205,7 @@ private MemberAssignment CreatePropertyAssignment(PropertySelector propertySelec
206205
private Expression CreateAssignmentRightHandSideForLayer(QueryLayer layer, LambdaScope outerLambdaScope, MemberExpression propertyAccess,
207206
PropertyInfo selectorPropertyInfo, QueryClauseBuilderContext context)
208207
{
209-
Type? collectionElementType = CollectionConverter.FindCollectionElementType(selectorPropertyInfo.PropertyType);
208+
Type? collectionElementType = CollectionConverter.Instance.FindCollectionElementType(selectorPropertyInfo.PropertyType);
210209
Type bodyElementType = collectionElementType ?? selectorPropertyInfo.PropertyType;
211210

212211
if (collectionElementType != null)
@@ -233,7 +232,7 @@ private static MethodCallExpression CreateCollectionInitializer(LambdaScope lamb
233232

234233
Expression layerExpression = context.QueryableBuilder.ApplyQuery(layer, nestedContext);
235234

236-
string operationName = CollectionConverter.TypeCanContainHashSet(collectionProperty.PropertyType) ? "ToHashSet" : "ToList";
235+
string operationName = CollectionConverter.Instance.TypeCanContainHashSet(collectionProperty.PropertyType) ? "ToHashSet" : "ToList";
237236
return CopyCollectionExtensionMethodCall(layerExpression, operationName, elementType);
238237
}
239238

src/JsonApiDotNetCore/Queries/QueryableBuilding/WhereClauseBuilder.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ namespace JsonApiDotNetCore.Queries.QueryableBuilding;
1313
[PublicAPI]
1414
public class WhereClauseBuilder : QueryClauseBuilder, IWhereClauseBuilder
1515
{
16-
private static readonly CollectionConverter CollectionConverter = new();
1716
private static readonly ConstantExpression NullConstant = Expression.Constant(null);
1817

1918
public virtual Expression ApplyWhere(FilterExpression filter, QueryClauseBuilderContext context)
@@ -40,7 +39,7 @@ public override Expression VisitHas(HasExpression expression, QueryClauseBuilder
4039
{
4140
Expression property = Visit(expression.TargetCollection, context);
4241

43-
Type? elementType = CollectionConverter.FindCollectionElementType(property.Type);
42+
Type? elementType = CollectionConverter.Instance.FindCollectionElementType(property.Type);
4443

4544
if (elementType == null)
4645
{

src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ namespace JsonApiDotNetCore.Repositories;
3131
public class EntityFrameworkCoreRepository<TResource, TId> : IResourceRepository<TResource, TId>, IRepositorySupportsTransaction
3232
where TResource : class, IIdentifiable<TId>
3333
{
34-
private readonly CollectionConverter _collectionConverter = new();
3534
private readonly ITargetedFields _targetedFields;
3635
private readonly DbContext _dbContext;
3736
private readonly IResourceGraph _resourceGraph;
@@ -250,7 +249,7 @@ public virtual async Task CreateAsync(TResource resourceFromRequest, TResource r
250249

251250
if (relationship is HasManyAttribute hasManyRelationship)
252251
{
253-
HashSet<IIdentifiable> rightResourceIds = _collectionConverter.ExtractResources(rightValue).ToHashSet(IdentifiableComparer.Instance);
252+
HashSet<IIdentifiable> rightResourceIds = CollectionConverter.Instance.ExtractResources(rightValue).ToHashSet(IdentifiableComparer.Instance);
254253

255254
await _resourceDefinitionAccessor.OnSetToManyRelationshipAsync(leftResource, hasManyRelationship, rightResourceIds, writeOperation,
256255
cancellationToken);
@@ -482,14 +481,14 @@ private ISet<IIdentifiable> GetRightValueToStoreForAddToToMany(TResource leftRes
482481
object? rightValueStored = relationship.GetValue(leftResource);
483482

484483
// @formatter:wrap_chained_method_calls chop_always
485-
// @formatter:wrap_before_first_method_call true
484+
// @formatter:wrap_after_property_in_chained_method_calls true
486485

487-
HashSet<IIdentifiable> rightResourceIdsStored = _collectionConverter
486+
HashSet<IIdentifiable> rightResourceIdsStored = CollectionConverter.Instance
488487
.ExtractResources(rightValueStored)
489488
.Select(_dbContext.GetTrackedOrAttach)
490489
.ToHashSet(IdentifiableComparer.Instance);
491490

492-
// @formatter:wrap_before_first_method_call restore
491+
// @formatter:wrap_after_property_in_chained_method_calls restore
493492
// @formatter:wrap_chained_method_calls restore
494493

495494
if (rightResourceIdsStored.Count > 0)
@@ -531,18 +530,18 @@ public virtual async Task RemoveFromToManyRelationshipAsync(TResource leftResour
531530
object? rightValueStored = relationship.GetValue(leftResourceTracked);
532531

533532
// @formatter:wrap_chained_method_calls chop_always
534-
// @formatter:wrap_before_first_method_call true
533+
// @formatter:wrap_after_property_in_chained_method_calls true
535534

536-
IIdentifiable[] rightResourceIdsStored = _collectionConverter
535+
IIdentifiable[] rightResourceIdsStored = CollectionConverter.Instance
537536
.ExtractResources(rightValueStored)
538537
.Concat(extraResourceIdsToRemove)
539538
.Select(_dbContext.GetTrackedOrAttach)
540539
.ToArray();
541540

542-
// @formatter:wrap_before_first_method_call restore
541+
// @formatter:wrap_after_property_in_chained_method_calls restore
543542
// @formatter:wrap_chained_method_calls restore
544543

545-
rightValueStored = _collectionConverter.CopyToTypedCollection(rightResourceIdsStored, relationship.Property.PropertyType);
544+
rightValueStored = CollectionConverter.Instance.CopyToTypedCollection(rightResourceIdsStored, relationship.Property.PropertyType);
546545
relationship.SetValue(leftResourceTracked, rightValueStored);
547546

548547
MarkRelationshipAsLoaded(leftResourceTracked, relationship);
@@ -624,11 +623,11 @@ protected async Task UpdateRelationshipAsync(RelationshipAttribute relationship,
624623
return null;
625624
}
626625

627-
IReadOnlyCollection<IIdentifiable> rightResources = _collectionConverter.ExtractResources(rightValue);
626+
IReadOnlyCollection<IIdentifiable> rightResources = CollectionConverter.Instance.ExtractResources(rightValue);
628627
IIdentifiable[] rightResourcesTracked = rightResources.Select(_dbContext.GetTrackedOrAttach).ToArray();
629628

630629
return rightValue is IEnumerable
631-
? _collectionConverter.CopyToTypedCollection(rightResourcesTracked, relationshipPropertyType)
630+
? CollectionConverter.Instance.CopyToTypedCollection(rightResourcesTracked, relationshipPropertyType)
632631
: rightResourcesTracked.Single();
633632
}
634633

src/JsonApiDotNetCore/Resources/OperationContainer.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ namespace JsonApiDotNetCore.Resources;
1010
[PublicAPI]
1111
public sealed class OperationContainer
1212
{
13-
private static readonly CollectionConverter CollectionConverter = new();
14-
1513
public IIdentifiable Resource { get; }
1614
public ITargetedFields TargetedFields { get; }
1715
public IJsonApiRequest Request { get; }
@@ -56,7 +54,7 @@ public ISet<IIdentifiable> GetSecondaryResources()
5654
private void AddSecondaryResources(RelationshipAttribute relationship, HashSet<IIdentifiable> secondaryResources)
5755
{
5856
object? rightValue = relationship.GetValue(Resource);
59-
IReadOnlyCollection<IIdentifiable> rightResources = CollectionConverter.ExtractResources(rightValue);
57+
IReadOnlyCollection<IIdentifiable> rightResources = CollectionConverter.Instance.ExtractResources(rightValue);
6058

6159
secondaryResources.UnionWith(rightResources);
6260
}

0 commit comments

Comments
 (0)