PropertyChanged function : modifying a deleted entity

Jan 23, 2009 at 5:25 PM
                           bool isDbGenerated = _entityDbGeneratedProperties.TryGetValue(e.PropertyName, out propInfo);
                            if (isDbGenerated)
                                return;

                            // if the object isn't already modified or detached
                            // set it as modified
                            if (LINQEntityState != EntityState.Modified && LINQEntityState != EntityState.Detached
                                /*&& LINQEntityState != EntityState.Deleted*/ /*pa : ajouter (et enlever) deleted*/)
                            {
                                this._originalEntityValue = this._originalEntityValueTemp;
                                LINQEntityState = EntityState.Modified;
                            }

Hi Matt,

We are still using your entitybase, and it works great!
We have an deleted entity, and we would like to be sure that it will not become modified if we change a property. We propose to add this test if it is possible.

What do you think about that. Can this generate a problem?

Patrick.
Jan 23, 2009 at 8:40 PM
Hi Matt,

We think that it would be better to make this change (because an entity should not be modified after deleting).
Note we add the Ignored entity state to process corectly entities that are created and deleted during the same disconnected session.

        private void PropertyChanging(object sender, PropertyChangingEventArgs e)
        {
            if (this.LINQEntityState == EntityState.Deleted || this.LINQEntityState == EntityState.Ignored)
            {
                throw new ApplicationException("You cannot modify a deleted or ignored entity");
            }

            // If it's a change tracked object thats in "Original" state
            // grab a copy of the object incase it's going to be modified
            if (this.LINQEntityState == EntityState.Original && LINQEntityKeepOriginal == true && LINQEntityOriginalValue == null)
            {
                _originalEntityValueTemp = LINQEntityBase.ShallowCopy(this);
            }
        }

Patrick

Jan 26, 2009 at 10:29 AM
Hi Patrick,

Just caught up on your emails!

Yes, I think that the later would be the way to go (the right place to put it), infact I think it's a good idea and I'll it the source code soon.

I like the idea of the ignored state, I was thinking about something similar earlier this week - I wanted to handle the case where you create a new object and then delete it in the same disconnected session as you describe, I never got around to playing with it.

The question I had was if you infact created a new object and then deleted it in the same session, would it still be "attached" to it's parent even after you hit the database (assuming the parent wasn't changed/deleted as well)?  From memory, it would still hang around as a child (if you didn't re-load the entire tree), so it will need to be removed in the "SynchroniseWithDataContext" method (perhaps by setting the child FK to null?).

Anyway, see what you find - hopefully i'll work on this during the week and get some source code published.

Cheers

Matt.
Jan 29, 2009 at 11:14 AM
Hi Patrick,

I've made the change...

So Basically, if you try and change an object that has a state of deleted or cancelnew (same as your ignore) then it will complain.

On top of this, when syncronising with database, the object with a state of cancel new will have all FK references null and will not longer be available (as thought it had been "deleted") during sycnronisation.

Cheers

Matt.
Feb 4, 2009 at 11:19 PM
Hi Matt,

I have a lot to tell you in this post!!

A)
I think the best location for the code :
           if (this.LINQEntityState == EntityState.Deleted || this.LINQEntityState == EntityState.Ignored)
            {
                throw new ApplicationException("You cannot modify a deleted or ignored entity");
            }


is in fact where i put it first, that is to say in the propertychanged function

                            // if the object isn't already modified or detached
                            // set it as modified
                            if (LINQEntityState != EntityState.Modified && LINQEntityState != EntityState.Detached)
                            {
                                if (this.LINQEntityState == EntityState.Deleted || this.LINQEntityState == EntityState.Ignored)
                                {
                                    throw new ApplicationException("You cannot set as modified a deleted or ignored entity");
                                }
                                this._originalEntityValue = this._originalEntityValueTemp;
                                LINQEntityState = EntityState.Modified;
                            }

This is better  for at least two reasons
1) we must throw an exception only when the entity is really changed (and the property don't keep the same value)
2) You bind to event entites ondeserialized, but the graph will be created after and association properties are changed during this step!!!

