Auditing updated fields

Posts   
 
    
tomahawk
User
Posts: 169
Joined: 02-Mar-2005
# Posted on: 16-Jul-2009 02:08:12   

Hi there,

Based on the sample in the documentation, I have added Auditing to my application, which records information to an Audit table. However, I am having doing the following:

When an Entity is Updated, both the old value and new value should be recorded to the database.

I am overriding AuditUpdateOfExistingEntity, but according to the SQL output, the UPDATE to the database is made, then the entity re-read, resluting in my CurrentValue and DbValue properties having the same value.


        public override void AuditUpdateOfExistingEntity(IEntityCore entity)
        {
            HistoryEntity info = CreateNew(entity);
            if (info == null) return;
            info.ChangeType = (int)AuditType.UpdateOfExistingEntity;

            string detail = string.Empty;
            foreach (EntityField f in entity.Fields)
            {
                if (f.CurrentValue != f.DbValue)
                    detail += String.Format(", {0} changed from {1} to {2}", f.Name, f.DbValue, f.CurrentValue);
            }
            info.Comments = detail == string.Empty ? detail : detail.Remove(0, 1);
            _auditEntities.Add(info);
        }

How can I achieve my goal?

Thank you.

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 16-Jul-2009 06:52:35   

Hi there,

You should use AuditEntityFieldSet in conjunction with AuditUpdateOfExistingEntity. This is an example of the two methods (logging to a text file):

public override void AuditEntityFieldSet(IEntityCore entity, int fieldIndex, object originalValue)
{
    // avoid to audit into AuditInfoEntity (this would create an overflow effect). This is necessary if this auditor is injected into 
    // all entities, thus also in the AuditInfoEntity
    if(entity is AuditInfoEntity)
    {
        return;
    }

    // used to store the change experimented by a field.
    string originalValueAsString = string.Empty;
    string currentValue = string.Empty;

    // sets VALUE OR NULL for originalValue and uses it as string.
    if (originalValue != null)
    {
        originalValueAsString = originalValue.ToString();
    }
    else
    {
        originalValueAsString = "NULL";
    }

    // sets VALUE OR NULL for currentValue and uses it as string.
    if (entity.GetCurrentFieldValue(fieldIndex) != null)
    {
        currentValue = entity.GetCurrentFieldValue(fieldIndex).ToString();
    }
    else
    {
        currentValue = "NULL";
    }


    // construct audit info record
    StringWriter auditInfo = ConstructAuditRecord(entity.LLBLGenProEntityName,
        AuditType.EntityFieldSet,
        string.Format("{0}. FieldSet: {1}. OriginalValue: {2}. CurrentValue: {3}",
            GetPrimaryKeyInfoFromEntity(entity),
            ((IEntity2)entity).Fields[fieldIndex].Name,
            originalValueAsString, currentValue));

    // add to later-persist list
    AddAuditRecordStringToBulkStream(auditInfo.GetStringBuilder().ToString());          
}
public override void AuditUpdateOfExistingEntity(IEntityCore entity)
{
    // avoid to audit into AuditInfoEntity (this would create an overflow effect). This is necessary if this auditor is injected into 
    // all entities, thus also in the AuditInfoEntity
    if(entity is AuditInfoEntity)
    {
        return;
    }

    // construct audit info record
    StringWriter auditInfo = ConstructAuditRecord(entity.LLBLGenProEntityName,
        AuditType.UpdateOfExistingEntity,
        GetPrimaryKeyInfoFromEntity(entity));

    // add to later-persist list
    AddAuditRecordStringToBulkStream(auditInfo.GetStringBuilder().ToString());  
}

