FieldManager.IsFieldDirty behaviour on a field that is a child object

FieldManager.IsFieldDirty behaviour on a field that is a child object

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


chrisdot posted on Thursday, April 15, 2010

Hello,

I was trying to use the FieldManager.IsFieldDirty(...) method with managed backing fields. Everything seems alright if I use it on simple fields like int, strings, datetimes, etc...But if I use it on a field that is a child object (BusinessBase object type) , then if I change the reference, the IsFieldDirty() method still returns false (like "unchanged"). I was expecting to get "true" (like "changed").

Is this normal? Did I miss something?

On the other hand it seems to work with a ReadOnlyBase type child object.

(I'm using this in cslaslight 8.3.2, on SL3)

Thank you,

Christophe

rsbaker0 replied on Thursday, April 15, 2010

Are you maintaining your own private backing field or letting CSLA hold the reference inside the FieldManager?

chrisdot replied on Thursday, April 15, 2010

No, I'm not using private backing fields, but (csla) managed backing fields. (In the book, there's a strong recommandation about using it like that  when concerning children objects.)

 

rsbaker0 replied on Thursday, April 15, 2010

In that case, whether or not the property is considered dirty is delegated to the actual child object itself being stored in the FieldManager.

So, replacing the property value from one "clean" child to a another clean child won't cause the property itself to be considered dirty. 

You can see this in the implementation of FieldData.IsDirty

    /// 
    /// Gets a value indicating whether the field
    /// has been changed.
    /// 
    public virtual bool IsDirty
    {
      get
      {
        ITrackStatus child = _data as ITrackStatus;
        if (child != null)
        {
          return child.IsDirty;

        }
        else
        {
          return _isDirty;
        }
      }
    }

chrisdot replied on Friday, April 16, 2010

Ok, thank you, now I understand why. Basically, because a BusinessBase object implements the ITrackStatus interface, and the ReadOnlyBase doesn't. (Sorry I'm quite new to CSLA).

I still have a question. I have a relation between a parent object and a single child object. Does CSLA provide a way to both change the relation property on the parent  (in short, changing the parent's assigned child) and also to modify the new selected child's properties (in the same trip?). How should I proceed if this is possible ? Should I use my own private backing field ? Is there an sample somewhere I could rely on?

Thank you

Christophe

rsbaker0 replied on Friday, April 16, 2010

I would think that if the new child object you are assigning is also dirty, then the parent object would now consider both itself and the child to be dirty and then a Save() operation would allow you to persist the changes to the database.

 

 

chrisdot replied on Friday, April 16, 2010

This makes sense. But in that case, I still have my original problem. How can I know that we changed the reference to the child? I'm not sure to explain it properly. My problem is not to detect the changes in the child, it is mostly on the parent to detect that the child is no more the same instance (not that the child's data has been changed). And additionnaly there is still the case where only the reference to the child has been changed and not the child's data itself.

Do I have to process this with my own private backing field ?

RockfordLhotka replied on Friday, April 16, 2010

This is an interesting situation.

So what you really want, I think, is to have a combination of the primitive field and child object behaviors. In other words, IsDirty is:

Is this what you need?

chrisdot replied on Friday, April 16, 2010

Yes exactly !

This is my current case in a parent to single child relation. But I imagine that this case could also apply to a parent to collection children? Am I right? Or will the child collection manage it for you in that case ? I'll try one of these days.

RockfordLhotka replied on Friday, April 16, 2010

Collections are easier, because the collection tracks add/remove operations of its child objects.

Here's a workaround for you now - put a "ChildManager" in between the root and child. I'll add this to the wish list - I agree that the behavior should work as described in my previous post - probably something that'll end up in 4.1 at this point.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Csla;
using System.Diagnostics;

namespace ConsoleApplication1
{
  class Program
  {
    static void Main(string[] args)
    {
      var root = new Root();
      Debug.Assert(root.Child != null, "Child should not be null");
      Debug.Assert(!root.IsNew, "Root should not be new");
      Debug.Assert(!root.IsDirty, "Root should not be dirty");

      root.Child.Child = new Child();
      Debug.Assert(!root.IsNew, "Root should not be new");
      Debug.Assert(root.IsDirty, "Root should be dirty");

      Console.WriteLine("All is well");
      Console.ReadLine();
    }
  }

  [Serializable]
  public class Root : BusinessBase<Root>
  {
    public Root()
    {
      Child = new ChildManager();
      Child.LoadChild(new Child());
      MarkOld();
    }

    private static PropertyInfo<int> IdProperty = RegisterProperty<int>(c => c.Id);
    public int Id
    {
      get { return GetProperty(IdProperty); }
      set { SetProperty(IdProperty, value); }
    }

    private static PropertyInfo<ChildManager> ChildProperty = RegisterProperty<ChildManager>(c => c.Child);
    public ChildManager Child
    {
      get { return ReadProperty(ChildProperty); }
      set { LoadProperty(ChildProperty, value); }
    }
  }

  [Serializable]
  public class Child : BusinessBase<Child>
  {
    public Child()
    {
      MarkOld();
    }
  }

  [Serializable]
  public class ChildManager : BusinessBase<ChildManager>
  {
    public ChildManager()
    {
      MarkOld();
    }

    public override bool IsSelfDirty
    {
      get
      {
        return ChildReferenceChanged || base.IsSelfDirty;
      }
    }

    private static PropertyInfo<bool> ChildReferenceChangedProperty = RegisterProperty<bool>(c => c.ChildReferenceChanged);
    private bool ChildReferenceChanged
    {
      get { return ReadProperty(ChildReferenceChangedProperty); }
      set { LoadProperty(ChildReferenceChangedProperty, value); }
    }

    public void LoadChild(Child child)
    {
      LoadProperty(ChildProperty, child);
    }

    private static PropertyInfo<Child> ChildProperty = RegisterProperty<Child>(c => c.Child);
    public Child Child
    {
      get { return ReadProperty(ChildProperty); }
      set
      {
        LoadProperty(ChildProperty, value);
        ChildReferenceChanged = true;
        OnPropertyChanged("Child");
      }
    }
  }
}

chrisdot replied on Monday, April 19, 2010

Thank you Rocky & rsbaker0 for your answers. I'll try your solutions. I'm looking forward for v4.1 of CSLA then... ;-)

rsbaker0 replied on Friday, April 16, 2010

As an alternative in the meantime, I believe OnPropertyChanged() still gets called even when replacing a reference to a child object (except for the odd case I reported a couple of days ago when you have overridden the Equals() implementation and the new child object has the same "value"), so you could override that in your base class and use it to set an additional private flag that you could use to indicate the child had been replaced.   You could also override IsDirty() so that it also looks at the flag indicating a new child had been replaced.

 

Copyright (c) Marimer LLC