- Home
- LLBLGen Pro
- LLBLGen Pro Runtime Framework
Undo for object with child objects and - lists
Joined: 04-Mar-2005
Hi All
I'm a newbie to the llbl framework, so maybe this question has been answered already in the past and I haven't found it...
I have an Entity - object which contains child entities and -lists (which of course contains entity entries. In my GUI i bind that object to a big form with several Grids, sub-forms etc.
On the main form I have a cancel - button. The child objects I handle in popup windows which also have cancel - buttons.
When I hit the cancel - button on the main form, I want a undo of all objects ( means the main object, the child objects and the child lists ). When I hit the cancel button on a sub window I want a undo just on the current child object.
I've tried BeginEdit() and CancelEdit() which only did an undo on the Fields of the main object, not on the childs.
SaveFields() and RollbackFields() didn't work either.
My Object has no connection to a Database, it gets the Data from a Webservice via a DTO object. I use the EntityObjects which are generated on service-side development. The generated entity object are derived from a base class which adds some functionality to the EntityBase - class.
Does anybody know a solution for a complete undo or did I use Save/RollbackFields( )in the wrong way?
Thanx & ciao Bernd
public abstract class MyEntityBase : EntityBase2
{
int undoCounter = 0;
//...
public void Start( )
{
undoCounter ++;
this.SaveFields( undoCounter.ToString( ) );
}
public void Undo( )
{
if ( undoCounter > 0 )
{
this.RollbackFields( undoCounter.ToString( ) );
undoCounter --;
}
}
}
public class HugeDataEntity : MyEntityBase
{
//...
}
rollback and save fields save the entity fields, and a rollback of fields simply sets teh Fields object to the version saved using the SaveFields action with the same name.
When you call RollbackFields, do you indeed get a rollback of the fields, or nothing happens? Mind you: only the fields of the entity are rolled back, not collections. So if I do: myCustomer.RollbackFields("foo"); I won't roll back the myCustomer.Orders collection's state.
Joined: 04-Mar-2005
Hi Otis
You've exactly described my problem
So I'll have to create also a "MyEntityCollectionBase" which provides a Start() and Undo() - method, where the Start() method saves the object in the collection in a second list - lets call that "undo-list" - and calls SaveFields() for each object and the Undo() method writes back the objects from the undo-list and calls RollbackFields() for each object ?
The only problem remaining would be if I assign a new collection during the edit process, e.g.
bla.myCollection = getSomeNewCollection( )
( same problems for single child objects ...
The 'best' way to roll back changes is to throw them away and revert to a previous graph. You can do this by serializing (using binary formatter) the object graph into a memory stream and then go into the forms. If the user presses Save/OK, throw away the memorystream, if the user presses Cancel, throw away the changes and deserialize the graph back from the memory stream using teh binary formatter.
It will otherwise be a nightmare, tracking all the changes in the object graph, and it will be more efficient to simply refetch the data (or revert to a serialized memorystream).
Serializing/deserializing to/from memorystream: (this serializes/deserializes a relationpredicatebucket, but the principle is the same)
IRelationPredicateBucket filter = new RelationPredicateBucket();
filter.PredicateExpression.Add(PredicateFactory.CompareValue(InvoicesFieldIndex.ShipCity, ComparisonOperator.Equal, "Berlin"));
// serialize the predicate to a memstream.
MemoryStream memStream = new MemoryStream();
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(memStream, filter);
// seek back, deserialize in other bucket.
memStream.Seek(0, SeekOrigin.Begin);
IRelationPredicateBucket deserializedFilter = (RelationPredicateBucket)formatter.Deserialize(memStream);
memStream.Close();
Joined: 04-Mar-2005
Hi Otis
First of all thanx for the fast reply .
The serialisation works. The only remaining object is that I have to serialize/deserialize the object from outside (serialization itself is not the problem but deserialization...)
but I think I can live with that...
greets, Bernd
Joined: 03-Nov-2007
Hi Bernd -
Did you ever get this to work?
I also need the ability to Cancel (without refetching my objects from the database).
I am able to serialize/deserialize fine. So now Cancelling isn't a problem, as I just throw the serialized object away.
But if the user hits OK - How do I overwrite / update my existing original object with the changes made by the user?
Any help would be much appreciated! Thanks!
Ryan D. Hatch
Joined: 08-Oct-2008
You don't. At the start of the process you serialize the graph in its clean state to a stream which you stash away somewhere.
You then bind yout clean graph to the form and let the user do what they need to.
If the user cancells you deserialize the stream - if they "OK" you keep and use the objects they were modifying on the form.
Matt
Joined: 03-Nov-2007
Hi Matt & Fellow LLBLGen Users -
Thanks for the reply. That is precisely what I ended up doing (Method #1 below). For several days, I researched the number of options for implementing undo / cancel functionality. Here is a list of options I compiled & reviewed. I wish I had this list to review when I first started down this road. Hopefully this can help others.
My hope was that the user could edit the objects without affecting the underlying screen. Perhaps its not the biggest deal, but I was not able to achieve that functionality.
Goal: Multiple In-Memory Saves / Undos, without going to the Database
Definitions: • Root Entity ("well" Entity) - Starting Entity used to Databind entire GUI • Original Object - entity that we need to restore to • Edited Object - modified entity that we need to cancel our changes on
1.) Re-Databind to Cloned Graph (Currently the best-known solution) Description I have this implemented beautifully. I’ve created two classes: UndoManager and UndoRestorePoint. UndoManager knows how to Get/Set the RootEntity and the method to call to Re-DataBind. At any time I can ask the UndoManager to create a RestorePoint. Each RestorePoint contains a new snapshot of the object graph. myUndoRestorePoint = UndoManager.GetRestorePoint(). <- Called before going into an Edit dialog window myUndoRestorePoint.Restore <- Called if Cancel was clicked Advantages Multiple RestorePoints possible. Simple Disadvantages - Edited Object is seen by other screens while it is being modified - Does not maintain instances of objects. New instances must be created. So any references to our current objects are going to be referencing stale entities that our GUI will no longer be using (since creating a RestorePoint means serialization/deserializing to new instances). - Root Entity for GUI may be pretty far away from our current screen - but even so - we need to redatabind everything from the Root Entity onwards. Realize, there is probably more than one Root Entity at any given time in each session of your application. One possible way to mitigate this issue by decreasing the distance to your Root Entity is to pass primary keys to GUI screens instead of passing references to objects (ie, OrderID instead of a Customer.Order).
2.) Overwrite Cloned Instance Description On Edit - Create clone of Original Object. On Cancel, throw away Edited Entity. On Ok, overwrite Original Entity with Edited Entity. I was able to implement this by removing the Original Entity from its parent Collection, and also removing it from its parent Collection’s RemovedEntitiesTracker, if it exists. However, my attempt failed when trying to force Garbage Collection on the Original Object using a WeakReference.IsAlive to determine if it was collected. I was not able to get it to garbage collect, even though my graph did not reference the Original Object. Perhaps someone could make it work. Advantages - Would not need to rebind all data from Root - Could do unlimited depth undos Disadvantages - References would be lost, defeating the advantage of not re-databinding
3.) Inverting a UnitOfWork Description For each action on an entity, record the opposite into a UnitOfWork. Advantages ? Disadvantages Requires going to the Database Very bad idea. Maybe I misunderstood the post where I found this. As I understand it, If the user hits cancel - we actually Save the EditedObject to the database, and then Save again using the OriginalObject, effectively overwriting it. You would need to attach both UnitsOfWork to the same Transaction, but still - this just isn't right. If you're going to go back to the database anyway - just forget saving altogether & refetch new entities from the database.
4.) Context Cache & Refetch Description Use a Context to cache the EditedObject, and refetch the RootEntity to re-populate the graph & re-databind. In theory, it may be possible to return the EditedObject instead of the OriginalObject (although I'm not sure how). Advantages ? Disadvantages Requires going to the Database Still need to Re-DataBind
5.) Use an Embedded Database Description Save entity versions to an embedded DB (ie, SQLServer Compact Edition). Refetch & Re-Databind objects from embedded DB Advantages Theoretically, would deliver N-Level Undos without going back to the database. Could be stored in heap/memory-only tables. Disadvantages Overkill! Still requires Re-Databinding anyway using new instances of objects Requires replication of SQLServerCE with real database - in case of a schema change. Requires use of Adapter model. Self-Servicing not possible - because we are accessing two different databases. Although I've not tested this, it may be possible to use same Self-Servicing entities on both SQLServerCE & SQLServer.
6.) Observer (This is probably the best solution, once Frans releases his library) Description An observer object watches as changes are made to objects and knows how to undo them Advantages - Maintains Instances of objects - No re-databinding necessary Disadvantages - Complex to implement. Need to handle all events to all changes in object graph, and be able to rollback. - Not currently available until Frans releases his open source Graph library (requires .NET 3.5 lambdas). Unless someone has a simple implementation?
7.) SaveFields / RollbackFields Description Use LLBLGen’s integrated Save/RollbackFields methods Advantages Extremely Simple Disadvantages Is only aware of field values. Not aware of any modified collections / related entities, so pretty useless for advanced undo capability
Hope this helps!
Ryan D. Hatch