New Posts New Posts RSS Feed: Cascading Delete/Modification
  FAQ FAQ  Forum Search   Calendar   Register Register  Login Login

Cascading Delete/Modification

 Post Reply Post Reply
Author
ken.nelson View Drop Down
Groupie
Groupie


Joined: 03-Mar-2009
Location: Ann Arbor, MI
Posts: 54
Post Options Post Options   Quote ken.nelson Quote  Post ReplyReply Direct Link To This Post Topic: Cascading Delete/Modification
    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:
 
 
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
 
 


Edited by ken.nelson - 24-Mar-2009 at 1:32pm
Back to Top
kimj View Drop Down
IdeaBlade
IdeaBlade
Avatar

Joined: 09-May-2007
Posts: 1391
Post Options Post Options   Quote kimj Quote  Post ReplyReply Direct Link To This Post 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.
Back to Top
ken.nelson View Drop Down
Groupie
Groupie


Joined: 03-Mar-2009
Location: Ann Arbor, MI
Posts: 54
Post Options Post Options   Quote ken.nelson Quote  Post ReplyReply Direct Link To This Post Posted: 25-Mar-2009 at 8:39am
Cool, that worked, thanks!
Back to Top
ken.nelson View Drop Down
Groupie
Groupie


Joined: 03-Mar-2009
Location: Ann Arbor, MI
Posts: 54
Post Options Post Options   Quote ken.nelson Quote  Post ReplyReply Direct Link To This Post 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
Back to Top
kimj View Drop Down
IdeaBlade
IdeaBlade
Avatar

Joined: 09-May-2007
Posts: 1391
Post Options Post Options   Quote kimj Quote  Post ReplyReply Direct Link To This Post 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();
      }
    }
Back to Top
 Post Reply Post Reply

Forum Jump Forum Permissions View Drop Down