Altering the behavior of IsDirty

Altering the behavior of IsDirty

Old forum URL: forums.lhotka.net/forums/t/7653.aspx


RockfordLhotka posted on Friday, September 18, 2009

Jason Bock recently published a blog post detailing the steps to follow when altering the behavior of IsDirty when working with managed backing fields. His code works in CSLA .NET 3.7.1 or higher.

http://www.jasonbock.net/JB/Default.aspx?blog=entry.9cc70d85bef34e2b9a683ba82615f8a3

 

rfcdejong replied on Sunday, September 20, 2009

Nice blog and nice solution, the wish for the alternative IsDirty behaviour is really there.

However using this alternative IsDirty should stay optional, please don't impliment this into csla without a flag to switch the IsDirtyBehaviour.

- Double memory usage;
- Double serialize of data (a bit useless to serialize the old values?)
- Heavy CPU usage.

Consider an wpf command listening to BO.IsDirty() for its CanExecute. Default Csla behaviour checks an boolean while the solution in the blog iterates all properties and children's properties.

rsbaker0 replied on Sunday, September 20, 2009

I posted an alternative here, in the context of also being able to log changes to an audit log (which requires the actual previous value, not available in the above solution).

http://forums.lhotka.net/forums/post/35814.aspx

This was inspired by Rocky's undo implementation, in which the state is kept in a HybridDictionary.

