New Posts New Posts RSS Feed: How to create a calculated field that automatically updates when other fields change
  FAQ FAQ  Forum Search   Calendar   Register Register  Login Login

How to create a calculated field that automatically updates when other fields change

 Post Reply Post Reply
Author
Sinnema View Drop Down
Groupie
Groupie
Avatar

Joined: 23-Mar-2009
Location: Muri AG, EU, CH
Posts: 54
Post Options Post Options   Quote Sinnema Quote  Post ReplyReply Direct Link To This Post Topic: How to create a calculated field that automatically updates when other fields change
    Posted: 24-Jun-2009 at 7:20am
Hi,
 
We would like to create a new Property on the Business Object that composes 2 other fields (FullName is composed out of FirstName + " " + LastName). When FirstName or LastName changes the FullName should also change and fire it's updates.
 
We've already created the following code that works fine for a change but is not fired when the Object is loaded the first time.
 
Regards,
Paul Sinnema
Diartis AG
 

public partial class Person

{

static Person()

{

KLIBEntityManager.DefaultManager.VerifierEngine.DiscoverVerifiers(typeof(Person));

}

public Person()

{

// TODO: First load of entity does not set the FullName (i.e. does not fire the setter below).

PropertyInterceptorAction firstNameAction = new PropertyInterceptorAction<DataEntityPropertySetInterceptorArgs<DomainModel.Person, string>>(typeof(DomainModel.Person),

DomainModel.Person.EntityPropertyNames.FirstNames,

PropertyInterceptorMode.AfterSet,

FirstNameSetter);

PropertyInterceptorManager.CurrentInstance.AddAction(firstNameAction);

}

private void FirstNameSetter(DataEntityPropertySetInterceptorArgs<DomainModel.Person, string> args)

{

SetFullName();

}

public string CurrentLastName

{

get

{

var q = from ln in LastName

where ln.Person.Id == this.Id

orderby ln.ValidToHistory descending

select ln;

if (q.Count() > 0)

{

LastName lastName = q.First();

return lastName.Name;

}

return string.Empty;

}

}

/// <summary>

/// Get the Full Name for a Person. If no LastNames are present, only the FirstNames are returned.

/// </summary>

/// <returns></returns>

private void SetFullName()

{

string currentLastName = CurrentLastName;

if (currentLastName.Length > 0)

{

FullName = currentLastName + ", " + FirstNames;

}

else

{

FullName = FirstNames;

}

}

public string FullName

{

get { return (string)FullNameEntityProperty.GetValue(this); }

set { FullNameEntityProperty.SetValue(this, value); }

}

public static readonly IbEm.DataEntityProperty<Person, String> FullNameEntityProperty =

new IbEm.DataEntityProperty<Person, String>("FullName", true, false, IbEm.ConcurrencyStrategy.None, false, IbEm.VerificationSetterOptions.Both);

Back to Top
smi-mark View Drop Down
DevForce MVP
DevForce MVP
Avatar

Joined: 24-Feb-2009
Location: Dallas, Texas
Posts: 343
Post Options Post Options   Quote smi-mark Quote  Post ReplyReply Direct Link To This Post Posted: 06-Jul-2009 at 1:08pm
In the First Name / Last Name setters you should do this:

this.EntityAspect.ForcePropertyChanged(null);

It will cause all the calculated properties to be 'reset'
Back to Top
eileenv View Drop Down
IdeaBlade
IdeaBlade
Avatar

Joined: 15-Jun-2007
Location: United States
Posts: 68
Post Options Post Options   Quote eileenv Quote  Post ReplyReply Direct Link To This Post Posted: 06-Jul-2009 at 6:59pm

PropertyInterceptors are not fired during object load, but when a property is get or set.

Why don't you implement your logic for constructing the FullName in the getter of the FullName property?

For example,
 
public String FullName {
  get {
    string currentLastName = CurrentLastName;
   if (currentLastName.Length > 0) {
     return currentLastName + ", " + FirstNames;
   } else {
     return FirstNames;
   } 
  }
}
 
Then you can define property interceptors on the FirstName and LastName that call ForcePropertyChanged to force an update of all the Person's custom/calculated properties whenever FirstName or LastName changes:
 
