Why IsDirty = false?

Posts   
 
    
kievBug
User
Posts: 105
Joined: 09-Jan-2009
# Posted on: 05-Nov-2010 21:41:01   

Hey guys,

I've a small problem with IsDirty and checking is entity changed or not in these scenario:


   var instance = Entity Loaded From DB;

   //here IsDirty = false - everything is fine, changing the field ...

    instance.SomeField = new AnotherEntity();

   // I thought that here it should be IsDirty = true, but it is not - IsDirty = false

As I understand the problem is that PK field in not set for AnotherEntity and that is why instance is not dirty.

If I change the code to be something like below, instanse will become dirty.


   var instance = Entity Loaded From DB;

   //here IsDirty = false - everything is fine, changing the field ...

    instance.SomeField = new AnotherEntity(PK_VALUE);

   // IsDirty = true

Can somebody explain why instance is not dirty in the first scenario? Am I missing something? It seems like it is a bug?

Thanks, Anton

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 06-Nov-2010 03:51:50   

kievBug wrote:


   var instance = Entity Loaded From DB;

   //here IsDirty = false - everything is fine, changing the field ...

    instance.SomeField = new AnotherEntity();

   // I thought that here it should be IsDirty = true, but it is not - IsDirty = false

As I understand the problem is that PK field in not set for AnotherEntity and that is why instance is not dirty.

Looks like an fk field isn't synced directly, right? This is logical, as new AnotherEntity(); is likely an entity with an identity PK or a new entity without a PK, so it's synced after the save of the new AnotherEntity. This makes instance.SomeField not dirty at assignment, but it IS saved. instance.SomeField isn't reset to a new entity, the Fk field is just updated.

Your workaround works because either the entity with that PK exists, or it's new, but the PK is already assigned. So it's expected behavior.

David Elizondo | LLBLGen Support Team
kievBug
User
Posts: 105
Joined: 09-Jan-2009
# Posted on: 08-Nov-2010 06:00:14   

Well, as for me behavior by design would be IsDirty = true when field of the entity is changed. And it should not be matter that the field will be set after related entity is saved and re-fetched from DB to get PK value.

As you can see the difference between the two scenarios is that in the second I've set PK value for related entity. So this means that if I want IsDirty to work correctly, I should assign pk to new entities.

But why should I as a developer care about setting PK for new entity? Llblgen should care about it, and I just need to set/assign an object to the property. What if I use SQL Server IDENTITY? As I understand it is not possible to set the pk for the new entity at all?! So I'll just set property equal to new object and that's it. But after this object won't be dirty?!?

Not sure that this behavior is by design.

I'd say it is a bug and it should be fixed. It shouldn't be matter whether I set PK for the new entity or not, IsDirty should work the same!

Thanks, Anton

Walaa avatar
Walaa
Support Team
Posts: 14950
Joined: 21-Aug-2005
# Posted on: 08-Nov-2010 10:09:00   

Are you using SelfServicing?

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 08-Nov-2010 14:03:35   

Also, v3 or v2.6?

Frans Bouma | Lead developer LLBLGen Pro
kievBug
User
Posts: 105
Joined: 09-Jan-2009
# Posted on: 08-Nov-2010 15:08:57   

I use 2.6 and Adapter templates

kievBug
User
Posts: 105
Joined: 09-Jan-2009
# Posted on: 08-Nov-2010 15:11:41   

Also one more thing the field SomeFieldId which is a base field(holds the id of related entity) for SomeField is a non-nullable field.

I will check the behavior when it is nullable, but it seems to me that it doesn't matter.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 09-Nov-2010 13:59:49   
instance.SomeField = new AnotherEntity();
// I thought that here it should be IsDirty = true, but it is not - IsDirty = false

'IsDirty' is a flag for fields in 'instance' here, and not about whether it refers to related entities. So when you do:

instance.SomeField = new AnotherEntity("SOMEVALUE");

