@@ -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 ;
@@ -325,32 +326,29 @@ public virtual async Task DeleteAsync(TId id, CancellationToken cancellationToke
325
326
326
327
using IDisposable _ = CodeTimingSessionManager . Current . Measure ( "Repository - Delete resource" ) ;
327
328
328
- // 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 ;
329
+ var placeholderResource = _resourceFactory . CreateInstance < TResource > ( ) ;
330
+ placeholderResource . Id = id ;
332
331
333
- await _resourceDefinitionAccessor . OnWritingAsync ( emptyResource , WriteOperationKind . DeleteResource , cancellationToken ) ;
332
+ await _resourceDefinitionAccessor . OnWritingAsync ( placeholderResource , WriteOperationKind . DeleteResource , cancellationToken ) ;
334
333
335
- using var collector = new PlaceholderResourceCollector ( _resourceFactory , _dbContext ) ;
336
- TResource resource = collector . CreateForId < TResource , TId > ( id ) ;
334
+ var resourceTracked = ( TResource ) _dbContext . GetTrackedOrAttach ( placeholderResource ) ;
337
335
338
336
foreach ( RelationshipAttribute relationship in _resourceGraph . GetResourceContext < TResource > ( ) . Relationships )
339
337
{
340
338
// Loads the data of the relationship, if in EF Core it is configured in such a way that loading the related
341
339
// entities into memory is required for successfully executing the selected deletion behavior.
342
340
if ( RequiresLoadOfRelationshipForDeletion ( relationship ) )
343
341
{
344
- NavigationEntry navigation = GetNavigationEntry ( resource , relationship ) ;
342
+ NavigationEntry navigation = GetNavigationEntry ( resourceTracked , relationship ) ;
345
343
await navigation . LoadAsync ( cancellationToken ) ;
346
344
}
347
345
}
348
346
349
- _dbContext . Remove ( resource ) ;
347
+ _dbContext . Remove ( resourceTracked ) ;
350
348
351
349
await SaveChangesAsync ( cancellationToken ) ;
352
350
353
- await _resourceDefinitionAccessor . OnWriteSucceededAsync ( resource , WriteOperationKind . DeleteResource , cancellationToken ) ;
351
+ await _resourceDefinitionAccessor . OnWriteSucceededAsync ( resourceTracked , WriteOperationKind . DeleteResource , cancellationToken ) ;
354
352
}
355
353
356
354
private NavigationEntry GetNavigationEntry ( TResource resource , RelationshipAttribute relationship )
@@ -413,8 +411,7 @@ public virtual async Task SetRelationshipAsync(TResource leftResource, object ri
413
411
414
412
AssertIsNotClearingRequiredRelationship ( relationship , leftResource , rightValueEvaluated ) ;
415
413
416
- using var collector = new PlaceholderResourceCollector ( _resourceFactory , _dbContext ) ;
417
- await UpdateRelationshipAsync ( relationship , leftResource , rightValueEvaluated , collector , cancellationToken ) ;
414
+ await UpdateRelationshipAsync ( relationship , leftResource , rightValueEvaluated , cancellationToken ) ;
418
415
419
416
await _resourceDefinitionAccessor . OnWritingAsync ( leftResource , WriteOperationKind . SetRelationship , cancellationToken ) ;
420
417
@@ -442,16 +439,18 @@ public virtual async Task AddToToManyRelationshipAsync(TId leftId, ISet<IIdentif
442
439
443
440
if ( rightResourceIds . Any ( ) )
444
441
{
445
- using var collector = new PlaceholderResourceCollector ( _resourceFactory , _dbContext ) ;
446
- TResource leftResource = collector . CreateForId < TResource , TId > ( leftId ) ;
442
+ var leftPlaceholderResource = _resourceFactory . CreateInstance < TResource > ( ) ;
443
+ leftPlaceholderResource . Id = leftId ;
447
444
448
- await UpdateRelationshipAsync ( relationship , leftResource , rightResourceIds , collector , cancellationToken ) ;
445
+ var leftResourceTracked = ( TResource ) _dbContext . GetTrackedOrAttach ( leftPlaceholderResource ) ;
449
446
450
- await _resourceDefinitionAccessor . OnWritingAsync ( leftResource , WriteOperationKind . AddToRelationship , cancellationToken ) ;
447
+ await UpdateRelationshipAsync ( relationship , leftResourceTracked , rightResourceIds , cancellationToken ) ;
448
+
449
+ await _resourceDefinitionAccessor . OnWritingAsync ( leftResourceTracked , WriteOperationKind . AddToRelationship , cancellationToken ) ;
451
450
452
451
await SaveChangesAsync ( cancellationToken ) ;
453
452
454
- await _resourceDefinitionAccessor . OnWriteSucceededAsync ( leftResource , WriteOperationKind . AddToRelationship , cancellationToken ) ;
453
+ await _resourceDefinitionAccessor . OnWriteSucceededAsync ( leftResourceTracked , WriteOperationKind . AddToRelationship , cancellationToken ) ;
455
454
}
456
455
}
457
456
@@ -470,35 +469,62 @@ public virtual async Task RemoveFromToManyRelationshipAsync(TResource leftResour
470
469
using IDisposable _ = CodeTimingSessionManager . Current . Measure ( "Repository - Remove from to-many relationship" ) ;
471
470
472
471
var relationship = ( HasManyAttribute ) _targetedFields . Relationships . Single ( ) ;
472
+ HashSet < IIdentifiable > rightResourceIdsToRemove = rightResourceIds . ToHashSet ( IdentifiableComparer . Instance ) ;
473
473
474
- await _resourceDefinitionAccessor . OnRemoveFromRelationshipAsync ( leftResource , relationship , rightResourceIds , cancellationToken ) ;
474
+ await _resourceDefinitionAccessor . OnRemoveFromRelationshipAsync ( leftResource , relationship , rightResourceIdsToRemove , cancellationToken ) ;
475
475
476
- if ( rightResourceIds . Any ( ) )
476
+ if ( rightResourceIdsToRemove . Any ( ) )
477
477
{
478
+ var leftResourceTracked = ( TResource ) _dbContext . GetTrackedOrAttach ( leftResource ) ;
479
+
480
+ // Make EF Core believe any additional resources added from ResourceDefinition already exist in database.
481
+ IIdentifiable [ ] extraResourceIdsToRemove = rightResourceIdsToRemove . Where ( rightId => ! rightResourceIds . Contains ( rightId ) ) . ToArray ( ) ;
482
+
478
483
object rightValueStored = relationship . GetValue ( leftResource ) ;
479
484
480
- HashSet < IIdentifiable > rightResourceIdsToStore =
481
- _collectionConverter . ExtractResources ( rightValueStored ) . ToHashSet ( IdentifiableComparer . Instance ) ;
485
+ // @formatter:wrap_chained_method_calls chop_always
486
+ // @formatter:keep_existing_linebreaks true
482
487
483
- rightResourceIdsToStore . ExceptWith ( rightResourceIds ) ;
488
+ IIdentifiable [ ] rightResourceIdsStored = _collectionConverter
489
+ . ExtractResources ( rightValueStored )
490
+ . Concat ( extraResourceIdsToRemove )
491
+ . Select ( rightResource => _dbContext . GetTrackedOrAttach ( rightResource ) )
492
+ . ToArray ( ) ;
493
+
494
+ // @formatter:keep_existing_linebreaks restore
495
+ // @formatter:wrap_chained_method_calls restore
496
+
497
+ rightValueStored = _collectionConverter . CopyToTypedCollection ( rightResourceIdsStored , relationship . Property . PropertyType ) ;
498
+ relationship . SetValue ( leftResource , rightValueStored ) ;
499
+
500
+ MarkRelationshipAsLoaded ( leftResource , relationship ) ;
484
501
485
- AssertIsNotClearingRequiredRelationship ( relationship , leftResource , rightResourceIdsToStore ) ;
502
+ HashSet < IIdentifiable > rightResourceIdsToStore = rightResourceIdsStored . ToHashSet ( IdentifiableComparer . Instance ) ;
503
+ rightResourceIdsToStore . ExceptWith ( rightResourceIdsToRemove ) ;
486
504
487
- using var collector = new PlaceholderResourceCollector ( _resourceFactory , _dbContext ) ;
488
- await UpdateRelationshipAsync ( relationship , leftResource , rightResourceIdsToStore , collector , cancellationToken ) ;
505
+ AssertIsNotClearingRequiredRelationship ( relationship , leftResourceTracked , rightResourceIdsToStore ) ;
489
506
490
- await _resourceDefinitionAccessor . OnWritingAsync ( leftResource , WriteOperationKind . RemoveFromRelationship , cancellationToken ) ;
507
+ await UpdateRelationshipAsync ( relationship , leftResourceTracked , rightResourceIdsToStore , cancellationToken ) ;
508
+
509
+ await _resourceDefinitionAccessor . OnWritingAsync ( leftResourceTracked , WriteOperationKind . RemoveFromRelationship , cancellationToken ) ;
491
510
492
511
await SaveChangesAsync ( cancellationToken ) ;
493
512
494
- await _resourceDefinitionAccessor . OnWriteSucceededAsync ( leftResource , WriteOperationKind . RemoveFromRelationship , cancellationToken ) ;
513
+ await _resourceDefinitionAccessor . OnWriteSucceededAsync ( leftResourceTracked , WriteOperationKind . RemoveFromRelationship , cancellationToken ) ;
495
514
}
496
515
}
497
516
517
+ private void MarkRelationshipAsLoaded ( TResource leftResource , RelationshipAttribute relationship )
518
+ {
519
+ EntityEntry < TResource > leftEntry = _dbContext . Entry ( leftResource ) ;
520
+ CollectionEntry rightCollectionEntry = leftEntry . Collection ( relationship . Property . Name ) ;
521
+ rightCollectionEntry . IsLoaded = true ;
522
+ }
523
+
498
524
protected async Task UpdateRelationshipAsync ( RelationshipAttribute relationship , TResource leftResource , object valueToAssign ,
499
- PlaceholderResourceCollector collector , CancellationToken cancellationToken )
525
+ CancellationToken cancellationToken )
500
526
{
501
- object trackedValueToAssign = EnsureRelationshipValueToAssignIsTracked ( valueToAssign , relationship . Property . PropertyType , collector ) ;
527
+ object trackedValueToAssign = EnsureRelationshipValueToAssignIsTracked ( valueToAssign , relationship . Property . PropertyType ) ;
502
528
503
529
if ( RequireLoadOfInverseRelationship ( relationship , trackedValueToAssign ) )
504
530
{
@@ -511,15 +537,15 @@ protected async Task UpdateRelationshipAsync(RelationshipAttribute relationship,
511
537
relationship . SetValue ( leftResource , trackedValueToAssign ) ;
512
538
}
513
539
514
- private object EnsureRelationshipValueToAssignIsTracked ( object rightValue , Type relationshipPropertyType , PlaceholderResourceCollector collector )
540
+ private object EnsureRelationshipValueToAssignIsTracked ( object rightValue , Type relationshipPropertyType )
515
541
{
516
542
if ( rightValue == null )
517
543
{
518
544
return null ;
519
545
}
520
546
521
547
ICollection < IIdentifiable > rightResources = _collectionConverter . ExtractResources ( rightValue ) ;
522
- IIdentifiable [ ] rightResourcesTracked = rightResources . Select ( collector . CaptureExisting ) . ToArray ( ) ;
548
+ IIdentifiable [ ] rightResourcesTracked = rightResources . Select ( rightResource => _dbContext . GetTrackedOrAttach ( rightResource ) ) . ToArray ( ) ;
523
549
524
550
return rightValue is IEnumerable
525
551
? _collectionConverter . CopyToTypedCollection ( rightResourcesTracked , relationshipPropertyType )
@@ -551,6 +577,8 @@ protected virtual async Task SaveChangesAsync(CancellationToken cancellationToke
551
577
await _dbContext . Database . CurrentTransaction . RollbackAsync ( cancellationToken ) ;
552
578
}
553
579
580
+ _dbContext . ResetChangeTracker ( ) ;
581
+
554
582
throw new DataStoreUpdateException ( exception ) ;
555
583
}
556
584
}
0 commit comments