    [AfterSet(EntityPropertyNames.FirstName)]
    [AfterSet(EntityPropertyNames.LastName)]
    public void AfterSetFirstOrLastName(PropertyInterceptorArgs<Employee, String> args) {
      this.EntityAspect.ForcePropertyChanged(null);
    }
 
 
Back to Top
Sinnema View Drop Down
Groupie
Groupie
Avatar

Joined: 23-Mar-2009
Location: Muri AG, EU, CH
Posts: 54
Post Options Post Options   Quote Sinnema Quote  Post ReplyReply Direct Link To This Post Posted: 16-Jul-2009 at 2:56am
Eileen,
 
We've found a beautifull solution here it is:
 

/// <summary>

/// Set the Full Name for a Person. If no LastNames are present, only the FirstNames are returned.

/// </summary>

/// <returns></returns>

[AfterSet(EntityPropertyNames.FirstNames)]

[AfterSet(EntityPropertyNames.LastName)]

[BeforeGet("FullName")]

public void AfterChangeFirstOrLastName(PropertyInterceptorArgs<Person, String> args)

{

    SetFullName();

}

private void SetFullName()

{

string currentLastName = CurrentLastName;

    if (currentLastName.Length > 0)

    {

        FullName = currentLastName + ", " + FirstNames;

    }

    else

    {

        FullName = FirstNames;

    }

}

public string FullName

{

    get { return (string)FullNameEntityProperty.GetValue(this); }

    set { FullNameEntityProperty.SetValue(this, value); }

}

public static readonly IbEm.DataEntityProperty<Person, String> FullNameEntityProperty =

new IbEm.DataEntityProperty<Person, String>("FullName", true, false, IbEm.ConcurrencyStrategy.None, false, IbEm.VerificationSetterOptions.Both);



Edited by Sinnema - 16-Jul-2009 at 2:58am
Back to Top
Sinnema View Drop Down
Groupie
Groupie
Avatar

Joined: 23-Mar-2009
Location: Muri AG, EU, CH
Posts: 54
Post Options Post Options   Quote Sinnema Quote  Post ReplyReply Direct Link To This Post Posted: 22-Jul-2009 at 4:31am

We still have a problem with this solution. The Property is registered on the Model. This means that any change to the 'FullName' causes the Entity Modified flag to be set.

We now do our initialization and then call:
 
