1
Vote

Remove an entity and the relations to other entities

description

It should be possible to remove a trainer even if the trainer has a relation to one or several dogs.

I've attached a updated version of M2M4RiaDemo. Changes that I have made:
In MainPageModel.cs
  • Renamed DeleteTrainerCommand To DeleteTrainerFromDogCommand etc
  • Added new DeleteTrainerCommand, DeleteDogCommand
    In MainPage.Xaml
  • Renamed some buttons and added 2 new buttons with commands to DeleteTrainerCommand & DeleteDogCommand
When this was done an exception was thrown when saving after I deleted a dog/trainer that had trainers/dogs.

After that I updated M2M4RiaLinqToEntitiesDomainService.ttinclude
So that it was possbile to save.

Kind regards Pontus

file attachments

comments

MdeJ wrote Sep 6, 2012 at 8:01 PM

Hi Pontus,
Thanks for sharing.
Could you perhaps let me know what exactly goes wrong when deleting a dog/trainer and what you changed in M2M4RiaLinqToEntitiesDomainService.ttinclude to make it work?

Thanks in advance,
Kind regards,
Merijn

klamfeldt wrote Sep 7, 2012 at 11:49 AM

The the child entity was found but if it was not attached to the context then an exception was thrown.

I Also changed the stateentry for the child entity aswell If it was deleted to Unchanged. Also restored the StateEntry (if changed) after removal of the link between parent and child.

danneesset wrote Sep 19, 2012 at 8:41 AM

Since the zip already contains a ttinclude file where the bug is fixed I'll just post a more detailed comment about the changes to the generated code:

1; Code fetching an entity must make sure that it is also attached to the ObjectContext:

Change from:
if(dog == null)
{
          dog = ChangeSet.ChangeSetEntries.Select(cse => cse.Entity)
              .OfType<Dog>()
                            .SingleOrDefault(e => e.DogId == dogTrainer.DogDogId );
}
To:
if(dog == null)
{
          dog = ChangeSet.ChangeSetEntries.Select(cse => cse.Entity)
              .OfType<Dog>()
                            .SingleOrDefault(e => e.DogId == dogTrainer.DogDogId );
            // If we found an entity we need to make sure it is attached to the context 
            // before we set up the relation, or we will get an exception
            if(dog != null && dog.EntityState  == EntityState.Detached)
            {
                ObjectContext.Dogs.Attach(dog);
            }
}

2; Before attaching an entity to a relation we must make sure that neither side is in the deleted state(previously only one of the entities where checked)

Change from:
        if(dog.Trainers.IsLoaded == false)
        {
            ObjectStateEntry stateEntry = ObjectContext.ObjectStateManager.GetObjectStateEntry(dog);
            EntityState state = stateEntry.State;

            if(state == EntityState.Deleted)
            {
                stateEntry.ChangeState(EntityState.Unchanged);
            }
            dog.Trainers.Attach(trainer);
            if(stateEntry.State != state)
            {
                stateEntry.ChangeState(state);
            }
        }
To:
        if(dog.Trainers.IsLoaded == false)
        {
            EntityState trainerState = trainer.EntityState;
            EntityState dogState = dog.EntityState;
            ObjectStateEntry dogStateEntry = null;
            ObjectStateEntry trainerStateEntry = null;

            //Backup state and change any deleted state to unchanged
            if(dogState == EntityState.Deleted)
            {
                dogStateEntry = ObjectContext.ObjectStateManager.GetObjectStateEntry(dog);
                dogStateEntry.ChangeState(EntityState.Unchanged);              
            }
            if(trainerState == EntityState.Deleted)
            {
                trainerStateEntry = ObjectContext.ObjectStateManager.GetObjectStateEntry(trainer);
                trainerStateEntry.ChangeState(EntityState.Unchanged);                    
            }

            dog.Trainers.Attach(trainer);

            //Restore previous state, if we have changed it
            if(dogStateEntry != null)
                dogStateEntry.ChangeState(dogState);
            if(trainerStateEntry != null)
                trainerStateEntry.ChangeState(trainerState);

        }

danneesset wrote Sep 19, 2012 at 8:46 AM

A note about #2:

You can not attach a relationship "dog.Trainers.Attach(trainer);" when any of the entities are in any of the states (Added, Detatched or Deleted) so it would be better to also check for both the added and deleted state (although I find it hard to belive that we would actually be able to remove a relation from a pair where one entity is about to be added as if the relationsip exist (so it can be deleted) then both entities should also exist and should thus not be in the added state)

danneesset wrote Jan 4, 2013 at 12:52 PM

The is a slight bug in the .ttinclude posted in the zip (for inheritance hierarchie), but apart from that it works really well.

To fix the bug (wrong class used in OfType call) change the code in the MakeGetType function from:

<#=entity#> = ChangeSet.ChangeSetEntries.Select(cse => cse.Entity)
              .OfType<<#= concreteEntityType#>>()
              .SingleOrDefault(e => <#= associationSelector #> );
into:

<#=entity#> = ChangeSet.ChangeSetEntries.Select(cse => cse.Entity)
              .OfType<<#= entityType #>>()
              .SingleOrDefault(e => <#= associationSelector #> );


I have attached a file where I've added the fix to the orignal patch