Deeper Entity.IsDirty and Entity.RejectChanges

Posts   
 
    
arschr
User
Posts: 894
Joined: 14-Dec-2003
# Posted on: 28-Apr-2006 14:51:25   

RejectChanges is an old routine, ment for databinding purposes. RejectChanges is used when IEditableObject.CancelEdit is called by a grid for example. It calls entity.Fields.RejectChanges which in turn calls field.RejectChange, which copies _originalValue back to _currentValue and resets IsChanged (and IsDirty is reset as well on Fields)

If I have an entity with child collections that has been edited elsewhere in the system.

1) Is it true that entity.IsDirty will not tell me if the entity has dirty members in it's entity collections?

2) Is it true that entity.RejectChanges won't roll back changes to the members of the entities child collections?

If 1 is true, could you give a code snippet that would tell me (for a generic entity) if it or any of it's child collections (or their entities child collections, recursively) are dirty?

If 2 is true, could you give a code snippet that would rollback changes (for a generic entity) in an entity and any of it's child collections (or their entities child collections, recursively)?

If 1 or 2 is true will any of that get easier in v2.0?

Walaa avatar
Walaa
Support Team
Posts: 14995
Joined: 21-Aug-2005
# Posted on: 28-Apr-2006 15:37:11   

1) Is it true that entity.IsDirty will not tell me if the entity has dirty members in it's entity collections?

Yes it is true

2) Is it true that entity.RejectChanges won't roll back changes to the members of the entities child collections?

As far as I know Yes it is true

If 1 is true, could you give a code snippet that would tell me (for a generic entity) if it or any of it's child collections (or their entities child collections, recursively) are dirty?

Please check the following thread: http://www.llblgen.com/tinyforum/Messages.aspx?ThreadID=3057

If 2 is true, could you give a code snippet that would rollback changes (for a generic entity) in an entity and any of it's child collections (or their entities child collections, recursively)?

Best way is to refetch the entire graph again.

Another option is to use SaveFields & RollbackFields methods on related entities.

Another option is in the following thread: http://www.llblgen.com/tinyforum/Messages.aspx?ThreadID=2458

arschr
User
Posts: 894
Joined: 14-Dec-2003
# Posted on: 28-Apr-2006 17:07:18   

Please check the following thread: http://www.llblgen.com/tinyforum/Messages.aspx?ThreadID=3057

Well, you can traverse the entities referenced by an entity, like the Save routine does too, by using the following routines: - entity.GetDependentRelatedEntities(), which gets a collection of entity objects which 'entity' depends on (e.g. a loaded Customer entity for an order entity). m:1/1:1 relations - entity.GetDependingRelatedEntities(), which gets a collection of entity objects which depend on 'entity'. (1:1 relations) - entity.GetMemberEntityCollections(), which gets a collection of collections (1:n relations). See the reference manual for details on these methods. With the contents of these methods you can check further, for example recursively in there to go deeper into the graph.

Since the save routine does this, couldn't we have the helper function available to use, rather than me recoding it. Is this source for this routine available?

Best way is to refetch the entire graph again.

That seems really bad in a disconnected environment.

Another option is in the following thread: http://www.llblgen.com/tinyforum/Messages.aspx?ThreadID=2458

OK, so If I serialize the entity ahead of time and store it somewhere, then if there was a good way to determine if the entity was deep dirty I could restore using this.

Is any of this going to change in v2.0?

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39927
Joined: 17-Aug-2003
# Posted on: 28-Apr-2006 18:15:53   

arschr wrote:

If I have an entity with child collections that has been edited elsewhere in the system.

1) Is it true that entity.IsDirty will not tell me if the entity has dirty members in it's entity collections?

Yes, as related entities aren't part of the entity, they're related.

2) Is it true that entity.RejectChanges won't roll back changes to the members of the entities child collections?

Yes, as related entities aren't part of the entity, they're related. simple_smile

