Print Page | Close Window

Cascading Delete/Modification

Printed From: IdeaBlade
Category: DevForce
Forum Name: DevForce 2009
Forum Discription: For .NET 3.5
URL: http://www.ideablade.com/forum/forum_posts.asp?TID=1168
Printed Date: 15-Apr-2025 at 6:35am


Topic: Cascading Delete/Modification
Posted By: ken.nelson
Subject: Cascading Delete/Modification
Date Posted: 24-Mar-2009 at 10:24am
We're having a problem while deleting an entity that has related entities.  In DevForce Classic, you used to be able to override Delete() and handle any additional cleanup there, but that seems to have been removed from EF.  The Reference Help document indicates that to get around this:
 

The ms-its:C:%5CProgram%20Files%5CIdeaBlade%20DevForce%20EF%5CDocumentation%5CIdeaBladeDevForceV4HelpReference.chm::/IdeaBlade.EntityModel.v4~IdeaBlade.EntityModel.v4.EntityGroup~EntityChanging_EV.html - IdeaBlade.EntityModel.v4.EntityGroup.EntityChanging and ms-its:C:%5CProgram%20Files%5CIdeaBlade%20DevForce%20EF%5CDocumentation%5CIdeaBladeDevForceV4HelpReference.chm::/IdeaBlade.EntityModel.v4~IdeaBlade.EntityModel.v4.EntityGroup~EntityChanged_EV.html - IdeaBlade.EntityModel.v4.EntityGroup.EntityChanged events will fire during a Delete call with an EntityAction of ms-its:C:%5CProgram%20Files%5CIdeaBlade%20DevForce%20EF%5CDocumentation%5CIdeaBladeDevForceV4HelpReference.chm::/IdeaBlade.EntityModel.v4~IdeaBlade.EntityModel.v4.EntityAction~Delete.html - IdeaBlade.EntityModel.v4.EntityAction.Delete .

 
For another bad example... Customer has Orders, we want to remove the Orders associated with a Customer via code (meaning, we're not looking for a database solution here).  Following the above guidelines, we have the following:
 
public Customer()
{
     this.EntityAspect.EntityGroup.EntityChanging += new
          EventHandler<IdeaBlade.EntityModel.v4.EntityChangingEventArgs>(EntityGroup_EntityChanging);
}
private void EntityGroup_EntityChanging(object sender, IdeaBlade.EntityModel.v4.EntityChangingEventArgs e)
{
    
if (e.Action == IdeaBlade.EntityModel.v4.EntityAction.Delete)
    
{
          
foreach (var order in this.Orders)
               order.Delete();
    
}
}
 
The problem is when we delete the Customer, the EntityChanging event never fires.  When we SaveChanges(), an Exception is thrown, indicating that there are still Orders referencing the Customer, which makes sense given our cleanup code never fires.
 
Are we doing something incorrectly here?  Is there a better way to handle this?
 
Thanks,
Ken
 
 



Replies:
Posted By: kimj
Date Posted: 24-Mar-2009 at 4:33pm
The problem is that the EntityGroup on which the handler is placed is not the "real" EntityGroup holding the entity. Confusing, yes. 
 
Setting the handler in the entity constructor is not a good idea for two reasons - 1) the event is fired for the EntityGroup and creating a handler for each entity instance means there will be many more handlers than needed, and 2) more importantly, and for obscure reasons, the EntityGroup used in entity construction turns out not to be the EntityGroup used to cache the entities.  What you should do instead is ask the EntityManager for the EntityGroup and set the handler once:
 
     _entityManager.GetEntityGroup(typeof(Customer)).EntityChanging += EntityGroup_EntityChanging;
 
The handler can get the Entity involved via the EntityChangingEventArgs.
 
We'll take a look at how we can better document the EntityGroup and its events, or change the API, so this isn't so cryptic.


Posted By: ken.nelson
Date Posted: 25-Mar-2009 at 8:39am
Cool, that worked, thanks!


Posted By: ken.nelson
Date Posted: 27-Mar-2009 at 1:25pm

I've got a followup question on this one, specifically for Silverlight.

The above solution appears to work properly, when the Orders are in the cache.  However when not in the cache, I've got to do the pending list solution:
 
public static void EntityChanging(object sender, IdeaBlade.EntityModel.v4.EntityChangingEventArgs e)
{
    
if (e.Action == IdeaBlade.EntityModel.v4.EntityAction.Delete)
     {
          if (e.Entity is Customer)
          {
               Customer customer = e.Entity as Customer;
               if (customer.Orders.IsPendingEntityList)
              
{
                    customer.Orders.PendingEntityListResolved +=
                         new EventHandler<PendingEntityListResolvedEventArgs<Order>>(OrdersResolvedForDelete);
               }
               else
               {
                    foreach (var order in customer.Orders.ToList())
                        order.EntityAspect.Delete();
               }
          }
    
}
}
 
private void OrdersResolvedForDelete(object sender, PendingEntityListResolvedEventArgs<Order> e)
{
     foreach (var order in e.ResolvedEntities)
          order.EntityAspect.Delete();
}
 
The problem is, this doesn't appear to work properly when the list is pending.  It seems that if the Customer is marked as Deleted before the pending EntityList is resolved, then the e.ResolvedEntities will come back empty and the related Orders will never get deleted, which ultimately results in a foreign key exception being thrown.
 
Any idea's how to get around this?
 
Thanks,
Ken


Posted By: kimj
Date Posted: 27-Mar-2009 at 8:05pm
You're right that use of the EntityChanging event to handle cascaded deletions falls short when asynchronous navigation is involved.  One workaround, when possible, is to pre-load the dependent objects so you avoid this situation altogether.  When that's not possible, we really don't have a great answer for this right now.
 
I did look at using the AsyncSerialTask to do this and came up with something which works, but is a bit ugly.  Pending entity resolution is not currently handled the same way as other asynchronous actions within DevForce, but we will look at making this easier.  The sample below takes advantage of how the AsyncSerialTask passes arguments and results from step to step. 
 
      AsyncSerialTask.Create<Customer>("DeleteCustomer")
        .AddAsyncAction<AsyncEventArgs>(SetupDeletion)
        .AddAction(args => DoDelete((Customer)args.UserState))
        .Execute(aCustomerToBeDeleted);
 
    // "callback" is called when entities are available
    //  the Customer is passed as the userState to the AsyncEventArgs
    private void SetupDeletion(Customer cust, AsyncCompletedCallback<AsyncEventArgs> callback) {
      var list = cust.OrderSummaries;
      bool isPending = list.IsPendingEntityList;
      if (isPending) {
        list.PendingEntityListResolved += (o, args) => {
          callback(new AsyncEventArgs(cust));
        };
      } else {
        callback(new AsyncEventArgs(cust));
      }
    }
 
    // Called by AsyncSerialTask when the prior step completes.
    private void DoDelete(Customer cust) {
      cust.EntityAspect.Delete();
      foreach (var order in cust.OrderSummaries.ToList()) {
        order.EntityAspect.Delete();
      }
    }



Print Page | Close Window