B)
To answer at your question in the 26 Jan post :
The question I had was if you infact created a new object and then deleted it in the same session, would it still be "attached" to it's parent even after you hit the database (assuming the parent wasn't changed/deleted as well)?  From memory, it would still hang around as a child (if you didn't re-load the entire tree), so it will need to be removed in the "SynchroniseWithDataContext" method (perhaps by setting the child FK to null?).

To be sure our modification are correctly sent in the database by linqtosql (and to read possible modifications made by others in the DB), we always reload the entire tree from the DB after synchronising.
BUT, we still need to remove (by setting the child FK to null while synchronizing as you suggerated) a canceldelete entity (especially when its parent is new , because linqtosql  will create it in the DB otherwise : reading it in the entitysets?). We can notice that if the parent  is unchanged (i am not sure what happens if modified, but it should be the same as new i guess), there is no problem if the canceldelete entity is still attached to its parent while synchronizing --- it is not created in the DB.
We must remove the canceldelete entities like you in your new code version, but we don't need to use the _isDBSyncronizing boolean because we don't use the Detached entitystate.

C)
What about our previous post : deleting a modified entity?


Thank you again for all your work on the entitybase, it is a pleasure to work with.

Cheers,

Patrick



Feb 5, 2009 at 10:45 AM
Hi Patrick,

Yeah, this is tricky!

1) we must throw an exception only when the entity is really changed (and the property don't keep the same value)
In the propertychanged event, it's too late and the property is already changed.  When it's in propertychanging, it raises the event before any changes are made - are you saying that it's firing the exception accidentally due to some other change - is it the FK's?

2) You bind to event entites ondeserialized, but the graph will be created after and association properties are changed during this step!!!
Can you expand on this, perhaps given an example?  I've recently changed the code on deserialization, and it creates the graph and then binds to the propertychanging/propertychanged events - hence the association properties should not be firing the events at this point as the events aren't active till the end of deserialization.  Check you have the latest code - also, let me know if i'm totally missing the point ;)

B) OK.

C) Sorry didn't see that one! Must of thought it was part of this one...


Unfortunately I don't think it's that easy - if the deleted entity has children, we need to delete in the right order - the way LINQ 2 SQL works is it traverses the relationships of the attached entiteis and works out what is what and in what order it can do things - which means keeping the relationships intact. The "original copy" data doesn't have the relationships.  However, if you aren't going to have multi-level deletes then go for it.   Here's an idea though, what we could do is modify the code so that we disable the PropertyChanging/Changed events from firing and do a copy back of the original values (if available) into the actual entity and attach it for deletion - leaving it in the same EntityState but putting the originals propery values back.  This might work?  What do you think.

Cheers

Matt.



Feb 7, 2009 at 11:25 PM

Hi Matt,

 

Thank you for yours answers. Here are the news.

 

A) I didn't have the last version of the AfterDeserialized function.
I use now your _isDBSyncronizing boolean but  I also put this boolean value to true before calling  the submitChanges function (otherwise a call at PropertyChanging  will be made by my deleted entity during submitchange and it will complain!!!).

C) We made the copy back of the original values as you sugerated and it seems to work.


Thanks a lot for you help,

Patrick

Here is the correction we made  in the SynchronizeWithDataContext function:

 

 

if (entity.LINQEntityOriginalValue != null)

 

{

ShallowCopy(entity.LINQEntityOriginalValue, entity);

}

targetDataContext.GetTable(entity.GetEntityType()).Attach(entity);

targetDataContext.GetTable(entity.GetEntityType()).DeleteOnSubmit(entity);

entitiesDeleted.Add(entity);

WHERE ShallowCopy IS

 

 

public static LINQEntityBase ShallowCopy(LINQEntityBase source)

 

{

 

Type entityType = source.GetType();

 

 

LINQEntityBase destination;

 

destination =

Activator.CreateInstance(entityType) as LINQEntityBase;

 

ShallowCopy(source, destination);

destination.LINQEntityState =

EntityState.Original;

 

destination.LINQEntityGUID = source.LINQEntityGUID;

 

return destination;

 

}

 

public static void ShallowCopy(LINQEntityBase source, LINQEntityBase destination)

 

{

 

PropertyInfo[] sourcePropInfos = source.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);

 

 

PropertyInfo[] destinationPropInfos = destination.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);

 

 

foreach (PropertyInfo sourcePropInfo in sourcePropInfos)

 

{

 

if (Attribute.GetCustomAttribute(sourcePropInfo, typeof(ColumnAttribute), false) != null)

 

{

 

PropertyInfo destPropInfo = destinationPropInfos.Where(pi => pi.Name == sourcePropInfo.Name).First();

 

destPropInfo.SetValue(destination, sourcePropInfo.GetValue(source,

null), null);

 

}

}

}