If 1 is true, could you give a code snippet that would tell me (for a generic entity) if it or any of it's child collections (or their entities child collections, recursively) are dirty?

Fastest way is to add the entity to a UoW to save recursively, then call ConstructSaveProcessQueues on the UoW and you have your 2 queues with entities which are dirty or WILL get dirty because a dependent entity is scheduled to be saved.

If 2 is true, could you give a code snippet that would rollback changes (for a generic entity) in an entity and any of it's child collections (or their entities child collections, recursively)?

See above and rollback the changes. You can also use SaveFields to save the fields of the entities and roll them back later on.

If 1 or 2 is true will any of that get easier in v2.0?

No, as it's not solvable. It's effectively 'graph' versioning, which is very very complex. It's also complicated because it can be that an entity effects more than 1 other related entities (when an entity has multiple m:1 relations with different entities for example).

Frans Bouma | Lead developer LLBLGen Pro
arschr
User
Posts: 894
Joined: 14-Dec-2003
# Posted on: 28-Apr-2006 20:12:22   

Fastest way is to add the entity to a UoW to save recursively, then call ConstructSaveProcessQueues on the UoW and you have your 2 queues with entities which are dirty or WILL get dirty because a dependent entity is scheduled to be saved.

I've not worked with uow. I'll look at it. Does it seem to you that it provides the features I'm asking for?

No, as it's not solvable. It's effectively 'graph' versioning, which is very very complex. It's also complicated because it can be that an entity effects more than 1 other related entities (when an entity has multiple m:1 relations with different entities for example).

I don't think graph versioning is what is needed, tree versioning is. Is that then solvable?

If I have a order or some other parent type entity, when I ask if it or it's children are dirty or if I ask to save it and it's children. It's not "normal" in the real world to want or expect that changes to it's parents would be included/saved. If I use an order to change the address of the customer it belongs to, I should save the customer and it's children not the order and it's parents and children. I know that what is the parent and what is the child is not always clear.

By simplifying the model to tree situations, I think you would meet 90% to 99% of the real world requirements while providing a feature that would save the users of llblgen a lot of work.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39927
Joined: 17-Aug-2003
# Posted on: 28-Apr-2006 20:41:44   

arschr wrote:

Fastest way is to add the entity to a UoW to save recursively, then call ConstructSaveProcessQueues on the UoW and you have your 2 queues with entities which are dirty or WILL get dirty because a dependent entity is scheduled to be saved.

I've not worked with uow. I'll look at it. Does it seem to you that it provides the features I'm asking for?

You actually can better use the class ObjectGraphUtils, which is in the ormsupportclasses as well, and then call one of the DetermineActionQueues routines. (that class is used to determine the queues for a graph anyway wink ) These then will produce the queues for you, and you just have to traverse these to get all dirty or to be dirty entities in your graph.

Though if this is for rolling back changes in a form if the user clicks cancel: do this per control/data that's bound: first call SaveFields() on all entities involved, then when cancel is clicked, call RollbackFields on those entities.

No, as it's not solvable. It's effectively 'graph' versioning, which is very very complex. It's also complicated because it can be that an entity effects more than 1 other related entities (when an entity has multiple m:1 relations with different entities for example).

I don't think graph versioning is what is needed, tree versioning is. Is that then solvable?

It's not a tree, unfortunately, it's a directed graph.

If I have a order or some other parent type entity, when I ask if it or it's children are dirty or if I ask to save it and it's children. It's not "normal" in the real world to want or expect that changes to it's parents would be included/saved. If I use an order to change the address of the customer it belongs to, I should save the customer and it's children not the order and it's parents and children. I know that what is the parent and what is the child is not always clear.

The IsDirty flag is to signal that THAT particular entity is changed and should be persisted. The thing is: as soon as you change 1 field in an entity in a graph, the whole graph gets 'dirty' by your definition, which is IMHO not the case: only that entity is dirty.

