Preventing IsDirty when setting to default value

Posts   
 
    
Posts: 18
Joined: 26-Jul-2006
# Posted on: 27-Sep-2006 05:20:01   

I am wiring up my first real form using LLBLGen and I've run into a problem regarding the IsDirty flag, related to default values.

I have a UnitOfMeasureEntity, which has the following properties:

  • string Name
  • string Abbr
  • double RatioToPounds I have a form for editing/adding units of measure, which displays textboxes for these 3 fields. I'm using an MVP pattern in my WinForms 2.0 application. I'm using LLBLGen Pro 2.0 by the way.

Here's my scenario

  1. User clicks Add...
  2. This opens the edit/add form
  3. User then clicks Cancel, without entering any data

At this point, I want to be able to detect that no data has been entered, and silently close the form. If data has been entered, I will prompt the user for whether to save the data, discard it or cancel (keeping the form open).

I created a method for UpdateEntity that did the following (simplified slightly for brevity):


protected override void UpdateEntity()
{
    _unitOfMeasure.Name = _editView.Name;
    _unitOfMeasure.Abbr = _editView.Abbr;
    _unitOfMeasure.RatioToPounds = _editView.RatioToPounds;
}

What I was immediately finding was that after the Name property is set, IsDirty = true. This ended up being because my TypeDefaultValue for System.String was String.Empty, and _unitOfMeasure.Name was set to null. Changing Name from null to String.Empty results in IsDirty = true... fair enough. So I changed my TypeDefaultValue for String to null. Name and Abbr can now be set in this manner keeping IsDirty = false;

But then setting RatioToPounds to the default value for Double (Double.MinValue) resulted in IsDirty = true. While debugging, I see that _unitOfMeasure.RatioToPounds equals Double.MinValue before I try to set it to Double.MinValue... so I'm not really changing the value, but IsDirty still gets set to true.

Behind the scenes in the generated code, GetCurrentFieldValue for RatioToPounds sees that the true value is null. My guess is that SetNewFieldValue is checking the GetCurrentFieldValue against the value to set the field to, and seeing that they are different, thus setting IsDirty = true. But really, when I set a property to its TypeDefaultValue, I am implying that it should be set to null. And if its value was previously null, I would expect IsDirty to remain false.

With the strings, the problem was averted by setting the TypeDefaultValue to null. Then the current value and the new value truly match. But, with a double (or any other value type), on a field that is non-nullable in the database, I have no means (that I am aware of) for setting the property to null, avoiding the IsDirty = true situation.

After figuring this all out, I ended up with the following code:


protected override void UpdateEntity()
{
    // Populate the entity's properties from the view
    string name = _editView.Name; // Returns null if empty
    string abbr = _editView.Abbr; // Returns null if empty
    double ratio = _editView.RatioToPounds; // Returns double.MinValue if empty

    if (_unitOfMeasure.UnitOfMeasureName != name)
        _unitOfMeasure.UnitOfMeasureName = name;

    if (_unitOfMeasure.UnitOfMeasureAbbr != abbr)
        _unitOfMeasure.UnitOfMeasureAbbr = abbr;

    if (_unitOfMeasure.RatioToPounds != ratio)
        _unitOfMeasure.RatioToPounds = ratio;
}

This just seems like a PITA and like there must be a better way. I expected my UpdateEntity method to simply have 1 line of code for each field on the view. And I actually expect this line to be a call to a common routine that understands pulling values off a view, handling empty/default values. But I can't find a way to make a generic method automatically do the code above in an efficient manner.

Any direction on this would be very much appreciated. Thanks in advance!

bclubb
User
Posts: 934
Joined: 12-Feb-2004
# Posted on: 27-Sep-2006 06:50:23   

setting a field indicates that it has a value that should be passed to the database. So even if it is set to null, you may be taking a field that had a value and setting it to null. This forces the field to be defined in the query. Other methods that you may be able to use when doing your check are

entity.TestOriginalFieldValueForNull(FieldIndex);
entity.TestCurrentFieldValueForNull(FieldIndex)
Posts: 18
Joined: 26-Jul-2006
# Posted on: 27-Sep-2006 07:11:31   

bclubb wrote:

