Audit of value for insert, need primary key...

Posts   
 
    
Posts: 1268
Joined: 10-Mar-2006
# Posted on: 02-Nov-2011 16:11:32   

In the audit table, it is common to store the primary key of the record you are auditing and this is what I do also.

This works great in an AuditUpdateOfExistingEntity() scenario.

However, in an AuditInsertOfNewEntity() case, the primary key is not known until after the insert as the SQL server database hands out the identity column for us.

I realize this is not a new problem... http://www.llblgen.com/tinyforum/Messages.aspx?ThreadID=812

What I would like to do is propose a solution. Add a new method called AuditAfterInsertOfNewEntity() that is called after the insert of the entity is complete and the pkey has been refetched.

That would be very helpful. Thoughts?

Walaa avatar
Walaa
Support Team
Posts: 14995
Joined: 21-Aug-2005
# Posted on: 02-Nov-2011 17:20:00   

AFAIK, the PK is available in the AuditInsertOfNewEntity(). As the framework calls this method after inserting the entity.

Which LLBLGenPro runtime library version (build number) are you using?

Posts: 1268
Joined: 10-Mar-2006
# Posted on: 04-Nov-2011 13:21:11   

The pkey value is not populated for sure. I have override of AuditInsertOfNewEntity() and AuditUpdateOfExistingEntity(). Each of those overrides call the same internal method. The update works fine, the insert fails because the primary key is invalid.

The same situation was reported (and seemed to be acknowledged by Otis) in the link I posted above.

BTW, the version is 3.1.11.907.

(Sorry for the delayed response, I have been waiting for an answer to this thread for days, evidently I did not get the email notification. simple_smile )

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 04-Nov-2011 20:13:09   

The thread you posted is very old one, it such time Auditors did not exist. The PK is populated for new entities as the entity is already save when you reach AuditInsertOfNewEntity. The fetch of the primary key is part of the save process so it should be populated. I already tested in the Auditor example (which uses this approach).

If you don't have the PK populated please show us your code to see what's going wrong.

David Elizondo | LLBLGen Support Team
Posts: 1268
Joined: 10-Mar-2006
# Posted on: 09-Nov-2011 00:24:17   

Sorry, for the delay. Basically, this just tracks a single field in a table for changes. If that changes, there is a table designed to store just that change. This code works great, except if I want the initial value stored and uncomment the code in AuditInsertOfNewEntity, it crashes with SomeTableHistory missing pkey. Uses exact same code in update - so I know pkey is being retrieved correctly. This leads me to the conclusion the pkey is invalid on inserts when this code is called. They value that is put into the pkey is a -1, which means the type was not it or the value was null...

Thanks in advance.

namespace Test
{
    [DependencyInjectionInfo(typeof(SomeEntity), "AuditorToUse")]
    [Serializable]
    class SomeHistoryAuditor : AuditorBase
    {

        #region Data Members

        private int? NewMonitoredFieldId = null;
        private List<SomeTableHistoryEntity> _auditInfoEntities;

        #endregion

        #region Constructor

        public SomeHistoryAuditor()
        {
            _auditInfoEntities = new List<SomeTableHistoryEntity>();
        }

        #endregion

        #region Override Methods

        public override void AuditEntityFieldSet(IEntityCore entity, int fieldIndex, object originalValue)
        {
            #region Field Set, for column level auditing

            CommonEntityBase commonEntityBase = entity as CommonEntityBase;

            if (commonEntityBase == null)
            {
                throw new ArgumentException("Bad Entity Type : Expecting a CommonEntityBase", "entity");
            }

            if (fieldIndex == (int)SomeFieldIndex.MonitoredFieldId)
            {
                NewMonitoredFieldId = (int)commonEntityBase.Fields[fieldIndex].CurrentValue;
            }

            #endregion
        }

        public override void AuditInsertOfNewEntity(IEntityCore entity)
        {
            //addColumnAuditEntries(entity);
        }

        public override void AuditUpdateOfExistingEntity(IEntityCore entity)
        {
            addColumnAuditEntries(entity);
        }

        private void addColumnAuditEntries(IEntityCore entity)
        {
            #region Turn each column edit into a row in the audit table.

            if (!NewMonitoredFieldId.HasValue)
                return;

            //only keep last change...
            SomeTableHistoryEntity auditInfo;
            if (_auditInfoEntities.Count == 0)
            {
                auditInfo = getCurrentEntityInformation(entity);
                _auditInfoEntities.Add(auditInfo);
            }
            else
                auditInfo = _auditInfoEntities[0];
            auditInfo.MonitoredFieldId = NewMonitoredFieldId.Value;

            #endregion
        }


        public override IList GetAuditEntitiesToSave()
        {
            #region Get Audit Entities to Save

            return _auditInfoEntities;

            #endregion
        }

        public override void TransactionCommitted()
        {
            #region Reset data members to nothing to prepare for the next auditable transaction.

            NewMonitoredFieldId = null;
            _auditInfoEntities.Clear();

            #endregion
        }

        #endregion

        #region Helper Methods

        private int getTableKey(IList primaryKeyFields)
        {
            #region Get Table Key

            if (primaryKeyFields.Count == 0)
            {
                return -1;
            }

            //in inheritance situations, there are multiple keys, one from each table in the inheritance
            //all have the same value though!
            IEntityField keyfield = (IEntityField)primaryKeyFields[0];
            if (keyfield.ActualDotNetType != typeof(int) || keyfield.DbValue == null)
                return -1;
            return (int)keyfield.DbValue;

            #endregion
        }

        private SomeTableHistoryEntity getCurrentEntityInformation(IEntityCore entity)
        {
            #region Get Current Entity Information

            SomeTableHistoryEntity auditInfo = new SomeTableHistoryEntity();
            auditInfo.AuditorToUse = null;  //make sure auditing is not done on this table!
            auditInfo.SomeId = getTableKey(entity.PrimaryKeyFields);

            try
            {
                auditInfo.UserName = System.Threading.Thread.CurrentPrincipal.Identity.Name;
            }
            catch
            {
                auditInfo.UserName = "unknown";
            }

            return auditInfo;

            #endregion
        }

        #endregion
    }
}

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 09-Nov-2011 01:01:25   
private int getTableKey(IList primaryKeyFields)
        {
            #region Get Table Key

            if (primaryKeyFields.Count == 0)
            {
                return -1;
            }

            //in inheritance situations, there are multiple keys, one from each table in the inheritance
            //all have the same value though!
            IEntityField keyfield = (IEntityField)primaryKeyFields[0];
            if (keyfield.ActualDotNetType != typeof(int) || keyfield.DbValue == null)
                return -1;
            return (int)keyfield.DbValue;

            #endregion
        }

Don't use the DBValue for this method. DBValue is not populated at this point because technically the entity isn't refetched yet, and the whole save process isn't finish yet. Remember that if you raise an exception at this point, the transaction will be rollback, so you can't refer DBValue yet.

In short, use CurrentValue instead, this will work for new and existing entities:

private int getTableKey(IList primaryKeyFields)
        {
            #region Get Table Key

            if (primaryKeyFields.Count == 0)
            {
                return -1;
            }

            //in inheritance situations, there are multiple keys, one from each table in the inheritance
            //all have the same value though!
            IEntityField keyfield = (IEntityField)primaryKeyFields[0];
            if (keyfield.ActualDotNetType != typeof(int) || keyfield.CurrentValue == null)
                return -1;
            return (int)keyfield.CurrentValue;

            #endregion
        }
David Elizondo | LLBLGen Support Team