Author |
Share Topic Topic Search Topic Options
|
midnit
Senior Member
Joined: 22-Jun-2009
Location: Charlotte
Posts: 112
|
Post Options
Quote Reply
Topic: How to create a calculated field that automatically updates when other fields change 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...
|
 |
Sinnema
Groupie
Joined: 23-Mar-2009
Location: Muri AG, EU, CH
Posts: 54
|
Post Options
Quote Reply
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
|
 |
eileenv
IdeaBlade
Joined: 15-Jun-2007
Location: United States
Posts: 68
|
Post Options
Quote Reply
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();
}
|
 |
Sinnema
Groupie
Joined: 23-Mar-2009
Location: Muri AG, EU, CH
Posts: 54
|
Post Options
Quote Reply
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
|
 |
eileenv
IdeaBlade
Joined: 15-Jun-2007
Location: United States
Posts: 68
|
Post Options
Quote Reply
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.
|
 |
eileenv
IdeaBlade
Joined: 15-Jun-2007
Location: United States
Posts: 68
|
Post Options
Quote Reply
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
|
 |
Sinnema
Groupie
Joined: 23-Mar-2009
Location: Muri AG, EU, CH
Posts: 54
|
Post Options
Quote Reply
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
|
 |
Sinnema
Groupie
Joined: 23-Mar-2009
Location: Muri AG, EU, CH
Posts: 54
|
Post Options
Quote Reply
Posted: 29-Jul-2009 at 9:18am |
Bump
|
 |
Sinnema
Groupie
Joined: 23-Mar-2009
Location: Muri AG, EU, CH
Posts: 54
|
Post Options
Quote Reply
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.
|
 |
Sinnema
Groupie
Joined: 23-Mar-2009
Location: Muri AG, EU, CH
Posts: 54
|
Post Options
Quote Reply
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
|
 |
Sinnema
Groupie
Joined: 23-Mar-2009
Location: Muri AG, EU, CH
Posts: 54
|
Post Options
Quote Reply
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
|
 |
eileenv
IdeaBlade
Joined: 15-Jun-2007
Location: United States
Posts: 68
|
Post Options
Quote Reply
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); }
|
 |
smi-mark
DevForce MVP
Joined: 24-Feb-2009
Location: Dallas, Texas
Posts: 343
|
Post Options
Quote Reply
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'
|
 |
Sinnema
Groupie
Joined: 23-Mar-2009
Location: Muri AG, EU, CH
Posts: 54
|
Post Options
Quote Reply
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);
|
 |