By simplifying the model to tree situations, I think you would meet 90% to 99% of the real world requirements while providing a feature that would save the users of llblgen a lot of work.

That's not possible, it's not a tree. I once started implementing graph versioning, but hit a wall while doing it, as it looks simple at first, but then the complexity explodes once you encounter what can happen.

I've thought about having versioning in a context. For example, you add an entity or a set of entities to a context and use that context to version the entities which are inside it. It effectively pushes the state of all the entities involved.

The problems began when I realized it didn't solve the problems I ran into when I started with versioning graphs before: what if you add an order to a customer's order collection, add entities to that etc. ? What if you save in between? This thus means that all entities related to a given entity in the context are also part of the state of the whole graph: if you dereference an entity, and roll back the state, the entity should again be referenced.

This is in a way solvable, but not easily. I chose to leave it for now, as often it's solveable as well by the application programmer who just wants to have versioning over a bunch of entities in a grid: traverse them prior to binding: call SaveFields(name) on all of them, and when cancel is clicked, traverse them again and call RollbackFields(name).

Frans Bouma | Lead developer LLBLGen Pro
JimFoye avatar
JimFoye
User
Posts: 656
Joined: 22-Jun-2004
# Posted on: 28-Apr-2006 23:20:34   

Here's how I solved the problem, and so far, at least, this has been working OK for me.

This is from a winforms app, there is a root entity whose graph will contain everything else; this root entity is a well (as in, oil well). That is, the user always opens a well, so any entities are ultimately related to it. Based on the suggestion from the previous thread, I copied some code from LLBLGen source.

Also note the handling for my UnitOfWork2 object that contains entities to be deleted.

I hope this helps someone else. Of course, I would welcome suggestions for improvement.


        // BLL variables 
        private WellEntity well;
        private UnitOfWork2 uow;

............

        public bool IsDirty()
        {
            if (well != null)
            {
                if (uow.GetEntityElementsToDelete().Count > 0)
                    return true;

                ObjectGraphUtils utils = new ObjectGraphUtils();
                Hashtable inProcess = new Hashtable(512);
                Hashtable recursed = new Hashtable(512);
                Hashtable postponed = new Hashtable(512);

                LinkedListNode sortedGraphList = utils.ProduceTopologyOrderedList(well, inProcess, recursed, postponed);
                while (sortedGraphList != null)
                {
                    if (sortedGraphList.Data.IsDirty)
                        return true;
                    sortedGraphList = sortedGraphList.Next;
                } 
            }
            return false;
        }



arschr
User
Posts: 894
Joined: 14-Dec-2003
# Posted on: 28-Apr-2006 23:51:50   

This is in a way solvable, but not easily. I chose to leave it for now, as often it's solveable as well by the application programmer who just wants to have versioning over a bunch of entities in a grid: traverse them prior to binding: call SaveFields(name) on all of them, and when cancel is clicked, traverse them again and call RollbackFields(name).

You've clearly thought about it much deeper than I have. smile

The way I see it the entity would know it was deepdirty via the following: - Entities and Entity collections would each maintain a deep dirty count.

  • An entity has fields and collections, it already knows if the fields are dirty. If any are -> deepdirty.

  • entities notify any collections they are in (via an event) when they become dirty or if they are already dirty when added -> deep dirty.

  • When a collection has one or more dirty entities it notifies any entities it's attached to (via an event). The entity maintains a count.

  • collections would keep a deep dirty count. If deep dirty entities are removed from a collection the collections deepdirty count would be decreased. If the count goes to zero an event is raised to tell the concerned entities.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39927
Joined: 17-Aug-2003
# Posted on: 29-Apr-2006 09:36:56   

Jim: the ProduceTopologyOrderedList routine is indeed a way to sort the complete graph and have it in a single list. It's used by the DetermineActionQueues routine. simple_smile Keep in mind that that routine has been rewritten a month ago or so, as it had a flaw, and that rewrite caused the signature to change (had to). Not that much, but you can run into it if you upgrade to the latest version / have already run into it if you've upgraded recently.

