- Home
- LLBLGen Pro
- Architecture
Easily Managing Deleted Entities in an EntityCollection
Joined: 15-Nov-2006
Easily Managing Deleted Entities in an EntityCollection
I struggled with this problem quite a bit when I first started out with LLGL Gen Pro. I have read all the arguments, and I understand why this was not included as part of the framework.
The simple way to do this is as follows:
- Add column IsDeleted to database table, and regen.
- Create Class that inherits from HelperClasses.EntityCollection<TEntity>.
- Override GetDefaultView(), have it return a view that excludes entities that have IsDeleted = true, and make sure that that view set the PostCollectionChangeAction to PostCollectionChangeAction.ReapplyFilterAndSorter.
- Override RemoveAt() so that instead of calling the base implementatin, just set the IsDeleted on the entity to True.
- Use this collection when calling FetchEntityCollection in place of the generic EntityCollection<T> from HelperClasses.
- Use some mechanism to clean your database, either call the DeleteEntitiesDirectly, or a trigger, or a clean up sproc to remove the garbage that is left in the DB.
The result is a clean way to delete entities, have each entity maintain whether or not it is deleted. I don't have to maintain list of delted entities, or create UoW. Just save the collection as normal.
The last thing that I am working on is some way to make the call DeleteEntitiesDirectly with the RelationPredicateBucket specifying to delete all entities with IsDeleted = true. If you brains there at LLGL could help me out with that, I would be set.
Hope that helps some of you that struggled like I did in the begining with all those UoW objects and Lists of Deleted Entities. The biggest pain with the previous 2 solutions are that I have to touch the database after deleting entities to avoid ORMConcurrancy Exceptions, and in my environment, I need to work as disconnected as possible from the database because of the Client / Server Architecture our app needs. Plus, if I need to rollback the state to a previous state if a user clicks cancel on a dialog I am screwed. With this method, I just have to remember at BeginEdit what the state is of each entity, and if CancelEdit is called I can just revert the state of the entities.
I would love to see in the future an EntityCollection3 or some other mechanism where I could speficy to the framework the stereotype of tracking deleted entities, and it would handle creating a UoW for me, so I don't have to go to extra lengths to do such a thing. The alternative is a lot of extra code end users will have to write every time they use your product, clearly there is a need for the framework to take up some of that slack, and remove some of this lower level, redundant coding from us end users. True in some scenarios you don't want it, but in some you do. There is a need for both.
Improvements I am working on to this solution: 1. Easy way to clean up all the records with IsDelete set to true. 2. Easy way to Save the state of all the entities in the collection using the individual Entities Field State functionality. So, that you can edit this collection via DataBinding in the UI, and have CancelEdit actually rollback the changes that were made to all the chid entities since the last time Begin Edit was called.
Jayro
Joined: 15-Nov-2006
namespace Data.CollectionHelperClasses { public class EntityObjectEntityCollection : EntityCollection<EntityObject> { public EntityObjectEntityCollection() : base((IEntityFactory2)null) {
}
public EntityObjectEntityCollection(IEntityFactory2 entityFactoryToUse)
: base(entityFactoryToUse)
{
}
public EntityObjectEntityCollection(IList<FieldActionsEntity> initialContents)
: base(initialContents)
{
}
protected EntityObjectEntityCollection(SerializationInfo info, StreamingContext context)
: base(info, context)
{
}
public new void RemoveAt(int index)
{
TraceHelper.WriteLineIf(TraceHelper.GeneralSwitch.TraceInfo, "ValueTech.Data.CollectionHelperClasses.FieldActionsEntityCollection.RemoveAt", "Method Enter");
if (this.IsReadOnly)
{
throw new InvalidOperationException("This collection is read-only.");
}
EntityObject local1 = this.Items[index];
TraceHelper.WriteIf(TraceHelper.GeneralSwitch.TraceVerbose, this.GetEntityDescription(local1, TraceHelper.GeneralSwitch.TraceVerbose), "Entity to Remove Description");
TraceHelper.WriteIf(TraceHelper.GeneralSwitch.TraceVerbose, index, "Index passed in.");
if (!this.OnEntityRemoving(local1))
{
TraceHelper.WriteLineIf(TraceHelper.GeneralSwitch.TraceInfo, "CollectionCore.RemoveAt", "Canceled by EntityRemoving event. Method Exit");
}
else
{
local1.IsDeleted = true;
this.OnListChanged(index, ListChangedType.ItemChanged);
this.OnEntityRemoved(local1);
TraceHelper.WriteLineIf(TraceHelper.GeneralSwitch.TraceInfo, "ValueTech.Data.CollectionHelperClasses.FieldActionsEntityCollection.RemoveAt", "Method Exit");
}
}
public new bool Remove(EntityObject item)
{
TraceHelper.WriteLineIf(TraceHelper.GeneralSwitch.TraceInfo, "ValueTech.Data.CollectionHelperClasses.FieldActionsEntityCollection.Remove", "Method Enter");
TraceHelper.WriteIf(TraceHelper.GeneralSwitch.TraceVerbose, this.GetEntityDescription(item, TraceHelper.GeneralSwitch.TraceVerbose), "Entity to Remove Description");
if (this.IsReadOnly)
{
throw new InvalidOperationException("This collection is read-only.");
}
bool flag1 = false;
int num1 = this.IndexOf(item);
if (num1 >= 0)
{
if (!this.OnEntityRemoving(item))
{
TraceHelper.WriteLineIf(TraceHelper.GeneralSwitch.TraceInfo, "CollectionCore.Remove", "Canceled by EntityRemoving event. Method Exit");
return false;
}
item.IsDeleted = true;
flag1 = true;
this.OnListChanged(num1, ListChangedType.ItemChanged);
}
this.OnEntityRemoved(item);
TraceHelper.WriteLineIf(TraceHelper.GeneralSwitch.TraceInfo, "ValueTech.Data.CollectionHelperClasses.FieldActionsEntityCollection.Remove", "Method Exit");
return flag1;
}
protected override EntityView2<EntityObject> GetDefaultView()
{
return new EntityView2<EntityObject>(this, EntityObjectFields.IsDeleted == false, null, PostCollectionChangeAction.ReapplyFilterAndSorter);
}
}
}
Joined: 21-Aug-2005
For such reasons (the need to add or extend the framework) LLBLGen Pro uses templates to generate the code. To save a lot of work you can have the modifications generated for you if you add them to the code generation templates.
Joined: 03-Nov-2007
Hi Jayro,
Glad you found a solution that works for you.
If I may, is this not the purpose of IEntityCollection.RemovedEntitiesTracker? I always set .RemovedEntitiesTracker to a new collection... and when the user hits the Save button, I call .RemovedEntitiesTracker.DeleteMulti().
Hope this helps!
Ryan
Joined: 03-Nov-2007
Ahhh, very good, Jayro. I just realized this is an "IsDeleted" solution. You could also loop through your .RemovedEntitiesTracker and do .IsDeleted = True. But your solution is very clean, using the normal Save routine.
Good stuff! Thanks for the post.
Ryan