@@ -182,16 +182,14 @@ public virtual async Task CreateAsync(TResource resourceFromRequest, TResource r
182
182
183
183
using IDisposable _ = CodeTimingSessionManager . Current . Measure ( "Repository - Create resource" ) ;
184
184
185
- using var collector = new PlaceholderResourceCollector ( _resourceFactory , _dbContext ) ;
186
-
187
185
foreach ( RelationshipAttribute relationship in _targetedFields . Relationships )
188
186
{
189
187
object rightValue = relationship . GetValue ( resourceFromRequest ) ;
190
188
191
189
object rightValueEvaluated = await VisitSetRelationshipAsync ( resourceForDatabase , relationship , rightValue , WriteOperationKind . CreateResource ,
192
190
cancellationToken ) ;
193
191
194
- await UpdateRelationshipAsync ( relationship , resourceForDatabase , rightValueEvaluated , collector , cancellationToken ) ;
192
+ await UpdateRelationshipAsync ( relationship , resourceForDatabase , rightValueEvaluated , cancellationToken ) ;
195
193
}
196
194
197
195
foreach ( AttrAttribute attribute in _targetedFields . Attributes )
@@ -207,6 +205,8 @@ public virtual async Task CreateAsync(TResource resourceFromRequest, TResource r
207
205
await SaveChangesAsync ( cancellationToken ) ;
208
206
209
207
await _resourceDefinitionAccessor . OnWriteSucceededAsync ( resourceForDatabase , WriteOperationKind . CreateResource , cancellationToken ) ;
208
+
209
+ _dbContext . ResetChangeTracker ( ) ;
210
210
}
211
211
212
212
private async Task < object > VisitSetRelationshipAsync ( TResource leftResource , RelationshipAttribute relationship , object rightValue ,
@@ -254,8 +254,6 @@ public virtual async Task UpdateAsync(TResource resourceFromRequest, TResource r
254
254
255
255
using IDisposable _ = CodeTimingSessionManager . Current . Measure ( "Repository - Update resource" ) ;
256
256
257
- using var collector = new PlaceholderResourceCollector ( _resourceFactory , _dbContext ) ;
258
-
259
257
foreach ( RelationshipAttribute relationship in _targetedFields . Relationships )
260
258
{
261
259
object rightValue = relationship . GetValue ( resourceFromRequest ) ;
@@ -265,7 +263,7 @@ public virtual async Task UpdateAsync(TResource resourceFromRequest, TResource r
265
263
266
264
AssertIsNotClearingRequiredRelationship ( relationship , resourceFromDatabase , rightValueEvaluated ) ;
267
265
268
- await UpdateRelationshipAsync ( relationship , resourceFromDatabase , rightValueEvaluated , collector , cancellationToken ) ;
266
+ await UpdateRelationshipAsync ( relationship , resourceFromDatabase , rightValueEvaluated , cancellationToken ) ;
269
267
}
270
268
271
269
foreach ( AttrAttribute attribute in _targetedFields . Attributes )
@@ -278,18 +276,21 @@ public virtual async Task UpdateAsync(TResource resourceFromRequest, TResource r
278
276
await SaveChangesAsync ( cancellationToken ) ;
279
277
280
278
await _resourceDefinitionAccessor . OnWriteSucceededAsync ( resourceFromDatabase , WriteOperationKind . UpdateResource , cancellationToken ) ;
279
+
280
+ _dbContext . ResetChangeTracker ( ) ;
281
281
}
282
282
283
283
protected void AssertIsNotClearingRequiredRelationship ( RelationshipAttribute relationship , TResource leftResource , object rightValue )
284
284
{
285
- bool relationshipIsRequired = false ;
286
-
287
- if ( relationship is not HasManyAttribute { IsManyToMany : true } )
285
+ if ( relationship is HasManyAttribute { IsManyToMany : true } )
288
286
{
289
- INavigation navigation = TryGetNavigation ( relationship ) ;
290
- relationshipIsRequired = navigation ? . ForeignKey ? . IsRequired ?? false ;
287
+ // Many-to-many relationships cannot be required.
288
+ return ;
291
289
}
292
290
291
+ INavigation navigation = TryGetNavigation ( relationship ) ;
292
+ bool relationshipIsRequired = navigation ? . ForeignKey ? . IsRequired ?? false ;
293
+
293
294
bool relationshipIsBeingCleared = relationship is HasManyAttribute hasManyRelationship
294
295
? IsToManyRelationshipBeingCleared ( hasManyRelationship , leftResource , rightValue )
295
296
: rightValue == null ;
@@ -326,31 +327,30 @@ public virtual async Task DeleteAsync(TId id, CancellationToken cancellationToke
326
327
using IDisposable _ = CodeTimingSessionManager . Current . Measure ( "Repository - Delete resource" ) ;
327
328
328
329
// This enables OnWritingAsync() to fetch the resource, which adds it to the change tracker.
329
- // If so, we'll reuse the tracked resource instead of a placeholder resource.
330
- var emptyResource = _resourceFactory . CreateInstance < TResource > ( ) ;
331
- emptyResource . Id = id ;
330
+ // If so, we'll reuse the tracked resource instead of this placeholder resource.
331
+ var placeholderResource = _resourceFactory . CreateInstance < TResource > ( ) ;
332
+ placeholderResource . Id = id ;
332
333
333
- await _resourceDefinitionAccessor . OnWritingAsync ( emptyResource , WriteOperationKind . DeleteResource , cancellationToken ) ;
334
+ await _resourceDefinitionAccessor . OnWritingAsync ( placeholderResource , WriteOperationKind . DeleteResource , cancellationToken ) ;
334
335
335
- using var collector = new PlaceholderResourceCollector ( _resourceFactory , _dbContext ) ;
336
- TResource resource = collector . CreateForId < TResource , TId > ( id ) ;
336
+ var resourceTracked = ( TResource ) _dbContext . GetTrackedOrAttach ( placeholderResource ) ;
337
337
338
338
foreach ( RelationshipAttribute relationship in _resourceGraph . GetResourceContext < TResource > ( ) . Relationships )
339
339
{
340
340
// Loads the data of the relationship, if in EF Core it is configured in such a way that loading the related
341
341
// entities into memory is required for successfully executing the selected deletion behavior.
342
342
if ( RequiresLoadOfRelationshipForDeletion ( relationship ) )
343
343
{
344
- NavigationEntry navigation = GetNavigationEntry ( resource , relationship ) ;
344
+ NavigationEntry navigation = GetNavigationEntry ( resourceTracked , relationship ) ;
345
345
await navigation . LoadAsync ( cancellationToken ) ;
346
346
}
347
347
}
348
348
349
- _dbContext . Remove ( resource ) ;
349
+ _dbContext . Remove ( resourceTracked ) ;
350
350
351
351
await SaveChangesAsync ( cancellationToken ) ;
352
352
353
- await _resourceDefinitionAccessor . OnWriteSucceededAsync ( resource , WriteOperationKind . DeleteResource , cancellationToken ) ;
353
+ await _resourceDefinitionAccessor . OnWriteSucceededAsync ( resourceTracked , WriteOperationKind . DeleteResource , cancellationToken ) ;
354
354
}
355
355
356
356
private NavigationEntry GetNavigationEntry ( TResource resource , RelationshipAttribute relationship )
@@ -413,8 +413,7 @@ public virtual async Task SetRelationshipAsync(TResource leftResource, object ri
413
413
414
414
AssertIsNotClearingRequiredRelationship ( relationship , leftResource , rightValueEvaluated ) ;
415
415
416
- using var collector = new PlaceholderResourceCollector ( _resourceFactory , _dbContext ) ;
417
- await UpdateRelationshipAsync ( relationship , leftResource , rightValueEvaluated , collector , cancellationToken ) ;
416
+ await UpdateRelationshipAsync ( relationship , leftResource , rightValueEvaluated , cancellationToken ) ;
418
417
419
418
await _resourceDefinitionAccessor . OnWritingAsync ( leftResource , WriteOperationKind . SetRelationship , cancellationToken ) ;
420
419
@@ -442,16 +441,18 @@ public virtual async Task AddToToManyRelationshipAsync(TId leftId, ISet<IIdentif
442
441
443
442
if ( rightResourceIds . Any ( ) )
444
443
{
445
- using var collector = new PlaceholderResourceCollector ( _resourceFactory , _dbContext ) ;
446
- TResource leftResource = collector . CreateForId < TResource , TId > ( leftId ) ;
444
+ var leftPlaceholderResource = _resourceFactory . CreateInstance < TResource > ( ) ;
445
+ leftPlaceholderResource . Id = leftId ;
447
446
448
- await UpdateRelationshipAsync ( relationship , leftResource , rightResourceIds , collector , cancellationToken ) ;
447
+ var leftResourceTracked = ( TResource ) _dbContext . GetTrackedOrAttach ( leftPlaceholderResource ) ;
449
448
450
- await _resourceDefinitionAccessor . OnWritingAsync ( leftResource , WriteOperationKind . AddToRelationship , cancellationToken ) ;
449
+ await UpdateRelationshipAsync ( relationship , leftResourceTracked , rightResourceIds , cancellationToken ) ;
450
+
451
+ await _resourceDefinitionAccessor . OnWritingAsync ( leftResourceTracked , WriteOperationKind . AddToRelationship , cancellationToken ) ;
451
452
452
453
await SaveChangesAsync ( cancellationToken ) ;
453
454
454
- await _resourceDefinitionAccessor . OnWriteSucceededAsync ( leftResource , WriteOperationKind . AddToRelationship , cancellationToken ) ;
455
+ await _resourceDefinitionAccessor . OnWriteSucceededAsync ( leftResourceTracked , WriteOperationKind . AddToRelationship , cancellationToken ) ;
455
456
}
456
457
}
457
458
@@ -470,35 +471,62 @@ public virtual async Task RemoveFromToManyRelationshipAsync(TResource leftResour
470
471
using IDisposable _ = CodeTimingSessionManager . Current . Measure ( "Repository - Remove from to-many relationship" ) ;
471
472
472
473
var relationship = ( HasManyAttribute ) _targetedFields . Relationships . Single ( ) ;
474
+ HashSet < IIdentifiable > rightResourceIdsToRemove = rightResourceIds . ToHashSet ( IdentifiableComparer . Instance ) ;
473
475
474
- await _resourceDefinitionAccessor . OnRemoveFromRelationshipAsync ( leftResource , relationship , rightResourceIds , cancellationToken ) ;
476
+ await _resourceDefinitionAccessor . OnRemoveFromRelationshipAsync ( leftResource , relationship , rightResourceIdsToRemove , cancellationToken ) ;
475
477
476
- if ( rightResourceIds . Any ( ) )
478
+ if ( rightResourceIdsToRemove . Any ( ) )
477
479
{
480
+ var leftResourceTracked = ( TResource ) _dbContext . GetTrackedOrAttach ( leftResource ) ;
481
+
482
+ // Make EF Core believe any additional resources added from ResourceDefinition already exist in database.
483
+ IIdentifiable [ ] extraResourceIdsToRemove = rightResourceIdsToRemove . Where ( rightId => ! rightResourceIds . Contains ( rightId ) ) . ToArray ( ) ;
484
+
478
485
object rightValueStored = relationship . GetValue ( leftResource ) ;
479
486
480
- HashSet < IIdentifiable > rightResourceIdsToStore =
481
- _collectionConverter . ExtractResources ( rightValueStored ) . ToHashSet ( IdentifiableComparer . Instance ) ;
487
+ // @formatter:wrap_chained_method_calls chop_always
488
+ // @formatter:keep_existing_linebreaks true
482
489
483
- rightResourceIdsToStore . ExceptWith ( rightResourceIds ) ;
490
+ IIdentifiable [ ] rightResourceIdsStored = _collectionConverter
491
+ . ExtractResources ( rightValueStored )
492
+ . Concat ( extraResourceIdsToRemove )
493
+ . Select ( rightResource => _dbContext . GetTrackedOrAttach ( rightResource ) )
494
+ . ToArray ( ) ;
495
+
496
+ // @formatter:keep_existing_linebreaks restore
497
+ // @formatter:wrap_chained_method_calls restore
498
+
499
+ rightValueStored = _collectionConverter . CopyToTypedCollection ( rightResourceIdsStored , relationship . Property . PropertyType ) ;
500
+ relationship . SetValue ( leftResource , rightValueStored ) ;
501
+
502
+ MarkRelationshipAsLoaded ( leftResource , relationship ) ;
484
503
485
- AssertIsNotClearingRequiredRelationship ( relationship , leftResource , rightResourceIdsToStore ) ;
504
+ HashSet < IIdentifiable > rightResourceIdsToStore = rightResourceIdsStored . ToHashSet ( IdentifiableComparer . Instance ) ;
505
+ rightResourceIdsToStore . ExceptWith ( rightResourceIdsToRemove ) ;
486
506
487
- using var collector = new PlaceholderResourceCollector ( _resourceFactory , _dbContext ) ;
488
- await UpdateRelationshipAsync ( relationship , leftResource , rightResourceIdsToStore , collector , cancellationToken ) ;
507
+ AssertIsNotClearingRequiredRelationship ( relationship , leftResourceTracked , rightResourceIdsToStore ) ;
489
508
490
- await _resourceDefinitionAccessor . OnWritingAsync ( leftResource , WriteOperationKind . RemoveFromRelationship , cancellationToken ) ;
509
+ await UpdateRelationshipAsync ( relationship , leftResourceTracked , rightResourceIdsToStore , cancellationToken ) ;
510
+
511
+ await _resourceDefinitionAccessor . OnWritingAsync ( leftResourceTracked , WriteOperationKind . RemoveFromRelationship , cancellationToken ) ;
491
512
492
513
await SaveChangesAsync ( cancellationToken ) ;
493
514
494
- await _resourceDefinitionAccessor . OnWriteSucceededAsync ( leftResource , WriteOperationKind . RemoveFromRelationship , cancellationToken ) ;
515
+ await _resourceDefinitionAccessor . OnWriteSucceededAsync ( leftResourceTracked , WriteOperationKind . RemoveFromRelationship , cancellationToken ) ;
495
516
}
496
517
}
497
518
519
+ private void MarkRelationshipAsLoaded ( TResource leftResource , RelationshipAttribute relationship )
520
+ {
521
+ EntityEntry < TResource > leftEntry = _dbContext . Entry ( leftResource ) ;
522
+ CollectionEntry rightCollectionEntry = leftEntry . Collection ( relationship . Property . Name ) ;
523
+ rightCollectionEntry . IsLoaded = true ;
524
+ }
525
+
498
526
protected async Task UpdateRelationshipAsync ( RelationshipAttribute relationship , TResource leftResource , object valueToAssign ,
499
- PlaceholderResourceCollector collector , CancellationToken cancellationToken )
527
+ CancellationToken cancellationToken )
500
528
{
501
- object trackedValueToAssign = EnsureRelationshipValueToAssignIsTracked ( valueToAssign , relationship . Property . PropertyType , collector ) ;
529
+ object trackedValueToAssign = EnsureRelationshipValueToAssignIsTracked ( valueToAssign , relationship . Property . PropertyType ) ;
502
530
503
531
if ( RequireLoadOfInverseRelationship ( relationship , trackedValueToAssign ) )
504
532
{
@@ -511,15 +539,15 @@ protected async Task UpdateRelationshipAsync(RelationshipAttribute relationship,
511
539
relationship . SetValue ( leftResource , trackedValueToAssign ) ;
512
540
}
513
541
514
- private object EnsureRelationshipValueToAssignIsTracked ( object rightValue , Type relationshipPropertyType , PlaceholderResourceCollector collector )
542
+ private object EnsureRelationshipValueToAssignIsTracked ( object rightValue , Type relationshipPropertyType )
515
543
{
516
544
if ( rightValue == null )
517
545
{
518
546
return null ;
519
547
}
520
548
521
549
ICollection < IIdentifiable > rightResources = _collectionConverter . ExtractResources ( rightValue ) ;
522
- IIdentifiable [ ] rightResourcesTracked = rightResources . Select ( collector . CaptureExisting ) . ToArray ( ) ;
550
+ IIdentifiable [ ] rightResourcesTracked = rightResources . Select ( rightResource => _dbContext . GetTrackedOrAttach ( rightResource ) ) . ToArray ( ) ;
523
551
524
552
return rightValue is IEnumerable
525
553
? _collectionConverter . CopyToTypedCollection ( rightResourcesTracked , relationshipPropertyType )
@@ -551,6 +579,8 @@ protected virtual async Task SaveChangesAsync(CancellationToken cancellationToke
551
579
await _dbContext . Database . CurrentTransaction . RollbackAsync ( cancellationToken ) ;
552
580
}
553
581
582
+ _dbContext . ResetChangeTracker ( ) ;
583
+
554
584
throw new DataStoreUpdateException ( exception ) ;
555
585
}
556
586
}
0 commit comments