Implementing Partial Save of deletions (plus Undo)

May 18, 2012 at 3:15 AM
Edited May 18, 2012 at 3:23 AM

I've been working on implementing support for deletes in Partial Save, and also support for Partial Undo (context.RejectChanges) of additions, deletes.

I found there was no way around having to hold references for obsolete entities (I tried releasing on Detached, IdentityKeys). And then I found Undo added a whole other layer of complexity (Add, Delete during Reject). So I figured as long as people knew it was happening and could stop it, holding onto obsolete entities was survivable.

I bit the bullet and introduced another mode of behaviour for EntityGraph to TrackGraphedNodes.

I've now got it passing my unit Tests, but I'm having second thoughts about my choice of API members as that "DoTrackGraphedNodes mode" smells. Currently it used like

         === Initialise ===
        graph.TrackGraphedNodesStart();
 
         === Entity Changes here ===
 
         === Partial Save =
        EntityGraphTestsDomainContext tempContext = new EntityGraphTestsDomainContext();
        var clone = graph.Clone(tempContext);
        tempContext.AcceptChanges();    //or tempContext.RejectChanges();
        graph.SynchronizeTracked(context, clone);        
        graph.TrackGraphedNodesReset();
 
         === Dispose (release Entity references) ===
        graph.TrackGraphedNodesStop();

The context parameter in the SynchronizeTracked method is needed as the target Entity's states need updating. Which makes me question why we dont hold the context in the graph class.

Does that mode smell enough to justify reworking it to a new class? Say EntityGraphTracked

         === Initialise ===
         EntityGraphTracked graph = new EntityGraphTracked(context, sourceEntity, graphShape);          === Entity Changes here ===          === Partial Save =         EntityGraphTestsDomainContext tempContext = new EntityGraphTestsDomainContext();         var clone = graph.Clone(tempContext);         tempContext.AcceptChanges(); //or tempContext.RejectChanges();         graph.Synchronize(clone);                 graph.TrackGraphedNodesReset();          === Dispose (release Entity references) ===         graph.TrackGraphedNodesStop();

Is it a fair assumption that holding Entity references is ok as long as it can be stopped? Or are there consequences I haven't found yet?

I'm currently testing it in my LOB project, but would like Feedback on the approach.

Regards,

Peter

May 25, 2012 at 5:57 AM

Hi Peter,

That's a wonderful thing you've done. It would really great if you pulled this off. I was badly looking for this as I was having the requirement of doing the Partial Save of the deleted Entities. Keep us posted on the updates. Good Luck.

Thanks 

Syam

Jun 22, 2012 at 4:52 AM

Hi Peter,

Will it be possible for you to share the customized source code of EntityGraph which can do Partial Save with deletions. It will be extremely helpful for me as I am having a similar requirement.

Thanks

Syam 

Jun 25, 2012 at 10:27 PM

Hi Syam,

Yes I can do that. What is the best method for sharing, just a big zip?

It's proved mostly stable, I've had to make only some minor changes. However just yesterday I think I have uncovered a bug so I need to resolve that.

I have done anything about refactoring/improving the API. I've ended up with several "if (IsTrackGraphedNodes) ..." which make me think it should be refactored into an derived class. I still have some edge methods that arent implemented for TrackGraphedNodes (copy & clone functions, IEditable ?).

Peter

Jun 26, 2012 at 10:53 AM

Hi Peter,

Thanks a lot. Appreciate your help.

You can send me as a zip to my personal email address(wickedsyam@gmail.com).

I have been using the EntityGraph by Merijn, mostly the Clone method and Synchronize method. So it would be great if you send me the source once you implement the Clone method.

Thanks in Advance

Syam

Jul 11, 2012 at 11:11 AM

Hi Peter,

Sorry for bugging you again. Can you tell me an approximate time by which you will be able to complete the Clone feature and share the source with me.

Thanks

Syam

Jul 17, 2012 at 12:40 PM

Hi Syam,

Sorry for the delay, was away on business, holiday. I've sent the code separately to you.

TODO

  • Clone() GraphedNodes  no context parm 
  • Copy GraphedNodes
  • IsCopyOf GraphedNodes
  • Synchronize MergeIntoCurrent GraphedNodes
  • Resolve if should implement IEditableObject for EntityRelationGraph.DroppedNodes?

I've added a number of tests, including specifically the SubmitChanges/RejectChanges scenarios. The code seems to be working in my limited application.

I discovered a related problem when processing changesets. By default multiple changes submitted all at once get processed in an unknown order on the server.
 Delete Child followed by Add, gets processed on Server as Add & then Delete.
 So if you reuse Child keys ==> problems

One possible solution is to in the Web Server project service override Submit method. In there process the deletes first, then the remain changes. See http://msdn.microsoft.com/en-us/library/ee707364%28v=vs.91%29.aspx

        public override bool Submit(ChangeSet changeSet)
        {
            bool submitOk = true;
            using (var scope = new TransactionScope())
            {               
                ChangeSet changeSetDeletes = new ChangeSet(changeSet.ChangeSetEntries.Where(t => t.Operation == DomainOperation.Delete));
                ChangeSet changeSetNotDeletes = new ChangeSet(changeSet.ChangeSetEntries.Where(t => t.Operation != DomainOperation.Delete));
                if (changeSetDeletes.ChangeSetEntries.Count > 0)
                    submitOk = base.Submit(changeSetDeletes);
                if (changeSetNotDeletes.ChangeSetEntries.Count > 0)
                    submitOk = submitOk & base.Submit(changeSetNotDeletes);

                //Does not update changeSet parm
                //Does not update and is not able to change this.ChangeSet. It is readonly

                if (!changeSetDeletes.HasError && !changeSetNotDeletes.HasError)
                    scope.Complete();               
            }
            return submitOk;
        }