setting a field indicates that it has a value that should be passed to the database. So even if it is set to null, you may be taking a field that had a value and setting it to null. This forces the field to be defined in the query.

So does that mean the following code would result in the Name property being included in the update statement? (Sorry for such a newbie question.)

_unitOfMeasure.Name = _unitOfMeasure.Name;
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39618
Joined: 17-Aug-2003
# Posted on: 27-Sep-2006 09:42:32   

Please state llblgen pro version and ormsupportclasses runtime lib build version (rightclick on ormsupportclasses dll in explorer -> properties -> version tab), as stated in the remark above the textbox when you started this thread simple_smile

This is important, as there were some behavioral changes in v2.0 (small, but noticable).

Frans Bouma | Lead developer LLBLGen Pro
Posts: 18
Joined: 26-Jul-2006
# Posted on: 27-Sep-2006 14:41:48   

Sorry Frans, I'm a tool.

SD.LLBLGen.Pro.ORMSupportClasses.NET20.dll: Version 2.0.0.0

LLBLGen Designer: Version 2.0.0.0

Microsoft Visual Studio 2005 Team Edition for Software Developers Version 8.0.50727.42 (RTM.050727-4200) Microsoft .NET Framework Version 2.0.50727

Edit: Oh, and I'm using the Adapter, 1 class template.

Walaa avatar
Walaa
Support Team
Posts: 14951
Joined: 21-Aug-2005
# Posted on: 27-Sep-2006 15:02:44   

Runtime lib build version: (rightclick on ormsupportclasses dll in explorer -> properties -> version tab -> File Version)

Posts: 18
Joined: 26-Jul-2006
# Posted on: 27-Sep-2006 15:24:28   

File Version: 2.0.0.060808

Sorry.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39618
Joined: 17-Aug-2003
# Posted on: 28-Sep-2006 11:37:20   

spider_from_ohio wrote:

I am wiring up my first real form using LLBLGen and I've run into a problem regarding the IsDirty flag, related to default values.

I have a UnitOfMeasureEntity, which has the following properties:

  • string Name
  • string Abbr
  • double RatioToPounds I have a form for editing/adding units of measure, which displays textboxes for these 3 fields. I'm using an MVP pattern in my WinForms 2.0 application. I'm using LLBLGen Pro 2.0 by the way.

Here's my scenario

  1. User clicks Add...
  2. This opens the edit/add form
  3. User then clicks Cancel, without entering any data

At this point, I want to be able to detect that no data has been entered, and silently close the form. If data has been entered, I will prompt the user for whether to save the data, discard it or cancel (keeping the form open).

I created a method for UpdateEntity that did the following (simplified slightly for brevity):


protected override void UpdateEntity()
{
    _unitOfMeasure.Name = _editView.Name;
    _unitOfMeasure.Abbr = _editView.Abbr;
    _unitOfMeasure.RatioToPounds = _editView.RatioToPounds;
}

What I was immediately finding was that after the Name property is set, IsDirty = true. This ended up being because my TypeDefaultValue for System.String was String.Empty, and _unitOfMeasure.Name was set to null. Changing Name from null to String.Empty results in IsDirty = true... fair enough. So I changed my TypeDefaultValue for String to null. Name and Abbr can now be set in this manner keeping IsDirty = false;

