ForceCurrentValueWrite not working as expected in 4.0

Posts   
 
    
Posts: 98
Joined: 10-Nov-2006
# Posted on: 26-Apr-2013 01:44:03   

Using LLBLGen 4.0, Self Servicing, runtime version 4.0.13.0417

If I run this code with LLBLGen 3.5 vs 4.0, I get different results:

var e = new DataClasses.EntityClasses.ItemEntity();
int itemIdField = e.Fields["ItemId"].FieldIndex;
e.Fields[itemIdField].ForcedCurrentValueWrite(333, 3333);
e.Fields[itemIdField].CurrentValue.Dump();
e.Fields[itemIdField].DbValue.Dump();

On LLBLGen 3.5, the e.Fields[itemIdField].CurrentValue returns 333, and the e.Fields[itemIdField].DbValue returns 3333. This is what I expect.

On LLBLGen 4.0, the e.Fields[itemIdField].CurrentValue returns 333, and the e.Fields[itemIdField].DbValue returns null.

Is ForcedCurrentValueWrite not working correctly on 4.0?

Walaa avatar
Walaa
Support Team
Posts: 14950
Joined: 21-Aug-2005
# Posted on: 26-Apr-2013 11:01:12   

In v.4.0, if the entity has not been fetched, DBValue is always null.

It comes down to the following piece of code:

public object GetDbValue(int index)
{
    if(index < 0)
    {
        return null;
    }
    // _dbValues isn't always there. It's only there if data is fetched and a field has been changed. If nothing has changed
    // and the entity isn't new, read the current value (as that's the db value). If the entity is new, nothing is fetched, so always null.
    // if the entity isn't new, and the field hasn't been changed, the value is in currentvalues. 
    if(!this.DbValuesAreValid)
    {
        return null;
    }
    if(_changedFlags[index])
    {
        // field was changed, entity isn't new, dbvalues is there, normally. It can be the user manipulated IsNew and IsChanged
        // which means _dbValues might not be here (first IsChanged, then IsNew). In that case, we return the currentvalue,
        // as that's the value read from the DB. 
        return _dbValues == null ? _currentValues[index] : _dbValues[index];
    }
    return _currentValues[index];
}

The second if condition, is the one returning the null in your case.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 26-Apr-2013 11:36:29   

Hmm... still, could be seen as a breaking change / bug. We'll see if we can make this work. The main issue is that the 'state' of the entityfields object is still 'new', so the code doesn't check the dbvalue. There's no dbvalue by default. Will think about this.

Frans Bouma | Lead developer LLBLGen Pro
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 26-Apr-2013 14:38:10   

Your code misses a couple of important aspects: The field isn't marked as changed, while the values of currentValue and dbValue differ: you previously might not have run into this, but in v4.0 this is more prominent: if dbvalue and currentvalue differ, it has to mean the field is changed, and the field has to be marked as such. Also, the entity state has to be 'fetched', otherwise dbvalue is not defined.

If you do, the DbValue will then be returned properly. In your code in the topicstart, the field isn't marked as changed, and as you ask for the dbvalue, the code simply returns null, as dbvalue isn't known: if the entity was fetched, and the field was not changed, currentvalue holds the db value so that value is returned. Your entity is new, so it will return null.

