Skip to content

Commit bf1072b

Browse files
author
Bart Koelman
committed
Refactor callback wire-ups, adds support for relationships
1 parent 0715d0e commit bf1072b

File tree

10 files changed

+388
-241
lines changed

10 files changed

+388
-241
lines changed
Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,39 @@
11
namespace JsonApiDotNetCore.Middleware
22
{
33
/// <summary>
4-
/// Lists the functional operation kinds from an atomic:operations request.
4+
/// Lists the functional operation kinds from a resource request or an atomic:operations request.
55
/// </summary>
66
public enum OperationKind
77
{
8+
/// <summary>
9+
/// Create a new resource with attributes, relationships or both.
10+
/// </summary>
811
CreateResource,
12+
13+
/// <summary>
14+
/// Update the attributes and/or relationships of an existing resource. Only the values of sent attributes are replaced. And only the values of sent
15+
/// relationships are replaced.
16+
/// </summary>
917
UpdateResource,
18+
19+
/// <summary>
20+
/// Delete an existing resource.
21+
/// </summary>
1022
DeleteResource,
23+
24+
/// <summary>
25+
/// Perform a complete replacement of a relationship on an existing resource.
26+
/// </summary>
1127
SetRelationship,
28+
29+
/// <summary>
30+
/// Add resources to a to-many relationship.
31+
/// </summary>
1232
AddToRelationship,
33+
34+
/// <summary>
35+
/// Remove resources from a to-many relationship.
36+
/// </summary>
1337
RemoveFromRelationship
1438
}
1539
}

src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs

Lines changed: 86 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -172,20 +172,48 @@ public virtual async Task CreateAsync(TResource resourceFromRequest, TResource r
172172
foreach (RelationshipAttribute relationship in _targetedFields.Relationships)
173173
{
174174
object rightResources = relationship.GetValue(resourceFromRequest);
175-
await UpdateRelationshipAsync(relationship, resourceForDatabase, rightResources, collector, cancellationToken);
175+
176+
object rightResourcesEdited = await EditRelationshipAsync(resourceForDatabase, relationship, rightResources, OperationKind.CreateResource,
177+
cancellationToken);
178+
179+
await UpdateRelationshipAsync(relationship, resourceForDatabase, rightResourcesEdited, collector, cancellationToken);
176180
}
177181

178182
foreach (AttrAttribute attribute in _targetedFields.Attributes)
179183
{
180184
attribute.SetValue(resourceForDatabase, attribute.GetValue(resourceFromRequest));
181185
}
182186

183-
await _resourceDefinitionAccessor.OnBeforeCreateResourceAsync(resourceForDatabase, cancellationToken);
187+
await _resourceDefinitionAccessor.OnWritingAsync(resourceForDatabase, OperationKind.CreateResource, cancellationToken);
184188

185189
DbSet<TResource> dbSet = _dbContext.Set<TResource>();
186190
await dbSet.AddAsync(resourceForDatabase, cancellationToken);
187191

188192
await SaveChangesAsync(cancellationToken);
193+
194+
await _resourceDefinitionAccessor.OnWriteSucceededAsync(resourceForDatabase, OperationKind.CreateResource, cancellationToken);
195+
}
196+
197+
private async Task<object> EditRelationshipAsync(TResource leftResource, RelationshipAttribute relationship, object rightResourceIds,
198+
OperationKind operationKind, CancellationToken cancellationToken)
199+
{
200+
if (relationship is HasOneAttribute hasOneRelationship)
201+
{
202+
return await _resourceDefinitionAccessor.OnSetToOneRelationshipAsync(leftResource, hasOneRelationship, (IIdentifiable)rightResourceIds,
203+
operationKind, cancellationToken);
204+
}
205+
206+
if (relationship is HasManyAttribute hasManyRelationship)
207+
{
208+
HashSet<IIdentifiable> rightResourceIdSet = _collectionConverter.ExtractResources(rightResourceIds).ToHashSet(IdentifiableComparer.Instance);
209+
210+
await _resourceDefinitionAccessor.OnSetToManyRelationshipAsync(leftResource, hasManyRelationship, rightResourceIdSet, operationKind,
211+
cancellationToken);
212+
213+
return rightResourceIdSet;
214+
}
215+
216+
return rightResourceIds;
189217
}
190218

