Updating ReadOnly Field with ForcedCurrentValueWrite

Posts   
 
    
coco
User
Posts: 8
Joined: 28-Dec-2005
# Posted on: 28-Dec-2005 00:46:28   

EntityField2.ForcedCurrentValueWrite.ForcedCurrentValueWrite(object value, object dbValue);

The documentation says :

Used by internal code only. Do not call this from your code.

This is obvious why you wouldn't ever use this method from your own code, but I have a certain instance where I want to do this.

I am using a database column named "RowVersion" as a timestamp to implement optimistic concurrency. The concurrency predicate checks if "RowVersion" is still the same.

Now, if a concurrency exception is thrown. I want to take get a new instance of that entity and basically merge the data into the entity that I had just modified (In the ORMConcurrencyException.EntityWhichFailed). Then I would just return newly merged data back to the user so that they will be notified of the action that has taken place so that they can review the data and attempt to save again.

My problem is that the "RowVersion" field is a ReadOnly byte field so therefore technically, I am not 'supposed' to update this. Using the newly fetched database entity isn't really an option because this entity may be a node in an 'entity tree' and it is far too much trouble to try to merge this entity back into it's position into the 'tree'. I used the ForcedCurrentValueWrite method to achieve the results I was looking for, but I am afraid that LLBLGen may not provide this method in a future release. I guess what I'm really looking for is reassurance that this approach that I have taken is acceptable in the way that I am using it and that the method will remain public in the future.

I've considered an alternative to my "RowVersion" approach using predicates to check if the dbvalue is still equal to the database value, but it just seems far too time consuming to code all of this. If someone else has come up with a more efficient way to merge data when a concurrency exception occurs; I'd be willing to hear how you've implemented this.

Walaa avatar
Walaa
Support Team
Posts: 14986
Joined: 21-Aug-2005
# Posted on: 28-Dec-2005 06:16:15   

Please check this thread: http://www.llblgen.com/tinyforum/Messages.aspx?ThreadID=4629

And the following thread explains something about dbValue & Current value in the context of Optimistic Concurrency check: http://www.llblgen.com/tinyforum/Messages.aspx?ThreadID=1923

And again, in the following threads Frans suggested the use of ForcedCurrentValueWrite(): http://www.llblgen.com/tinyforum/Messages.aspx?ThreadID=3071 http://www.llblgen.com/tinyforum/Messages.aspx?ThreadID=454

Good Luck

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39766
Joined: 17-Aug-2003
# Posted on: 28-Dec-2005 09:07:35   

In the docs it's stated to avoid that method, that's there to discourage people to use that method as it bypasses change tracking and therefore the field won't be marked as changed. The method is only needed in certain situations with identity fields.

Frans Bouma | Lead developer LLBLGen Pro
coco
User
Posts: 8
Joined: 28-Dec-2005
# Posted on: 28-Dec-2005 17:48:06   

Great! I was leary to use the method in case it would someday be removed from public access.

By the way, here is the way I 'merged' my entity when the concurrency error is caught:


        public bool Merge(IEntity2 entityWhichFailed)
        {
                       //entityWhichFailed will come from                   
                       //ORMConcurrencyException.EntityWhichFailed

            //make a copy of the entity so that you can re-fetch into it.

            IEntity2 dbEntity = EntityHelper.CopyEntity(entityWhichFailed, false);
                
            DataHelper.GetAdapter().FetchEntity(dbEntity);

            //Loop through each field in the dirty entity.
            for(int i = 0; i < entityWhichFailed.Fields.Count - 1; i++)
            {
                IEntityField2 field = entityWhichFailed.Fields[i];
                IEntityField2 dbField = dbEntity.Fields[field.Name];

                if((field.DbValue == null && dbField.DbValue != null) || 
                    (field.DbValue != null && !field.DbValue.Equals(dbField.DbValue)))
                {
                    //field is different from what is in database

                    if(field.IsChanged)
                    {
                        //current user changed data an error must be created
                        //to notify user that their changes were overwritten.

                        ConcurrencyErrors.Add(new ConcurrencyConflictError());
                        entityWhichFailed.SetNewFieldValue(field.FieldIndex,  
                        dbField.CurrentValue);
                    }
                    else if(field.Name == "RowVersion" && field.DataType == 
                        typeof(System.Byte[]))
                    {
                        //The rowversion will always be different.
                        //we want to copy the latest rowversion into our entity
                        //to make our entity 'current'.

                        entityWhichFailed.Fields[i].ForcedCurrentValueWrite( 
                        dbField.CurrentValue, dbField.DbValue);
                    }
                    else
                    {
                        //this means that the current user hasn't changed this
                        //field, but another user has changed it.  Copy the
                        //copy the db value into our entity.

                        entityWhichFailed.SetNewFieldValue(field.FieldIndex,  
                        dbField.CurrentValue);
                    }
                }
            }

            return !HasConcurrencyErrors;
        }