This is effectively killing the usage of TypeDefaultValue for string. The value of CurrentValue (the property of the entity field object containing the field's value) is by default null, for every field. TypeDefaultValue is used return a value when the field is null. So by default it returns a string.Empty when you READ the property of a field which is set to null.

If you don't want to set the fields to a value, don't set them to a value. If you want to initialize fields to a value, you set them to a value, but then also you have a dirty entity because you've set fields to a value. The focus should be on: WHY do you want to initialize fields to a value? Is that because it displays better? Or because hte user won't have to set them to a value in that case?

But then setting RatioToPounds to the default value for Double (Double.MinValue) resulted in IsDirty = true. While debugging, I see that _unitOfMeasure.RatioToPounds equals Double.MinValue before I try to set it to Double.MinValue... so I'm not really changing the value, but IsDirty still gets set to true.

This is because the value of the field is null, and as the field isn't a nullable type, and as it's a value type, null can't be returned, so the property HAS to return a double, and it then picks the default value as set in TypeDefaultValue. So as you inspect the property's value, it's not the real value in this initial state. This is caused by the fact that reading a property from a NEW and UNinitialized entity gives undefined values, because the entity is new and uninitialized... As the valuetype properties have to return something, they return the default value.

However the value you SET is always accepted when the entity is new and the field is not set to a value yet (which is the case in this situation).

Behind the scenes in the generated code, GetCurrentFieldValue for RatioToPounds sees that the true value is null. My guess is that SetNewFieldValue is checking the GetCurrentFieldValue against the value to set the field to, and seeing that they are different, thus setting IsDirty = true. But really, when I set a property to its TypeDefaultValue, I am implying that it should be set to null. And if its value was previously null, I would expect IsDirty to remain false.

No, that would be illogical, as you haven't set the field to a value, so if you want to set the field to the value which is also the default value, it wouldn't accept the value and thus would result in NULL in the db, instead of the value you want to set, which isn't what you want. This is the problem with value types which can't be null in .NET while the value CAN be null in the DB.

With the strings, the problem was averted by setting the TypeDefaultValue to null. Then the current value and the new value truly match. But, with a double (or any other value type), on a field that is non-nullable in the database, I have no means (that I am aware of) for setting the property to null, avoiding the IsDirty = true situation.

why do you want to avoid isdirty is true when you initialize the field to a value? If you don't want to have isdirty become true, don't set the field to a value! The property will return a proper value so no problem there. That's been taken care of in the property which represents the field, please check that in the generated code simple_smile

So I think you should just skip this init routine completely as it doesnt' add anything to the entity anyway.

Frans Bouma | Lead developer LLBLGen Pro
Posts: 18
Joined: 26-Jul-2006
# Posted on: 28-Sep-2006 15:59:22   

Frans,

Everything you said makes perfect sense, I'm just not sure it addresses my original scenario.

spider_from_ohio wrote:

Here's my scenario

  1. User clicks Add...
  2. This opens the edit/add form
  3. User then clicks Cancel, without entering any data

At this point, I want to be able to detect that no data has been entered, and silently close the form. If data has been entered, I will prompt the user for whether to save the data, discard it or cancel (keeping the form open).

I was expecting to be able to update the entity with the values from the form, and then check IsDirty. But since the fields are not initialized before this process, this won't work -- the entity is always dirty.

So I believe I need to do something like this:


protected override void UpdateEntity()
{
    // Populate the entity's properties from the view
    string name = _editView.Name; // Returns null if empty
    string abbr = _editView.Abbr; // Returns null if empty
    double ratio = _editView.RatioToPounds; // Returns double.MinValue if empty

    if (_unitOfMeasure.UnitOfMeasureName != name)
        _unitOfMeasure.UnitOfMeasureName = name;

    if (_unitOfMeasure.UnitOfMeasureAbbr != abbr)
        _unitOfMeasure.UnitOfMeasureAbbr = abbr;

    if (_unitOfMeasure.RatioToPounds != ratio)
        _unitOfMeasure.RatioToPounds = ratio;
}

Does this code match your recommendation?

Thanks, Jeff Handley

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39618
Joined: 17-Aug-2003
# Posted on: 29-Sep-2006 13:09:00   

spider_from_ohio wrote:

Frans,

Everything you said makes perfect sense, I'm just not sure it addresses my original scenario.

spider_from_ohio wrote:

Here's my scenario

  1. User clicks Add...
  2. This opens the edit/add form
  3. User then clicks Cancel, without entering any data

At this point, I want to be able to detect that no data has been entered, and silently close the form. If data has been entered, I will prompt the user for whether to save the data, discard it or cancel (keeping the form open).

I was expecting to be able to update the entity with the values from the form, and then check IsDirty. But since the fields are not initialized before this process, this won't work -- the entity is always dirty.

ah I see what you mean: the default is "", user doesn't change "", you set "" and the field is dirty.

Isn't this a gui problem? You could bind the entity to the form's controls, and the entity gets changed only when the user changes a value through a control. You can also bind to the event 'EntityContentsChanged' AFTER you've set the entity to default values.

You then get the values back, and the values which are indeed changing the default values you've set will raise the event EntityContentsChanged, so you know the entity has been changed by the user. Otherwise it's not.

Frans Bouma | Lead developer LLBLGen Pro