191219
/// <inheritdoc />
@@ -213,19 +241,24 @@ public virtual async Task UpdateAsync(TResource resourceFromRequest, TResource r
213241
{
214242
object rightResources = relationship.GetValue(resourceFromRequest);
215243

216-
AssertIsNotClearingRequiredRelationship(relationship, resourceFromDatabase, rightResources);
244+
object rightResourcesEdited = await EditRelationshipAsync(resourceFromDatabase, relationship, rightResources, OperationKind.UpdateResource,
245+
cancellationToken);
217246

218-
await UpdateRelationshipAsync(relationship, resourceFromDatabase, rightResources, collector, cancellationToken);
247+
AssertIsNotClearingRequiredRelationship(relationship, resourceFromDatabase, rightResourcesEdited);
248+
249+
await UpdateRelationshipAsync(relationship, resourceFromDatabase, rightResourcesEdited, collector, cancellationToken);
219250
}
220251

221252
foreach (AttrAttribute attribute in _targetedFields.Attributes)
222253
{
223254
attribute.SetValue(resourceFromDatabase, attribute.GetValue(resourceFromRequest));
224255
}
225256

226-
await _resourceDefinitionAccessor.OnBeforeUpdateResourceAsync(resourceFromDatabase, cancellationToken);
257+
await _resourceDefinitionAccessor.OnWritingAsync(resourceFromDatabase, OperationKind.UpdateResource, cancellationToken);
227258

228259
await SaveChangesAsync(cancellationToken);
260+
261+
await _resourceDefinitionAccessor.OnWriteSucceededAsync(resourceFromDatabase, OperationKind.UpdateResource, cancellationToken);
229262
}
230263

231264
protected void AssertIsNotClearingRequiredRelationship(RelationshipAttribute relationship, TResource leftResource, object rightValue)
@@ -238,9 +271,9 @@ protected void AssertIsNotClearingRequiredRelationship(RelationshipAttribute rel
238271
relationshipIsRequired = navigation?.ForeignKey?.IsRequired ?? false;
239272
}
240273

241-
bool relationshipIsBeingCleared = relationship is HasOneAttribute
242-
? rightValue == null
243-
: IsToManyRelationshipBeingCleared(relationship, leftResource, rightValue);
274+
bool relationshipIsBeingCleared = relationship is HasManyAttribute hasManyRelationship
275+
? IsToManyRelationshipBeingCleared(hasManyRelationship, leftResource, rightValue)
276+
: rightValue == null;
244277

245278
if (relationshipIsRequired && relationshipIsBeingCleared)
246279
{
@@ -249,11 +282,11 @@ protected void AssertIsNotClearingRequiredRelationship(RelationshipAttribute rel
249282
}
250283
}
251284

252-
private bool IsToManyRelationshipBeingCleared(RelationshipAttribute relationship, TResource leftResource, object valueToAssign)
285+
private bool IsToManyRelationshipBeingCleared(HasManyAttribute hasManyRelationship, TResource leftResource, object valueToAssign)
253286
{
254287
ICollection<IIdentifiable> newRightResourceIds = _collectionConverter.ExtractResources(valueToAssign);
255288

256-
object existingRightValue = relationship.GetValue(leftResource);
289+
object existingRightValue = hasManyRelationship.GetValue(leftResource);
257290

258291
HashSet<IIdentifiable> existingRightResourceIds =
259292
_collectionConverter.ExtractResources(existingRightValue).ToHashSet(IdentifiableComparer.Instance);
@@ -271,6 +304,13 @@ public virtual async Task DeleteAsync(TId id, CancellationToken cancellationToke
271304
id
272305
});
273306

307+
// This enables OnWritingAsync() to fetch the resource, which adds it to the change tracker.
308+
// If so, we'll reuse the tracked resource instead of a placeholder resource.
309+
var emptyResource = _resourceFactory.CreateInstance<TResource>();
310+
emptyResource.Id = id;
311+
312+
await _resourceDefinitionAccessor.OnWritingAsync(emptyResource, OperationKind.DeleteResource, cancellationToken);
313+
274314
using var collector = new PlaceholderResourceCollector(_resourceFactory, _dbContext);
275315
TResource resource = collector.CreateForId<TResource, TId>(id);
276316

@@ -288,6 +328,8 @@ public virtual async Task DeleteAsync(TId id, CancellationToken cancellationToke
288328
_dbContext.Remove(resource);
289329

290330
await SaveChangesAsync(cancellationToken);
331+
332+
await _resourceDefinitionAccessor.OnWriteSucceededAsync(resource, OperationKind.DeleteResource, cancellationToken);
291333
}
292334