EntityHelper.CopyEntity and DataHelper.GetAdapter() are my own classes outside of LLBLGen.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39766
Joined: 17-Aug-2003
# Posted on: 29-Dec-2005 11:34:16   

Cool code! Thansk for sharing simple_smile

Frans Bouma | Lead developer LLBLGen Pro
usschad
User
Posts: 77
Joined: 11-Sep-2008
# Posted on: 02-Dec-2024 23:37:52   

Hi, Sorry to post on this old thread, but I was reviewing it and wanted to add that this works when you are using ForcedCurrentValueWrite on a fetched entity.

However, if you are doing this on a new entity you must set entity.Fields.State = EntityState.Fetched. Otherwise, the DBValue wasn't getting set.

In my use case, I am writing unit tests, and want to fake a fetched entity. And this is how it was done:

    public static void MakeAppearFromDb<T>(this T entity) where T : IEntity2
    {
        entity.IsNew = false;
        entity.Fields.State = EntityState.Fetched; // this is the important part we were missing for the below code to work correctly.
        foreach (var field in entity.Fields)
        {
            var val = field.CurrentValue;
            entity.Fields.ForcedValueWrite(field.FieldIndex, val, val);
        }
        entity.IsDirty = false;
        return entity;
    }

p.s. I was actually the OP on this thread, but don't have access to that email anymore.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39766
Joined: 17-Aug-2003
# Posted on: 03-Dec-2024 10:19:47   

usschad wrote:

Sorry to post on this old thread, but I was reviewing it and wanted to add that this works when you are using ForcedCurrentValueWrite on a fetched entity.

However, if you are doing this on a new entity you must set entity.Fields.State = EntityState.Fetched. Otherwise, the DBValue wasn't getting set.

In my use case, I am writing unit tests, and want to fake a fetched entity. And this is how it was done:

    public static void MakeAppearFromDb<T>(this T entity) where T : IEntity2
    {
        entity.IsNew = false;
        entity.Fields.State = EntityState.Fetched; // this is the important part we were missing for the below code to work correctly.
        foreach (var field in entity.Fields)
        {
            var val = field.CurrentValue;
            entity.Fields.ForcedValueWrite(field.FieldIndex, val, val);
        }
        entity.IsDirty = false;
        return entity;
    }

The reason the state has to be set to Fetched isn't for the writing of the values, but for the reading of the DbValue simple_smile When the entity isn't marked as 'Fetched', the DbValues are by definition undefined, so they will always return 'null'. Setting the fields' state to Fetched, means that the DbValues in the Fields object are from the DB and it will return the DbValue that's stored in the DbValues array of the Volatile Data structure inside the Fields object.

In v5 we refactored the internal data structures for the fields to make them much more efficient. Before that each field contained its own value, but after that we store the values in an array (two arrays in fact, DbValues and CurrentValues). The DbValues array isn't created till it's written to. This has the advantage that we can just store the whole array read from the Db in 1 go (so fetches are much faster) and it doesn't create a lot of objects to store the values.

Using Field objects as separate objects is still possible, to keep backwards compatibility, however it's no longer needed. Your foreach loop creates new entityfield2 objects as they're not there after an entity has been created or data has been fetched. It's more efficient to do:

public static void MakeAppearFromDb<T>(this T entity) where T : IEntity2
{
    entity.IsNew = false;
    entity.Fields.State = EntityState.Fetched; // this is the important part we were missing for the below code to work correctly.
    for(int i=0;i<entity.Fields.Count;i++)
    {
        var val = entity.Fields.GetCurrentValue(i);
        entity.Fields.ForcedValueWrite(i, val, val);
    }
    entity.IsDirty = false;
    return entity;
}

as this way no entity field objects are created. While it isn't really important in a unit test but it might be in a high-performance code path where many entities are created and fields are set.

Frans Bouma | Lead developer LLBLGen Pro
usschad
User
Posts: 77
Joined: 11-Sep-2008
# Posted on: 03-Dec-2024 13:51:30   

That makes sense. That might have been the issue, not that the DbValue wasn't being set, but the IsChanged property wasn't updating correctly.

Either way, that was some great insight to how it works and the suggestion for using the index vs foreach is good to know!

Thanks!