Arschr: What you propose isn't efficient: what if I have 100 entities in a graph with paths of 4 deep? then I have to signal entities 4 hops away that an entity has been changed. With events that can be truly slow, not to mention the nightmare when you remove an entity from it or add it to the graph. (as it has to reach signals from every other entity in the graph at some point. (at least that's my feeling about it).

Frans Bouma | Lead developer LLBLGen Pro
JimFoye avatar
JimFoye
User
Posts: 656
Joined: 22-Jun-2004
# Posted on: 29-Apr-2006 17:39:53   

Frans, I'm running 1.0.2005 1 Final (November 30th). Normally I'm pretty good about keeping up with updates, but looks like I haven't (can't believe it, wow!). There appears to be a release this month.

So what should I do to update my IsDirty routine, exactly? Are you saying the basic logic is OK, but I should find ProduceTopologyOrderedList in the source and just update it to use this?

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39927
Joined: 17-Aug-2003
# Posted on: 29-Apr-2006 19:01:19   

You should just compile against the current version, if you want to upgrade. If you don't run into the issues fixed since november 30, just stick with the current version. simple_smile . This routine's signature has changed, which is very rare, though was necessary and because it's a routine actually just used by the framework, I thought it wasn't that bad to make the change. The routine you call is still there though, but won't do anything.

You'll get a compiler error that the method you're using is obsolete and that you have to use the other overload: public ArrayList ProduceTopologyOrderedList(IEntity2 entityToExamine)

Which is easier, you just have to traverse that arraylist. The linked list is gone.

Frans Bouma | Lead developer LLBLGen Pro
JimFoye avatar
JimFoye
User
Posts: 656
Joined: 22-Jun-2004
# Posted on: 29-Apr-2006 22:41:04   

OK, thanks. My code now looks like this:


            if (well != null)
            {
                if (uow.GetEntityElementsToDelete().Count > 0)
                    return true;

                ObjectGraphUtils utils = new ObjectGraphUtils();
                ArrayList list = utils.ProduceTopologyOrderedList(well);
                foreach (IEntity2 entity in list)
                {
                    if (entity.IsDirty)
                        return true;
                }
            }
            return false;

JimFoye avatar
JimFoye
User
Posts: 656
Joined: 22-Jun-2004
# Posted on: 30-Aug-2006 18:03:50   

[Some months later....]

I'm upgrading to v2 this morning, and now my IsDirty routine generates several errors:


'SD.LLBLGen.Pro.ORMSupportClasses.ObjectGraphUtils' is inaccessible due to its protection level 

The type 'SD.LLBLGen.Pro.ORMSupportClasses.ObjectGraphUtils' has no constructors defined

'SD.LLBLGen.Pro.ORMSupportClasses.ObjectGraphUtils' does not contain a definition for 'ProduceTopologyOrderedList'

Suggestions?

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39927
Joined: 17-Aug-2003
# Posted on: 30-Aug-2006 19:25:50   

I think that's a mistake, I'll make it public.

Frans Bouma | Lead developer LLBLGen Pro
JimFoye avatar
JimFoye
User
Posts: 656
Joined: 22-Jun-2004
# Posted on: 30-Aug-2006 19:27:19   

Oh goody! After 550 posts I actually found something wrong!sunglasses

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39927
Joined: 17-Aug-2003
# Posted on: 30-Aug-2006 20:13:10   

heh simple_smile

Frans Bouma | Lead developer LLBLGen Pro
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39927
Joined: 17-Aug-2003
# Posted on: 31-Aug-2006 10:51:01   

Fixed in next build. (.NET 2.0 codebase. The .NET 1.x codebase already has this class as 'public')

Frans Bouma | Lead developer LLBLGen Pro