Yeah, I totally understand how this can be useful. I am trying to analyze the
situation and just like with most things in Cayenne core it can get pretty
deep. Some factors to consider:
1. Performance (I mentioned about that before).
2. "Circular changes" canceling modifications:
assertEquals("A", o.getP());
o.setP("B"); // modified
o.setP("A"); // modified? (property change is "canceled", but maybe other
properties are also modified)
3. "Uncommittable changes". An example - objects with modified to-many
relationships are treated as modified, but during commit their DB records are
not updated.
So I am leaning towards either a method like
ObjectContext.hasCommittabledChanges() that would do deeper analysis of the
object graph, or maybe a more involved changeset API that I started to draft
under cayenne-lifecycle, that would among other things allow the caller to
obtain a more nuanced and detailed view of the graph changes (overall changes,
changes per object, committable/uncommitable changes etc.)
And there's of course we shouldn't forget ROP....
Andrus
On Mar 11, 2011, at 11:14 PM, Michael Gentry wrote:
> My particular use case in my Tapestry application is there are many
> different pages the user can edit before deciding to save. I wanted
> to display an "Unsaved Changes" message as they navigate around from
> page to page (combination of JavaScript for the current page and
> dataContext.hasChanges() when they move to a new page). However,
> Tapestry is always calling the setters when the form is submitted
> (page navigation, but not commitChanges()) and this is causing my
> "Unsaved Changes" message to always appear, even when they don't
> actually change values, because Tapestry has called all the setters
> with the original value.
>
> mrg
>
>
> On Fri, Mar 11, 2011 at 3:54 PM, Andrus Adamchik <[email protected]>
> wrote:
>> I'd rather we don't introduce a potentially expensive comparison in the
>> superclass setters. A setter may be called many times before a commit, so
>> the assumption is that it is much cheaper to only do comparisons once during
>> commit.
>>
>> I guess you can generate special setters with a custom template. Or we may
>> run pre-commit logic from within 'hasChanges'...
>>
>> Andrus
>>
>>
>>
>> On Mar 11, 2011, at 10:22 PM, Michael Gentry wrote:
>>> I just did a quick test using:
>>>
>>> DataContext dataContext = DataContext.createDataContext();
>>>
>>> User user = dataContext.newObject(User.class);
>>>
>>> user.setFirstName("System");
>>> user.setLastName("Administrator");
>>> user.setUsername("admin");
>>>
>>> dataContext.commitChanges();
>>>
>>> user.setFirstName("System"); // This isn't a real change
>>>
>>> System.out.println(dataContext.hasChanges());
>>>
>>> dataContext.commitChanges();
>>>
>>> In a nutshell, create a user, commit it, set a value to the same
>>> value, then check hasChanges(). The output is:
>>>
>>>
>>> INFO: INSERT INTO Users (first_name, id, last_name, password,
>>> username) VALUES (?, ?, ?, ?, ?)
>>> INFO: [bind: 1->first_name:'System', 2->id:200,
>>> 3->last_name:'Administrator', 4->password:NULL, 5->username:'admin']
>>> true
>>>
>>> The "true" is that the dataContext has changes, even though the second
>>> commitChanges() doesn't do anything (there are no real changes).
>>> Should we modify CayenneDataObject's writeProperty() to check if the
>>> old value and new value are equal before calling propertyChanged()? I
>>> was also noticing Embeddables doing something similar.
>>>
>>> Thanks,
>>>
>>> mrg
>>>
>>
>>
>