        m_Person.EntityAspect.AcceptChanges();
 
This solves our problem (temporarily) but we would like to solve this a better way. Questions:
Is the way we register the property correct? Can we register a property as having no influence on the Entity?
Maybe decorators [.....]
 
Regards,
Paul Sinnema
Diartis AG
Back to Top
Sinnema View Drop Down
Groupie
Groupie
Avatar

Joined: 23-Mar-2009
Location: Muri AG, EU, CH
Posts: 54
Post Options Post Options   Quote Sinnema Quote  Post ReplyReply Direct Link To This Post Posted: 22-Jul-2009 at 7:27am
Eileen,
 
Forgot to give some feedback on your reply with the Getter.
 
In the solution you propose what would happen if the FirstName or LastName change. We would like the FullName to change too.
 
Regards,
Paul.
Back to Top
Sinnema View Drop Down
Groupie
Groupie
Avatar

Joined: 23-Mar-2009
Location: Muri AG, EU, CH
Posts: 54
Post Options Post Options   Quote Sinnema Quote  Post ReplyReply Direct Link To This Post Posted: 29-Jul-2009 at 9:18am
Bump
Back to Top
Sinnema View Drop Down
Groupie
Groupie
Avatar

Joined: 23-Mar-2009
Location: Muri AG, EU, CH
Posts: 54
Post Options Post Options   Quote Sinnema Quote  Post ReplyReply Direct Link To This Post Posted: 04-Aug-2009 at 5:10am

Can we deduce from the fact that no replies are posted anymore to our question that it is not possible to create a calculated field the way we've programmed it without changing the Entity to state = modified?

The Problem for us is that after a load of the Person Entity (in this case) the BeforeGet is fired on the SetFullName which in turn changes the FullName Property which in turn sets the Entity to modified. FullName is only a calculated field and is never stored in the DB. Since we automatically save changes (without a save button being activated) we have gotten a performance problem. The Save takes 2 seconds (which is too long, but IdeaBlade is working on that) and we save when switching Tabs in a TabControl
Back to Top
eileenv View Drop Down
IdeaBlade
IdeaBlade
Avatar

Joined: 15-Jun-2007
Location: United States
Posts: 68
Post Options Post Options   Quote eileenv Quote  Post ReplyReply Direct Link To This Post Posted: 06-Aug-2009 at 11:24am
The entity should only become modified if the underlying native properties of the calculated property (in this case FirstNames and CurrentLastName) have changed. You may have come across a bug. I will investigate this further and keep you posted.

Edited by eileenv - 07-Aug-2009 at 9:50am
Back to Top
eileenv View Drop Down
IdeaBlade
IdeaBlade
Avatar

Joined: 15-Jun-2007
Location: United States
Posts: 68
Post Options Post Options   Quote eileenv Quote  Post ReplyReply Direct Link To This Post Posted: 06-Aug-2009 at 12:37pm
Yup, this is a bug. We will fix this in our next release. The fix will be to provide an additional constructor for DataEntityProperty with a flag that will allow you to specify whether or not this property is a native property (default=true). Therefore, for calculated properties you would set this flag to false in the constructor and this would guarantee that the entity state will not be set to modified if you call set on the calculated property.
Back to Top
Sinnema View Drop Down
Groupie
Groupie
Avatar

Joined: 23-Mar-2009
Location: Muri AG, EU, CH
Posts: 54
Post Options Post Options   Quote Sinnema Quote  Post ReplyReply Direct Link To This Post Posted: 06-Aug-2009 at 11:32pm
Hi Eileen,
 
That is indeed good news. We'll keep the code as is and enhance as soon as the next release is available.
 
Regards,
Paul Sinnema
Diartis AG
Back to Top
eileenv View Drop Down
IdeaBlade
IdeaBlade
Avatar

Joined: 15-Jun-2007
Location: United States
Posts: 68
Post Options Post Options   Quote eileenv Quote  Post ReplyReply Direct Link To This Post Posted: 10-Aug-2009 at 12:21pm
Paul,

In the interim, I believe this code would also work for you. It implements the logic of setting the FullName in the getter of the FullName property which is equivalent to defining a BeforeGet property interceptor. This in turn avoids the need to construct and use a DataEntityProperty which causes the bug I mentioned earlier.

    public String FullName {
      get {
        string currentLastName = CurrentLastName;
        if (currentLastName.Length > 0) {
          _fullName = currentLastName + ", " + FirstNames;
        } else {
          _fullName = FirstNames;
        }
        return _fullName;
      }
      set { _fullName = value; }
    }
    private static String _fullName;