293335
private NavigationEntry GetNavigationEntry(TResource resource, RelationshipAttribute relationship)
@@ -349,12 +391,19 @@ public virtual async Task SetRelationshipAsync(TResource primaryResource, object
349391

350392
RelationshipAttribute relationship = _targetedFields.Relationships.Single();
351393

352-
AssertIsNotClearingRequiredRelationship(relationship, primaryResource, secondaryResourceIds);
394+
object secondaryResourceIdsEdited =
395+
await EditRelationshipAsync(primaryResource, relationship, secondaryResourceIds, OperationKind.SetRelationship, cancellationToken);
396+
397+
AssertIsNotClearingRequiredRelationship(relationship, primaryResource, secondaryResourceIdsEdited);
353398

354399
using var collector = new PlaceholderResourceCollector(_resourceFactory, _dbContext);
355-
await UpdateRelationshipAsync(relationship, primaryResource, secondaryResourceIds, collector, cancellationToken);
400+
await UpdateRelationshipAsync(relationship, primaryResource, secondaryResourceIdsEdited, collector, cancellationToken);
401+
402+
await _resourceDefinitionAccessor.OnWritingAsync(primaryResource, OperationKind.SetRelationship, cancellationToken);
356403

357404
await SaveChangesAsync(cancellationToken);
405+
406+
await _resourceDefinitionAccessor.OnWriteSucceededAsync(primaryResource, OperationKind.SetRelationship, cancellationToken);
358407
}
359408

360409
/// <inheritdoc />
@@ -368,7 +417,9 @@ public virtual async Task AddToToManyRelationshipAsync(TId primaryId, ISet<IIden
368417

369418
ArgumentGuard.NotNull(secondaryResourceIds, nameof(secondaryResourceIds));
370419

371-
RelationshipAttribute relationship = _targetedFields.Relationships.Single();
420+
var relationship = (HasManyAttribute)_targetedFields.Relationships.Single();
421+
422+
await _resourceDefinitionAccessor.OnAddToRelationshipAsync<TResource, TId>(primaryId, relationship, secondaryResourceIds, cancellationToken);
372423

373424
if (secondaryResourceIds.Any())
374425
{
@@ -377,7 +428,11 @@ public virtual async Task AddToToManyRelationshipAsync(TId primaryId, ISet<IIden
377428

378429
await UpdateRelationshipAsync(relationship, primaryResource, secondaryResourceIds, collector, cancellationToken);
379430

431+
await _resourceDefinitionAccessor.OnWritingAsync(primaryResource, OperationKind.AddToRelationship, cancellationToken);
432+
380433
await SaveChangesAsync(cancellationToken);
434+
435+
await _resourceDefinitionAccessor.OnWriteSucceededAsync(primaryResource, OperationKind.AddToRelationship, cancellationToken);
381436
}
382437
}
383438

@@ -395,17 +450,26 @@ public virtual async Task RemoveFromToManyRelationshipAsync(TResource primaryRes
395450

396451
var relationship = (HasManyAttribute)_targetedFields.Relationships.Single();
397452

398-
object rightValue = relationship.GetValue(primaryResource);
453+
await _resourceDefinitionAccessor.OnRemoveFromRelationshipAsync(primaryResource, relationship, secondaryResourceIds, cancellationToken);
399454

400-
HashSet<IIdentifiable> rightResourceIds = _collectionConverter.ExtractResources(rightValue).ToHashSet(IdentifiableComparer.Instance);
401-
rightResourceIds.ExceptWith(secondaryResourceIds);
455+
if (secondaryResourceIds.Any())
456+
{
457+
object rightValue = relationship.GetValue(primaryResource);
402458

403-
AssertIsNotClearingRequiredRelationship(relationship, primaryResource, rightResourceIds);
459+
HashSet<IIdentifiable> rightResourceIds = _collectionConverter.ExtractResources(rightValue).ToHashSet(IdentifiableComparer.Instance);
460+
rightResourceIds.ExceptWith(secondaryResourceIds);
404461

405-
using var collector = new PlaceholderResourceCollector(_resourceFactory, _dbContext);
406-
await UpdateRelationshipAsync(relationship, primaryResource, rightResourceIds, collector, cancellationToken);
462+
AssertIsNotClearingRequiredRelationship(relationship, primaryResource, rightResourceIds);
407463

408-
await SaveChangesAsync(cancellationToken);
464+
using var collector = new PlaceholderResourceCollector(_resourceFactory, _dbContext);
465+
await UpdateRelationshipAsync(relationship, primaryResource, rightResourceIds, collector, cancellationToken);
466+
467+
await _resourceDefinitionAccessor.OnWritingAsync(primaryResource, OperationKind.RemoveFromRelationship, cancellationToken);
468+
469+
await SaveChangesAsync(cancellationToken);
470+
471+
await _resourceDefinitionAccessor.OnWriteSucceededAsync(primaryResource, OperationKind.RemoveFromRelationship, cancellationToken);
472+
}
409473
}
410474

411475
protected async Task UpdateRelationshipAsync(RelationshipAttribute relationship, TResource leftResource, object valueToAssign,
@@ -458,6 +522,8 @@ private bool IsOneToOneRelationship(RelationshipAttribute relationship)
458522

459523
protected virtual async Task SaveChangesAsync(CancellationToken cancellationToken)
460524
{
525+
cancellationToken.ThrowIfCancellationRequested();
526+
461527
try
462528
{
463529
await _dbContext.SaveChangesAsync(cancellationToken);

0 commit comments

Comments
 (0)