The above implementation keeps only the original values of *changed* properties and doesn't pre-reserve space for each property. So, if you haven't changed anything there is no overhead, and then the overhead is proportional to the number of changed properties. Testing for dirty is fast and trivial (if the original value cache is empty, then the object is clean, otherwise it's dirty).

JonnyBee replied on Wednesday, October 21, 2009

Hi,

There are 2 implementations of FieldData in Jasons sample:
FieldDataUsingOriginalValueViaHashCode: compute hashcode for strings  as OriginalValue on first change
FieldDataUsingOriginalValueViaDuplicate: compares OriginalValue and Value

Both classes inherit from abstract class FieldDataUsingOriginalValue that can be used for auditing.

Please note - all values are NOT duplicated (so will not cause double memory usage). Only "Dirty" (ie: modified fields by user) will have a values in OriginalValue.

JonnyBee replied on Wednesday, October 21, 2009

Hi all,

Jasons sample code on Custom Field Data is now adapted for Csla 3.7.1 for N2 and available in the MyCsla 3.7.1 project (intermediate base classes) on CslaContrib.

A description of changes made for running on Csla 3.7.1. for .Net 2.0 can be found in my blog article: Using Custom FieldData in Csla 3.7.1 for Net 2.0

Also thanks to Tiago for contributing code and feedback.

jjhartma replied on Sunday, November 15, 2009


I've been trying to use this code and I've been having a problem with my object not getting flagged dirty when an object in a child collection is changed.

I've narrowed it down to this code in FieldDataUsingOriginalValue.cs:

        public override bool IsDirty
        {
            get
            {
                return (this.HasMarkCleanBeenCalled ? this.HasValueChanged() : base.IsDirty);
            }
        }

Once the field has been loaded the HasMarkClean property always returns true, so HasValueChanged always gets called.  HasValueChanged just compares the field with the original value (which in the case of a collection doesn't actually change) and doesn't call the IsDirty of the child collection like the base implementation would.

Has anyone worked around this problem yet?  I keep running into problems with not being able to access the private variables of the FieldData base class.

JonnyBee replied on Monday, November 16, 2009

Hi,

Good spot -  this is a bug in Jasons code when handling child object/lists that implement ITrackStatus themselves.

Csla default fielddata implements this as:
    public virtual bool IsDirty
    {
      get
      {
        ITrackStatus child = _data as ITrackStatus;
        if (child != null)
        {
          return child.IsDirty;
        }
        else
        {
          return _isDirty;
        }
      }
    }


I made the necessary changes in the FieldDataClasses from Jasons example and added them in zip file. This test should also be used to not make a copy of value/hashcode, also included in zip file. I created a unit test and IsDirty works correct when both changing a child property and after restoring the original child value.

jjhartma replied on Monday, November 16, 2009


Wow, that fix is so easy, and so much more elegant than anything I was trying.

Thanks!

jjhartma replied on Monday, November 16, 2009


I'm having another issue that hopefully someone can help explain.  The following code is from the BusinessCore class, and I don't understand the check for RelationshipType.  It appears to me that RelationshipType is set to Child by default, so all of my properties, whether they are objects/collections or not, are marked as Child.  Therefore the IsFieldDirty is never called on any of the properties and it always returns False.  Am I mistaken?

        public override bool IsSelfDirty
        {
            get
            {
                var isSelfDirty = false;
                
                foreach(var registeredProperty in this.FieldManager.GetRegisteredProperties())
                {
                    if(((registeredProperty.RelationshipType & RelationshipTypes.Child) == 0) &&
                        this.FieldManager.IsFieldDirty(registeredProperty))
                    {
                        isSelfDirty = true;
                        break;
                    }
                }
                
                return isSelfDirty;
            }
        }


Thanks!

RockfordLhotka replied on Monday, November 16, 2009

The default is Child, that is true. Maybe that shouldn't be the default?
 

jjhartma replied on Monday, November 16, 2009


This is my first CSLA based application, so I'm a totally newbie and don't completely understand the framework yet.  With that said, of the few business objects I've created so far most of my properties aren't child objects.  I'm going to have to modify the templates I'm using to generate the BOs either way, but my opinion would be that Child not be the default.  Just my 2 cents.

JonnyBee replied on Tuesday, November 17, 2009

Hi,

I think it is confusing to look at RelationshipTypes Enum. Only possible values are Child and LazyLoad - no indication that this is a "value" property of the actual BO. 

And it is not binary flags so is not correct to do a binary "and" on Child value.

The only "proper" way to test for a "child" object is to test if the value object implements ITrackStatus.

Maybe there should be a enum value in RelationshipTypes that says "Value" or "None" and this is the default value - and use binary flags so it would be easier to do binary and (&).

The code for IsSelfDirty should be:
public override bool IsSelfDirty
{
  get
  {
     var isSelfDirty = false;
              
     foreach(var registeredProperty in this.FieldManager.GetRegisteredProperties())
    {
       var child = this.FieldManager.GetFieldData(registeredProperty).Value as ITrackStatus;
       // if value implements ITrackStatus it is a child/list object
       if (child == null && this.FieldManager.IsFieldDirty(registeredProperty))
      {
        isSelfDirty = true;
        break;
      }
    }
              
     return isSelfDirty;
  }
}


jjhartma replied on Tuesday, November 17, 2009


In CSLA 3.8.1 the RelationshipTypes enum has the [Flags] attribute.  It's also referenced in a few places in the code as a binary flag checking for the LazyLoad flag.  The problem for me was that there is no None option to clear the default in my RegisterProperty calls.

I ended up changing my copy of the framework to include a None = 0 option, changed the default to None, and then updated my templates to apply Child to objects.  That worked great, however unless the plan is to make this change in the released version of CSLA, I may just use your ITrackStatus method so that I'm not required to use a modified version of the framework.

jjhartma replied on Sunday, November 22, 2009


I've found one more issue that I figured I would point out for people interested in using this code.

If an object is Deleted, the IsDirty and IsSelfDirty methods in BusinessCore don't check that and will return false.  I fixed this by adding checks for IsDeleted in both IsDirty and IsSelfDirty:

        public override bool IsDirty
        {
            get
            {
                return this.IsDeleted || this.FieldManager.IsDirty();
            }
        }

        public override bool IsSelfDirty
        {
            get
            {
                var isSelfDirty = false;

                if (IsDeleted)
                    return true;

                foreach(var registeredProperty in this.FieldManager.GetRegisteredProperties())
                {
                    if(((registeredProperty.RelationshipType & RelationshipTypes.Child) == 0) &&
                        this.FieldManager.IsFieldDirty(registeredProperty))
                    {
                        isSelfDirty = true;
                        break;
                    }
                }
               
                return isSelfDirty;
            }
        }

Copyright (c) Marimer LLC