    [AfterSet(EntityPropertyNames.FirstName)]
    [AfterSet(EntityPropertyNames.LastName)]
    //[BeforeGet("FullName")]
    public void AfterChangeFirstOrLastName(PropertyInterceptorArgs<Person, String> args)
    {
      SetFullName();
    }
Back to Top
Sinnema View Drop Down
Groupie
Groupie
Avatar

Joined: 23-Mar-2009
Location: Muri AG, EU, CH
Posts: 54
Post Options Post Options   Quote Sinnema Quote  Post ReplyReply Direct Link To This Post Posted: 12-Aug-2009 at 1:26am

Hi Eileen,

Thanks for the reply.
 
No, this code will not work for all purposes. When FirstName or LastName changes, no push (interceptor) of the FullName is happening. We can change the FirstName in the Mask but the Interceptor for the FullName is never fired. It would need a pull from the View on the ViewModel to retrieve the FullName in this case.
 
This is how we implemented it in our ViewModel:
 
On retrieval we create a VMPerson which wraps a Person Entity. In the Init of the VMPerson all properies are tied up with an Intercepter like this:
 
========================================================================
   m_NicknameAction = new PropertyInterceptorAction<DataEntityPropertySetInterceptorArgs<DomainModel.Person, String>>(
          typeof(DomainModel.Person),
          DomainModel.Person.EntityPropertyNames.Nickname,
          PropertyInterceptorMode.AfterSet,
          NicknameSetter);
   PropertyInterceptorManager.CurrentInstance.AddAction(m_NicknameAction);
      m_Nickname.Value = m_Person.Nickname;
  m_Nickname.ValueChanged += NicknameOnValueChanged;
========================================================================
 
In the Init we call a partial method called InitPerson which adds the Intercepter for the FullName
 
========================================================================
  /// <summary>
  /// Called before initializing all properties for VMPerson
  /// </summary>
  partial void InitPerson()
  {
   m_FullName = new KLIBCustomProperty<KLIBStringProperty, string, DomainModel.Person>("FullName",
                        this.m_Person);
  }
========================================================================
 
In the Constructor of the KLIBCustomProperty the following happens:
 
========================================================================
  /// <summary>
  /// Initializing interceptor from the Model and the properties value
  /// </summary>
  /// <param name="propertyName"></param>
  /// <param name="entity"></param>
  public KLIBCustomProperty(string propertyName, Entity entity)
  {
   m_Entity = entity;
   m_Action = new PropertyInterceptorAction<DataEntityPropertySetInterceptorArgs<TInstance, TVariableType>>(
                   typeof(TInstance),
                   propertyName,
                   PropertyInterceptorMode.AfterSet,
                   Setter);
   PropertyInterceptorManager.CurrentInstance.AddAction(m_Action);
   DataEntityProperty dataEntityProperty = entity.EntityAspect.GetDataProperty(propertyName);
   m_Property.Value = (TVariableType)dataEntityProperty.GetValue(m_Entity);
  }
 
  /// <summary>
  /// This setter is called when the Property in the Model changes
  /// </summary>
  /// <param name="args"></param>
  private void Setter(DataEntityPropertySetInterceptorArgs<TInstance, TVariableType> args)
  {
   if (args.Instance.Equals(m_Entity))
   {
    if (!Object.Equals(args.Value, m_Property.Value))
    {
     m_Property.Value = (TVariableType)args.Value;
    }
   }
  }
========================================================================
 
As you can see the FullName is using an Interceptor to track changes and uses a Setter to communicate with WPF.
 
All Code in the ViewModel is generated using a T4 script. The InitPerson is custom code.
 
Regards,
Paul Sinnema
Diartis AG


Edited by Sinnema - 12-Aug-2009 at 1:29am
Back to Top
midnit View Drop Down
Senior Member
Senior Member
Avatar

Joined: 22-Jun-2009
Location: Charlotte
Posts: 112
Post Options Post Options   Quote midnit Quote  Post ReplyReply Direct Link To This Post Posted: 17-Aug-2009 at 9:23am

Are you binding to this "Fullname" property? If so I'm having a hard time seeing all the need for all the code. putting AfterSet on the FirstName and LastName that raise the onpropertychanged("Fullname") would alert the UI to re-get Fullname. And of course if you are checking the object through code you will trigger the re-get every time you reference it.

The code eileenv proposed would be fine, just add the propertychanged.
 
I would propose: (depending on if you really want to "set" the fullname or just "get" it) it sounds like you just need the get
 
    public String FullName {
      get {

        string currentLastName = CurrentLastName;
        if (currentLastName.Length > 0) { 
          return = currentLastName + ", " + FirstNames;
        } else { 
          return = FirstNames;
        } 
    } 

    [AfterSet(EntityPropertyNames.FirstName)]
    [AfterSet(EntityPropertyNames.LastName)] 
    public void AfterChangeFirstOrLastName(PropertyInterceptorArgs<Person, String> args)
    {
        OnPropertyChanged(new System.ComponentModel.PropertyChangedEventArgs("FullName"));
    }
 
And if you need it to pop (trigger UI to update) just call the OnPropertyChanged at any point. Or maybe I am not understanding your need...
Back to Top
 Post Reply Post Reply

Forum Jump Forum Permissions View Drop Down