the pk field in the AnotherEntity object is set to a value. This is then synced with the FK field in 'instance' and THIS makes 'instance' 'dirty', because a field in 'instance' was changed (namely from 'null' to "SOMEVALUE").

If you don't set the pk field in new AnotherEntity(), to what should the FK field be synced to? 'null' ? That's not its value, the pk field in anotherentity hasn't been set. That's a clear difference: being set to a value != not set yet.

Hence the behavior you're seeing.

Another reason this is done this way is that in:

instance.SomeField = new AnotherEntity();

nothing has changed in 'instance', which means nothing has to be saved. Would it be marked as 'dirty', it would cause a save, which is odd, considering nothing has changed, yet.

If you do:

instance.SomeField = new AnotherEntity();
instance.SomeField.PkField="SOMEVALUE";

this will cause 'SomeField' to be 'dirty' and a saveable entity and this makes 'instance' dirty for the save logic as well, even though its IsDirty flag hasn't been set: it has a 'pending Fk sync', which means it will be saved.

This is true for identity pk fields as well: a pending sync is stored but isdirty isn't flagged.

The main reason is that if you don't save recursively, the FK field won't get a value, so it's premature to set 'IsDirty' to true in this case: if it's only the fk side you're saving, and not recursively and only the pending fk sync should cause the entity to become dirty and the rest is not dirty, nothing is done.

This is all to prevent phantom inserts, i.e. entity class instances which contain no real entity (data) however have flags set as if they do (isdirty is true).

So to make things predictable: - assign entities to each other - set values to fields you want to have values - save recursively.

things will be taken care of for you.

Frans Bouma | Lead developer LLBLGen Pro
kievBug
User
Posts: 105
Joined: 09-Jan-2009
# Posted on: 10-Nov-2010 22:50:56   

Otis wrote:

instance.SomeField = new AnotherEntity();
// I thought that here it should be IsDirty = true, but it is not - IsDirty = false

'IsDirty' is a flag for fields in 'instance' here, and not about whether it refers to related entities.

Yeap, I understand this.

Otis wrote:

So when you do:

instance.SomeField = new AnotherEntity("SOMEVALUE");

the pk field in the AnotherEntity object is set to a value. This is then synced with the FK field in 'instance' and THIS makes 'instance' 'dirty', because a field in 'instance' was changed (namely from 'null' to "SOMEVALUE").

If you don't set the pk field in new AnotherEntity(), to what should the FK field be synced to? 'null' ? That's not its value, the pk field in anotherentity hasn't been set. That's a clear difference: being set to a value != not set yet.

Well, as for me there is no difference here, why? Because if I pass it for saving, in both cases it will save both entities.

So this means that by doing just this

instance.SomeField = new AnotherEntity();

the 'instance' somehow becomes dirty, otherwise why it was saved?! It becomes dirty, only after the AnotherEntity is saved and FK is refetched from DB or something like that and is synced back to instance.

But it will become dirty in any case whether there is an PK set for related or not, so why not to mark it as Dirty when I set this property? As for me it looks like a valid behavior.

Otis wrote:

Another reason this is done this way is that in:

instance.SomeField = new AnotherEntity();

nothing has changed in 'instance', which means nothing has to be saved. Would it be marked as 'dirty', it would cause a save, which is odd, considering nothing has changed, yet.

Nothing is changed? Why? I assigned a new object to the property that means that I want to create record in DB(may be I want all fields be default, or whatever). Plus, may be I'm wrong, but it seems to me that AnotherEntity that I've created would be save to the DB, even if I didn't set any fields. But that's not my case, I set fields for related entity. Let's say I did this

instance.SomeField = new AnotherEntity() {MyField = 1};

after that it is the same problem, instance is not dirty but it will be saved.

Otis wrote:

This is true for identity pk fields as well: a pending sync is stored but isdirty isn't flagged.

Yeap and that is causing problems as for me - it should be marked as Dirty.

Otis wrote:

The main reason is that if you don't save recursively

