@@ -325,6 +325,10 @@ public virtual async Task DeleteAsync(TResource? resourceFromDatabase, TId id, C
325
325
326
326
var resourceTracked = ( TResource ) _dbContext . GetTrackedOrAttach ( placeholderResource ) ;
327
327
328
+
329
+ //EnsureIncomingNavigationsAreTracked(resourceTracked);
330
+
331
+
328
332
foreach ( RelationshipAttribute relationship in _resourceGraph . GetResourceType < TResource > ( ) . Relationships )
329
333
{
330
334
// Loads the data of the relationship, if in Entity Framework Core it is configured in such a way that loading
@@ -335,6 +339,8 @@ public virtual async Task DeleteAsync(TResource? resourceFromDatabase, TId id, C
335
339
await navigation . LoadAsync ( cancellationToken ) ;
336
340
}
337
341
}
342
+
343
+
338
344
339
345
_dbContext . Remove ( resourceTracked ) ;
340
346
@@ -343,6 +349,114 @@ public virtual async Task DeleteAsync(TResource? resourceFromDatabase, TId id, C
343
349
await _resourceDefinitionAccessor . OnWriteSucceededAsync ( resourceTracked , WriteOperationKind . DeleteResource , cancellationToken ) ;
344
350
}
345
351
352
+ private void EnsureIncomingNavigationsAreTracked ( TResource resourceTracked )
353
+ {
354
+ IEntityType [ ] entityTypes = _dbContext . Model . GetEntityTypes ( ) . ToArray ( ) ;
355
+ IEntityType thisEntityType = entityTypes . Single ( entityType => entityType . ClrType == typeof ( TResource ) ) ;
356
+
357
+ HashSet < INavigation > navigationsToLoad = new ( ) ;
358
+
359
+ foreach ( INavigation navigation in entityTypes . SelectMany ( entityType => entityType . GetNavigations ( ) ) )
360
+ {
361
+ bool requiresLoad = navigation . IsOnDependent ? navigation . TargetEntityType == thisEntityType : navigation . DeclaringEntityType == thisEntityType ;
362
+
363
+ if ( requiresLoad && navigation . ForeignKey . DeleteBehavior == DeleteBehavior . ClientSetNull )
364
+ {
365
+ navigationsToLoad . Add ( navigation ) ;
366
+ }
367
+ }
368
+
369
+ // {Navigation: Customer.FirstOrder (Order) ToPrincipal Order}
370
+ // var query = from _dbContext.Set<Customer>().Where(customer => customer.FirstOrder == resourceTracked) // .Select(customer => customer.Id)
371
+
372
+ // {Navigation: Customer.LastOrder (Order) ToPrincipal Order}
373
+ // var query = from _dbContext.Set<Customer>().Where(customer => customer.LastOrder == resourceTracked) // .Select(customer => customer.Id)
374
+
375
+ // {Navigation: Order.Parent (Order) ToPrincipal Order}
376
+ // var query = from _dbContext.Set<Order>().Where(order => order.Parent == resourceTracked) // .Select(order => order.Id)
377
+
378
+ // {Navigation: ShoppingBasket.CurrentOrder (Order) ToPrincipal Order}
379
+ // var query = from _dbContext.Set<ShoppingBasket>().Where(shoppingBasket => shoppingBasket.CurrentOrder == resourceTracked) // .Select(shoppingBasket => shoppingBasket.Id)
380
+
381
+ var nameFactory = new LambdaParameterNameFactory ( ) ;
382
+ var scopeFactory = new LambdaScopeFactory ( nameFactory ) ;
383
+
384
+ foreach ( INavigation navigation in navigationsToLoad )
385
+ {
386
+ if ( ! navigation . IsOnDependent && navigation . Inverse != null )
387
+ {
388
+ // TODO: Handle the case where there is no inverse.
389
+ continue ;
390
+ }
391
+
392
+ IQueryable source = _dbContext . Set ( navigation . DeclaringEntityType . ClrType ) ;
393
+
394
+ using LambdaScope scope = scopeFactory . CreateScope ( source . ElementType ) ;
395
+
396
+ Expression expression ;
397
+
398
+ if ( navigation . IsCollection )
399
+ {
400
+ /*
401
+ {Navigation: WorkItem.Subscribers (ISet<UserAccount>) Collection ToDependent UserAccount}
402
+
403
+ var subscribers = dbContext.WorkItems
404
+ .Where(workItem => workItem == existingWorkItem)
405
+ .Include(workItem => workItem.Subscribers)
406
+ .Select(workItem => workItem.Subscribers);
407
+ */
408
+
409
+ Expression left = scope . Accessor ;
410
+ Expression right = Expression . Constant ( resourceTracked , typeof ( TResource ) ) ;
411
+
412
+ Expression whereBody = Expression . Equal ( left , right ) ;
413
+ LambdaExpression wherePredicate = Expression . Lambda ( whereBody , scope . Parameter ) ;
414
+ Expression whereExpression = WhereExtensionMethodCall ( source . Expression , scope , wherePredicate ) ;
415
+
416
+ // TODO: Use typed overload
417
+ Expression includeExpression = IncludeExtensionMethodCall ( whereExpression , scope , navigation . Name ) ;
418
+
419
+ MemberExpression selectorBody = Expression . MakeMemberAccess ( scope . Accessor , navigation . PropertyInfo ) ;
420
+ LambdaExpression selectorLambda = Expression . Lambda ( selectorBody , scope . Parameter ) ;
421
+
422
+ expression = SelectExtensionMethodCall ( includeExpression , source . ElementType , navigation . PropertyInfo . PropertyType , selectorLambda ) ;
423
+ }
424
+ else
425
+ {
426
+ MemberExpression left = Expression . MakeMemberAccess ( scope . Parameter , navigation . PropertyInfo ) ;
427
+ ConstantExpression right = Expression . Constant ( resourceTracked , typeof ( TResource ) ) ;
428
+
429
+ Expression body = Expression . Equal ( left , right ) ;
430
+ LambdaExpression selectorLambda = Expression . Lambda ( body , scope . Parameter ) ;
431
+ expression = WhereExtensionMethodCall ( source . Expression , scope , selectorLambda ) ;
432
+ }
433
+
434
+ IQueryable queryable = source . Provider . CreateQuery ( expression ) ;
435
+
436
+ // Executes the query and loads the returned entities in the change tracker.
437
+ // We can likely optimize this by only fetching IDs and creating placeholder resources for them.
438
+ object [ ] results = queryable . Cast < object > ( ) . ToArray ( ) ;
439
+ }
440
+ }
441
+
442
+ private Expression WhereExtensionMethodCall ( Expression source , LambdaScope lambdaScope , LambdaExpression predicate )
443
+ {
444
+ return Expression . Call ( typeof ( Queryable ) , "Where" , lambdaScope . Parameter . Type . AsArray ( ) , source , predicate ) ;
445
+ }
446
+
447
+ private Expression IncludeExtensionMethodCall ( Expression source , LambdaScope lambdaScope , string navigationPropertyPath )
448
+ {
449
+ Expression navigationExpression = Expression . Constant ( navigationPropertyPath ) ;
450
+
451
+ return Expression . Call ( typeof ( EntityFrameworkQueryableExtensions ) , "Include" , lambdaScope . Parameter . Type . AsArray ( ) , source , navigationExpression ) ;
452
+ }
453
+
454
+ private Expression SelectExtensionMethodCall ( Expression source , Type elementType , Type bodyType , Expression selectorBody )
455
+ {
456
+ Type [ ] typeArguments = ArrayFactory . Create ( elementType , bodyType ) ;
457
+ return Expression . Call ( typeof ( Queryable ) , "Select" , typeArguments , source , selectorBody ) ;
458
+ }
459
+
346
460
private NavigationEntry GetNavigationEntry ( TResource resource , RelationshipAttribute relationship )
347
461
{
348
462
EntityEntry < TResource > entityEntry = _dbContext . Entry ( resource ) ;
0 commit comments