So in short: prior to reading the DbValue value written with ForcedCurrentValueWrite: - the entity's state has to be: Fetched. If not, null is returned (as it's not known) - additionally, if the state is Fetched, the field's changed flag has to be set. If not, currentvalue's value is returned (as that's the value read from the db)

Frans Bouma | Lead developer LLBLGen Pro
Posts: 98
Joined: 10-Nov-2006
# Posted on: 26-Apr-2013 17:57:24   

Frans,

Thanks for the information. I can confirm that the following makes things work as expected:

var e = new DataClasses.EntityClasses.ItemEntity();
int itemIdField = e.Fields["ItemId"].FieldIndex;
e.Fields.MarkAsFetched();
e.Fields[itemIdField].IsChanged = true;
e.Fields[itemIdField].ForcedCurrentValueWrite(333, 3333);
e.Fields[itemIdField].CurrentValue.Dump();
e.Fields[itemIdField].DbValue.Dump();

I found that the IsChanged must be set _before _the ForcedCurrentValueWrite, otherwise the current value is returned for the DbValue.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 26-Apr-2013 18:41:41   

I found that the IsChanged must be set before the ForcedCurrentValueWrite, otherwise the current value is returned for the DbValue.

Hmm. Are you sure? The flag isn't checked when writing the value, only when reading it. ForcedCurrentValueWrite calls ForcedValueWrite on the VolatileEntityFieldsDataContainer. This method simply calls SetDbValue and after that writes the currentvalue. SetDbValue simply writes the value for the index as a dbvalue, and doesn't check a changed flag.

Frans Bouma | Lead developer LLBLGen Pro
Posts: 98
Joined: 10-Nov-2006
# Posted on: 26-Apr-2013 19:01:47   

I can't say why exactly, but my LinqPad tests definitely show that the order matters. In Test1, IsChanged comes before ForcedCurrentValueWrite, and DbValue is not correct. In Test2, the order is switched and DbValue is correct. Tests 3 and 4 are pretty much the same, but using the new methods on Fields, and they also show the same pattern.


DataClasses.EntityClasses.ItemEntity e;
int itemIdField;

"Test 1:".Dump();
e = new DataClasses.EntityClasses.ItemEntity();
itemIdField = e.Fields["ItemId"].FieldIndex;
e.Fields.MarkAsFetched();
e.Fields[itemIdField].IsChanged = true;
e.Fields[itemIdField].ForcedCurrentValueWrite(333, 3333);
e.Fields[itemIdField].CurrentValue.Dump();
e.Fields[itemIdField].DbValue.Dump();

"Test 2:".Dump();
e = new DataClasses.EntityClasses.ItemEntity();
itemIdField = e.Fields["ItemId"].FieldIndex;
e.Fields.MarkAsFetched();
e.Fields[itemIdField].ForcedCurrentValueWrite(333, 3333);
e.Fields[itemIdField].IsChanged = true;
e.Fields[itemIdField].CurrentValue.Dump();
e.Fields[itemIdField].DbValue.Dump();

"Test 3:".Dump();
e = new DataClasses.EntityClasses.ItemEntity();
itemIdField = e.Fields["ItemId"].FieldIndex;
e.Fields.MarkAsFetched();
e.Fields.SetIsChanged(itemIdField, true);
e.Fields.ForcedValueWrite(itemIdField, 333, 3333);
e.Fields.GetCurrentValue(itemIdField).Dump();
e.Fields.GetDbValue(itemIdField).Dump();

"Test 4:".Dump();
e = new DataClasses.EntityClasses.ItemEntity();
itemIdField = e.Fields["ItemId"].FieldIndex;
e.Fields.MarkAsFetched();
e.Fields.ForcedValueWrite(itemIdField, 333, 3333);
e.Fields.SetIsChanged(itemIdField, true);
e.Fields.GetCurrentValue(itemIdField).Dump();
e.Fields.GetDbValue(itemIdField).Dump();

Here's the output:

Test 1:
333
3333

Test 2:
333
333

Test 3:
333
3333

Test 4:
333
333
daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 27-Apr-2013 08:53:48   

I reproduced it on the latest v4: the order matters.

[TestMethod]
public void ForceDBValue1()
{           
    var customer = new CustomerEntity();
    var index = (int)CustomerFieldIndex.Country;

    customer.Fields.ForcedValueWrite(index, "GTM", "CRC");
    Assert.AreEqual("CRC", customer.Fields.GetDbValue(index));          
}

[TestMethod]
public void ForceDBValue2()
{
    var customer = new CustomerEntity();
    var index = (int)CustomerFieldIndex.Country;

    customer.Fields.MarkAsFetched();            
    customer.Fields.ForcedValueWrite(index, "GTM", "CRC");
    customer.Fields.SetIsChanged(index, true);
    Assert.AreEqual("CRC", customer.Fields.GetDbValue(index));
}

[TestMethod]
public void ForceDBValue3()
{
    var customer = new CustomerEntity();
    var index = (int)CustomerFieldIndex.Country;

    customer.Fields.MarkAsFetched();
    customer.Fields.SetIsChanged(index, true);
    customer.Fields.ForcedValueWrite(index, "GTM", "CRC");          
    Assert.AreEqual("CRC", customer.Fields.GetDbValue(index));
}

Only the last test passes. The second one should, but I think it doesn't because of this (**VolatileEntityFieldsDataContainer.cs:218**):

public void SetIsChanged(int index, bool value)
{
    if(value && this.DbValuesAreValid && !_changedFlags[index])
    {
        // value has changed, make sure the db value, which is in current value (as we keep 1 value from the db around), is copied
        // over to dbvalues this time. 
        SetDbValue(index, _currentValues[index]);
    }
    _changedFlags[index] = value;
}

We will look into this (whether this is expected, for some reason).

David Elizondo | LLBLGen Support Team
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 29-Apr-2013 12:32:46   

There are a series of issues with the current code, all related to the fact the 'db value from the DB' is stored in current value (to avoid having a copy of it in a 'db value' array) but due to manipulation of the flags like IsChanged and usage of ForcedValueWrite, the logic we have in place now might run into a state it can't determine whether the value in CurrentValue is actually the db value or not, compared to the v3.5 code (which had 2 values: dbValue and currentValue, so manipulating currentValue never manipulated dbValue).

We're investigating them now to see how we can make the new code behave like the v3.5 one and still not have duplicated values in memory if it's not necessary.

Frans Bouma | Lead developer LLBLGen Pro
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 29-Apr-2013 13:41:38   

Fixed a couple of issues with manipulating fields, setting db values etc. Situation one, where the entity is new and ForcedValueWrite is used will keep returning null for DbValue, as the entity is new. Situations 2 and 3 will succeed now, so the order with respect to SetIsChanged is no longer important. We did have to introduce a bitvector array for flags regarding db values, which increases the memory consumption per entity with 8 bytes at least if a field has been changed.

Fixed in next build.

Frans Bouma | Lead developer LLBLGen Pro