Author |
Share Topic Topic Search Topic Options
|
skingaby
DevForce MVP
Joined: 23-Apr-2008
Location: United States
Posts: 146
|
Post Options
Quote Reply
Topic: Entities marked as HasChanged when a property hasn't really changed Posted: 03-Aug-2009 at 8:14am |
Yay!!
|
|
kimj
IdeaBlade
Joined: 09-May-2007
Posts: 1391
|
Post Options
Quote Reply
Posted: 01-Aug-2009 at 8:48am |
You'll see this in the September release :)
|
|
skingaby
DevForce MVP
Joined: 23-Apr-2008
Location: United States
Posts: 146
|
Post Options
Quote Reply
Posted: 01-Aug-2009 at 8:20am |
Thanks Ward. You are a wise man and I hope to see this new feature in an upcoming release, for now, I will cross my fingers on the interceptor.
|
|
WardBell
IdeaBlade
Joined: 31-Mar-2009
Location: Emeryville, CA,
Posts: 338
|
Post Options
Quote Reply
Posted: 31-Jul-2009 at 1:38pm |
I FEEL you. It's driving me crazy too. The Silverlight DataForm automatically resets every bound property to itself when in edit mode ... dirtying every object it sees. I have gotten nowhere arguing with MS against this practice; I follow their reasoning and understand why it was economical for them. They actually expect bound objects to ignore a reset-to-same-value. Today, we do not.
Kim is correct about the internal debate ... which continues and has received new life from your stimulating example. No promises from me today. There are some use cases we have to feel good about first; we didn't arrive at the present behavior casually. But that behavior is being re-reviewed and we are all receptive to your argument. Stay tuned.
p.s.: yes, you wrote an interceptor that should work 99+% of the time; if you find an exception, it will be rare and you can insert a special purpose interceptor for that one.
|
|
skingaby
DevForce MVP
Joined: 23-Apr-2008
Location: United States
Posts: 146
|
Post Options
Quote Reply
Posted: 31-Jul-2009 at 11:41am |
P.S. Another reason to NOT set properties when the value hasn't changed is that some property changes are a trigger for other behaviors, for example, recalcs, refreshes, redraws, etc. If the property hasn't really changed, then there is no need to do any of those re-actions, some of which may be time-consuming or chatty.
For example, if you have an Order with 100 Order details. If the RecalcOrderTotals method iterates through all line items and calculates the extended price, then sums the extended price, factors in the the discount, taxes and shipping (applying the shipping price break logic) and then sets the OrderTotal, SalesTaxTotal, and ShippingTotal properties. That seems like an awful lot of overhead because a user just changed a line item quantity from 1 to 1.
|
|
skingaby
DevForce MVP
Joined: 23-Apr-2008
Location: United States
Posts: 146
|
Post Options
Quote Reply
Posted: 31-Jul-2009 at 11:33am |
So I added a method to the BaseEntity class. We have created this class and used it in the Object Mapper as our Base Entity Class:
public class BaseEntity : IdeaBlade.EntityModel.Entity |
The new method looks like this. It would seem to be rather crude though, and I imagine I will run into some types it can't parse, but I'll cross that bridge... Anyway, does this seem reasonable?
[BeforeSet(Order=-1.0)]
public void BeforeSettingAnyProperty(IPropertyInterceptorArgs args)
{
var dataArgs = args as IDataEntityPropertyInterceptorArgs;
if (dataArgs != null)
{
var oldValue = dataArgs.DataEntityProperty.GetValue(dataArgs.Instance, EntityVersion.Current);
var newValue = dataArgs.Value;
if (oldValue == null && newValue == null)
//both are null, nothings changed, cancel the update
dataArgs.Cancel = true;
else if (oldValue == null && newValue != null || oldValue != null && newValue == null)
//one is null, don't cancel
dataArgs.Cancel = false;
else if (oldValue.Equals(newValue))
//they're equal, nothings changed, cancel the update
dataArgs.Cancel = true;
}
} |
After implementing this, the Unit test above passes now.
|
|
skingaby
DevForce MVP
Joined: 23-Apr-2008
Location: United States
Posts: 146
|
Post Options
Quote Reply
Posted: 31-Jul-2009 at 10:28am |
GROOOANN!!!!
Cast my vote firmly against making an object dirty when a property hasn't really changed!
Yeuch.
And the workaround is going to be an AWFUL lot of tedious code.
Here's our real world problem:
1) Create a Silverlight grid with some date columns bound to Entities.
2) Tie functionality to the "Dirty" state of the row, perhaps enable/disable the row Save button.
3) Make single click edit functionality work in the cells. (I.e. skip the "click to select, then click again to edit" by dropping into edit mode on the cell when it gets focus.
4) Now, run the app. Click on the date field in several rows. Because of the silverlight binding behavior, as you click through the rows, with single-click edit, the field drops into and out of edit mode and the entity property gets read and set. Now, every row you touched is dirty, even though you haven't changed any thing. Inexplicably, all the save buttons light up. 555-1212 >> "Helpdesk? Yes, I didn't change anything but it wants me to save."
Maybe you can build this in and make a toggle so that developers can select to enable/disable the check with a selection in the Object Mapper.
Here's the code that I was generating in our custom datalayer before we switched to IdeaBlade:
<System.Runtime.Serialization.DataMemberAttribute()> _
Public Overridable Property DateOfDeal() As Date Implements IDeal.DateOfDeal
Get
Return Me._dateOfDeal
End Get
Set
If (Me._dateOfDeal <> value) Then
Me._dateOfDeal = value
Me.IsDirty = true
End If
End Set
End Property
<System.Runtime.Serialization.DataMemberAttribute()> _
Public Overridable Property DealNumber() As System.Nullable(Of Long) Implements IDeal.DealNumber
Get
Return Me._dealNumber
End Get
Set
If ((value.HasValue = false) _
AndAlso (Me._dealNumber.HasValue = true)) Then
Me._dealNumber = Nothing
Me.IsDirty = true
Else
If ((value.HasValue = true) _
AndAlso (Me._dealNumber.HasValue = false)) Then
Me._dealNumber = value
Me.IsDirty = true
Else
If (((value.HasValue = true) _
AndAlso (Me._dealNumber.HasValue = true)) _
AndAlso (value.Value <> Me._dealNumber.Value)) Then
Me._dealNumber = value
Me.IsDirty = true
End If
End If
End If
End Set
End Property |
|
|
kimj
IdeaBlade
Joined: 09-May-2007
Posts: 1391
|
Post Options
Quote Reply
Posted: 31-Jul-2009 at 9:26am |
We've had an internal debate about whether property setters on the Entity should first check if the incoming value is equal to the current value. So far, the winning side says that we do not make this check. This means that even if the new value is the same as the old, we still mark the entity as dirty. This behavior may change some day, especially if we get a lot of complaints about it.
One workaround, although I haven't tried it, might be to add an entity/property wide BeforeSet property interceptor which makes this check and cancels further setter actions if needed.
|
|
skingaby
DevForce MVP
Joined: 23-Apr-2008
Location: United States
Posts: 146
|
Post Options
Quote Reply
Posted: 31-Jul-2009 at 7:17am |
When I execute this test:
[TestMethod]
public void TestEntityChangedProperties()
{
Deal deal = Deal.Create();
deal.FlowDateStart = new DateTime(2009, 4, 1);
IdeaBlade.EntityModel.SaveResult result = deal.EntityAspect.EntityManager.SaveChanges();
Assert.IsTrue(result.Ok);
Assert.IsFalse(deal.EntityAspect.HasChanges());
//now, change the property to itself and see if HasChanges has changed
deal.FlowDateStart = new DateTime(2009, 4, 1);
Assert.IsFalse(deal.EntityAspect.HasChanges()); //FAILS
Assert.AreEqual(IdeaBlade.EntityModel.EntityState.Unchanged, deal.EntityAspect.EntityState);
} |
The test fails. Why? If I am setting the date to 4/1 and then after the save, changing it back to 4/1, shouldn't the property setter not bother to set the value and not mark the entity as dirty?
Edited by skingaby - 31-Jul-2009 at 7:18am
|
|