I recommend you to download the Auditing example at example section (http://llblgen.com/pages/examples.aspx). There you could figure this out. You could, for instance, grab the fields changes via _AuditEntityFieldSet _in a variable, then at _AuditUpdateOfExistingEntity _use that variable to complete the audit record log.

Hope helpful simple_smile

David Elizondo | LLBLGen Support Team
tomahawk
User
Posts: 169
Joined: 02-Mar-2005
# Posted on: 16-Jul-2009 21:07:31   

I looked at that example prior to posting, the issue is that if the user makes several changes to a field (or fields), and then persists them to the DB that 'ALL' of them will get logged, including the ones not persisted. Correct? Example:

User opens form with Street: 1234 First Street User changes Street to : 123 First Street User changes Street to : 123 First St User clicks Save

Database is saved with 123 First St, Audit table shows BOTH changes, as the EntityFieldSet method would have been called twice, once per field set, which would be inaccurate.

Either way, this seems a lot of extra code for something as simple as knowing the current and previous values. It seems to me that the UpdateOfExistingEntity method should be invoked prior to writing the updated value to the database, so that the CurrentValue and DbValue properties are useful. Why is this not the case?

MTrinder
User
Posts: 1461
Joined: 08-Oct-2008
# Posted on: 16-Jul-2009 21:57:23   

Don't forget that what you actually do in the AuditEntityFieldSet is up to you - there is no need to persist each field change at that point - you could for example maintain a hash table per field of the latest value supplied by the user - and then just record the last value when the entity is saved.

Matt

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 17-Jul-2009 10:48:10   

Indeed, the auditor is simply a recorder. You should record what happened, and when asked to persist that info, if desired, create entities for that and return them or store it in another form. So no complicated logic is required, simply record what happened and log that.

Frans Bouma | Lead developer LLBLGen Pro
tomahawk
User
Posts: 169
Joined: 02-Mar-2005
# Posted on: 18-Jul-2009 09:05:48   

I hate to disagree, but it seems to me that it actually is fairly complicated to record the old and new value.

It could be simple, if the DbValue, and CurrentValue were accurate in the AuditUpdateExistingEntity method, as only a single dependency-injected method would have to be written.

If I have to, additionally, intercept the field changed audit event, and then track (presumably in the UI layer, or in the OnSaveEntity event of each Entity) the Save action to know when a new value is actually recorded to the db (and not just edited), that is complicated. Unecessarily so, it seems to me.

Anotherwards, why should the developer have to do tracking when LLBLGen does a fine job already of knowing what values to change in the DB. Why can it not pass this knowledge on to the Audit event, instead of requiring code in different places to rediscover it?

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 18-Jul-2009 10:32:48   

tomahawk wrote:

I hate to disagree, but it seems to me that it actually is fairly complicated to record the old and new value.

It could be simple, if the DbValue, and CurrentValue were accurate in the AuditUpdateExistingEntity method, as only a single dependency-injected method would have to be written.

If I have to, additionally, intercept the field changed audit event, and then track (presumably in the UI layer, or in the OnSaveEntity event of each Entity) the Save action to know when a new value is actually recorded to the db (and not just edited), that is complicated. Unecessarily so, it seems to me.

Anotherwards, why should the developer have to do tracking when LLBLGen does a fine job already of knowing what values to change in the DB. Why can it not pass this knowledge on to the Audit event, instead of requiring code in different places to rediscover it?

The problem is timing. When you step in to audit changes at save time, it's too late to track who did made the change or when the change was made. In your auditor, you should simply wait till an entity field set audit event is triggered, so you can track that change: in a list, track which values are changed, when and by whom (active thread's user for example) and for which pk. At save time you can't do that, you then can only audit which fields were changed.

Back to AuditUpdateExistingEntity. It's defined as the event for a successful update of the entity. This has been changed a while ago in v2.6. It previously had the call to AuditUpdateExistingEntity after the refetch of data, it is now doing it before the refetch, so the dbvalue and currentvalue are still different. Now, it might be you're checking on the IsChanged flag. I see in the code there's still a problem with the code, as the AcceptChanges (which resets IsChanged flags) is called before the audit. Do you use the latest runtime lib and if so, do you rely on the IsChanged flag?

Frans Bouma | Lead developer LLBLGen Pro
tomahawk
User
Posts: 169
Joined: 02-Mar-2005
# Posted on: 20-Jul-2009 21:07:11   

I am using Runtime lib 2.6.09.0116