The default behavior of LLBLGEN is do recursive save, though. If user is that smart enough to override the default behavior, than probably he should do something in this case. Why in my case which falls under the default behavior I should do some additional things to keep it working correctly.

Plus another thing is that there is IsChanged flag on the field, which can be checked before saving/generating query, etc.

I guess the question here is not about how to fix it, but is it a bug or not. And as for me it is a bug.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 11-Nov-2010 10:55:52   

[Test]
public void PhantomSaveTest()
{
    using(DataAccessAdapter adapter = new DataAccessAdapter())
    {
        CustomerEntity c = new CustomerEntity();
        c.VisitingAddress = new AddressEntity();
        Assert.IsFalse(c.IsDirty);
        Assert.IsFalse(c.VisitingAddress.IsDirty);
        Assert.IsTrue(adapter.SaveEntity(c, false, true));
    }
}

Succeeds, nothing is saved. Address has an identity pk, but also non-nullable fields. No query was generated as none of the entities is dirty, which means 'none of the entities was changed'. This is the desired behavior, as there might be new entities created in the graph along the way and which then will result in phantom inserts if you save the whole graph recursively.

A bug is behavior which isn't according to the spec, i.e. it does something differently than it should. IsDirty is a flag for the framework to signal that a field has changed. A related entity doesn't belong to the entity like in the above example, the address entity doesn't belong to the customer entity. No fields were saved, no IsDirty flag set, nothing to be done.

This get different when I set a field in Address:


[Test]
public void PhantomSaveTest()
{
    using(DataAccessAdapter adapter = new DataAccessAdapter())
    {
        CustomerEntity c = new CustomerEntity();
        c.VisitingAddress = new AddressEntity();
        c.VisitingAddress.StreetName = "Some Road";
        Assert.IsFalse(c.IsDirty);
        Assert.IsTrue(c.VisitingAddress.IsDirty);
        Assert.IsTrue(adapter.SaveEntity(c, false, true));
    }
}

IsDirty on customer is still false. Dirty is set on address, as a field in address has changed. This test fails, as the insert in address fails, because I forgot to set other non-nullable fields. The point however is that the 'IsDirty' flag is strictly about which fields are changed, i.e. for change tracking for the save logic.

You see this differently, which is fine, but this is the way it is defined and works. The point of view where the related entity belongs to the containing entity is actually not going to work, hence why we don't do that: - in the above example, 'Address' can be assigned to multiple Customer instances. Which one owns the Address entity now? All of them? unclear - if I pass 'false' to the last parameter of SaveEntity, the second test passes, as it doesn't save Address, so for the persisting framework 'address' doesn't exist. This means that it only sees 'customer', which is a new, undefined entity without any data (i.e. the entity instance is the data, not the class instance!), so in other words, it makes no sense to save the data as there IS no data. Nothing to save. - if I have a deep graph, e.g. address is assigned to another customer, which references other entities, and deep in that graph I change a field. What should the 'isdirty' flag be in the customer instance 'c' above? true? That's odd, considering nothing in that entity has changed nor in its direct related entities. But if it's 'true', it should be persisted to the DB, otherwise the framework can't know whether a field has been changed or not, and it eats up performance for useless work we could have avoided. Logically it makes no sense to look at it this way.

You might have logic in your application relying on 'isdirty' to enable/disable a save button, but that's something you should step away from. Things are more complex than that: In the last example, say I have a customer, order and orderdetails in a form, and I change a value in an orderdetail entity. Which flag are you checking for your save button state? IsDirty on Customer? that's not going to change because of this change.

In short: you shouldn't worry about this at all. Simply work with the objects, things are taken care of for you. That's why you use a framework to do the work for you.

btw, recursive saves are the default for adapter, but selfservicing has non-recursive saves as default (as they were added later on). Pending fk syncs aren't part of the entity, and therefore won't be the reason something IS dirty, as it didn't happen YET.

I hope I have explained it enough.

Frans Bouma | Lead developer LLBLGen Pro