@@ -325,6 +325,9 @@ public virtual async Task DeleteAsync(TResource? resourceFromDatabase, TId id, C
325325
326326 var resourceTracked = ( TResource ) _dbContext . GetTrackedOrAttach ( placeholderResource ) ;
327327
328+ EnsureIncomingNavigationsAreTracked ( resourceTracked ) ;
329+
330+ /*
328331 foreach (RelationshipAttribute relationship in _resourceGraph.GetResourceType<TResource>().Relationships)
329332 {
330333 // 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
335338 await navigation.LoadAsync(cancellationToken);
336339 }
337340 }
341+ */
338342
339343 _dbContext . Remove ( resourceTracked ) ;
340344
@@ -343,6 +347,114 @@ public virtual async Task DeleteAsync(TResource? resourceFromDatabase, TId id, C
343347 await _resourceDefinitionAccessor . OnWriteSucceededAsync ( resourceTracked , WriteOperationKind . DeleteResource , cancellationToken ) ;
344348 }
345349
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+
346458 private NavigationEntry GetNavigationEntry ( TResource resource , RelationshipAttribute relationship )
347459 {
348460 EntityEntry < TResource > entityEntry = _dbContext . Entry ( resource ) ;
0 commit comments