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