I do not rely on the IsChanged flag in the method. I do compare DbValue and CurrentValue, and if not equal, log as a change. However, when AuditUpdateExistingEntity gets called, the UPDATE has already been issued (according to the debug output), and both DbValue and CurrentValue are equal to the updated value.

Will try again with newest libs.

tomahawk
User
Posts: 169
Joined: 02-Mar-2005
# Posted on: 20-Jul-2009 21:15:06   

With newest libs, the update is still performed prior to AuditUpdateExistingEntity being called, rendering DbValue and CurrentValue equal. This project is using SelfServicing.

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 21-Jul-2009 07:55:39   

tomahawk wrote:

With newest libs, the update is still performed prior to AuditUpdateExistingEntity being called, rendering DbValue and CurrentValue equal. This project is using SelfServicing.

I can't repro that with the latest build. What LLBLGen Runtime Library version are you using now? Please post some relevant code to see whether or not something is missing.

David Elizondo | LLBLGen Support Team
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 21-Jul-2009 09:26:41   

tomahawk wrote:

I am using Runtime lib 2.6.09.0116

I do not rely on the IsChanged flag in the method. I do compare DbValue and CurrentValue, and if not equal, log as a change. However, when AuditUpdateExistingEntity gets called, the UPDATE has already been issued (according to the debug output), and both DbValue and CurrentValue are equal to the updated value. Will try again with newest libs.

The update statement indeed has already ran, as that's what the method is for: auditing a successful update. One can't audit that if the update wasn't performed yet.

The thing is though: selfservicing doesn't refetch data by itself during save, it does that when you touch a property again after a save. This means that the DbValue and CurrentValue are left untouched: the audit call should reveal different values for DbValue and CurrentValue.

As you simply do a '==' compare, it might be you get different results. The problem is that if you have a byte array value for example, and you change a byte inside that array, you won't see the change, but llblgen pro will, as it does value compares on byte array values.

So, indeed, how did you test this, and what were the results you got, vs. the results you would have expected? Because, the update statement has no influence on DbValue and CurrentValue.

ps: please, next time try the latest builds of the runtime first, and give the runtime lib build nr as well as what you're using: adapter or selfservicing. We can't see your code in front of us, and therefore it will only take longer for us to help you. Thanks.

Frans Bouma | Lead developer LLBLGen Pro
tomahawk
User
Posts: 169
Joined: 02-Mar-2005
# Posted on: 21-Jul-2009 11:34:40   

The thing is though: selfservicing doesn't refetch data by itself during save, it does that when you touch a property again after a save. This means that the DbValue and CurrentValue are left untouched: the audit call should reveal different values for DbValue and CurrentValue.

I see, my trouble is I am touching a property before my comparison, so the entity reloads from the database. Any way I can avoid the reloading? I'd like to not unecessarily hit the database again from an update. Thanks for the help.

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 21-Jul-2009 17:14:15   

How are you touching it? Try something like this:

theEntity.Fields["NameOfField"].CurrentValue
// instead of
theEntity.Field
David Elizondo | LLBLGen Support Team
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 21-Jul-2009 17:56:18   

Or use entity.GetCurrentFieldValue(). I can't see how you touch the property in the code snippet of the first post. This audit method is called right after the save, so I don't really see where you touch a field, if the audit method is as you described in the first post.

Frans Bouma | Lead developer LLBLGen Pro
tomahawk
User
Posts: 169
Joined: 02-Mar-2005
# Posted on: 21-Jul-2009 21:26:43   
HistoryEntity info = CreateNew(entity);

It happens in the CreateNew method. I cast the entity in there and access a property directly. I can just check the CurrentValue and DbValue properties prior to this method.

When I do inspect the property I need, however, I am using the following:

(int?)((IEntityField)entity.Fields[(int)AddressFieldIndex.ContactID]).CurrentValue

as entity.GetCurrentFieldValue(int fieldIndex) does cause a refetch. If there an easy way to tell the entity not to do any lazy loading? Having to do this sort of casting for every property I want to record is tedious. Thanks for your patience with this.