Auditor Question

Posts   
 
    
JayWare
User
Posts: 14
Joined: 23-Sep-2010
# Posted on: 23-Sep-2010 22:49:48   

I've recently taken over a solution that uses LLBLGen Pro and I've got a question about the Auditor. It was commented out in the code, and I need to utilize it for a feature. I've un-commented it, and can step through it. I have also added a call to "AuditInsertOfNewEntity(IEntityCore entity)" on a method that creates a new entity - I've tried calling the Editor before and after the Entity.Save(), and then I step through the auditor, but it doesn't seem to save it out to the AuditInfo table.

Any ideas?

 public class GeneralAuditor : AuditorBase
    {
        private enum AuditType
        {
            DeleteOfEntity = 1,
            DirectDeleteOfEntities,
            DirectUpdateOfEntities,
            DereferenceOfRelatedEntity,
            ReferenceOfRelatedEntity,
            EntityFieldSet,
            InsertOfNewEntity,
            UpdateOfExistingEntity
        }

        private List<AuditInfoEntity> _auditInfoEntities;

        /// <summary>CTor </summary>
        public GeneralAuditor()
        {
            _auditInfoEntities = new List<AuditInfoEntity>();
        }

        /// <summary>Audits the successful delete of an entity from the database</summary>
        /// <param name="entity">The entity which was deleted.</param>
        /// <remarks>As the entity passed in was deleted succesfully, reading values from the 
        /// passed in entity is only possible in this routine. After this call, the
        /// state of the entity will be reset to Deleted again and reading the fields 
        /// will result in an exception. It's also recommended not to reference
        /// the passed in entity in any audit entity you might want to persist as the entity 
        /// doesn't exist anymore in the database.</remarks>
        public override void AuditDeleteOfEntity(IEntityCore entity)
        {
            AuditInfoEntity auditInfo = new AuditInfoEntity();
            auditInfo.ActionBy = FSPrincipal.GetCurrentPricipal().UserGuid;
            auditInfo.CustomerGuid = FSPrincipal.GetCurrentPricipal().CustomerGuid;
            IEntity obj = (IEntity)entity;
            try
            {
                if (obj.Fields["Guid"] != null)
                    auditInfo.RequestGuid = (Guid)obj.Fields["Guid"].CurrentValue;

            }
            catch (Exception ee)
            {
            }
            auditInfo.AffectedEntityName = entity.LLBLGenProEntityName;
            auditInfo.ActionDateTime = DateTime.Now;
            auditInfo.ActionType = (int)AuditType.DeleteOfEntity;
            _auditInfoEntities.Add(auditInfo);

        }

        /// <summary>Audits the successful dereference of related entity from the entity passed in.</summary>
        /// <param name="entity">The entity of which the related entity was dereferenced from.</param>
        /// <param name="relatedEntity">The related entity which was dereferenced from entity</param>
        /// <param name="mappedFieldName">Name of the mapped field onto the relation from entity to related 
        /// entity for which the related entity was dereferenced.</param>
        public override void AuditDereferenceOfRelatedEntity(IEntityCore entity, IEntityCore relatedEntity,
            string mappedFieldName)
        {
            AuditInfoEntity auditInfo = new AuditInfoEntity();

            auditInfo.ActionBy = FSPrincipal.GetCurrentPricipal().UserGuid;
            auditInfo.CustomerGuid = FSPrincipal.GetCurrentPricipal().CustomerGuid;
            IEntity obj = (IEntity)entity;
            try
            {
                if (obj.Fields["Guid"] != null)
                    auditInfo.RequestGuid = (Guid)obj.Fields["Guid"].CurrentValue;

            }
            catch (Exception ee)
            {
            }
            auditInfo.AffectedEntityName = entity.LLBLGenProEntityName;
            auditInfo.ActionDateTime = DateTime.Now;
            auditInfo.ActionType = (int)AuditType.DereferenceOfRelatedEntity;
            auditInfo.ActionData = string.Format("RelatedEntityName: {0}. MappedFieldName: {1}",
                    relatedEntity.LLBLGenProEntityName, mappedFieldName);
            _auditInfoEntities.Add(auditInfo);

        }

        /// <summary>Audits the successful insert of a new entity into the database.</summary>
        /// <param name="entity">The entity saved successfully into the database.</param>
        public override void AuditInsertOfNewEntity(IEntityCore entity)
        {
            AuditInfoEntity auditInfo = new AuditInfoEntity();

            auditInfo.ActionBy = FSPrincipal.GetCurrentPricipal().UserGuid;
            auditInfo.CustomerGuid = FSPrincipal.GetCurrentPricipal().CustomerGuid;
            IEntity obj = (IEntity)entity;
            try
            {
                if (obj.Fields["Guid"] != null)
                    auditInfo.RequestGuid = (Guid)obj.Fields["Guid"].CurrentValue;

            }
            catch (Exception ee)
            {
            }
            auditInfo.AffectedEntityName = entity.LLBLGenProEntityName;
            auditInfo.ActionDateTime = DateTime.Now;
            auditInfo.ActionType = (int)AuditType.InsertOfNewEntity;
            _auditInfoEntities.Add(auditInfo);

       }

        /// <summary>
        /// Audits the successful reference of related entity from the entity passed in.
        /// </summary>
        /// <param name="entity">The entity of which the related entity was dereferenced from.</param>
        /// <param name="relatedEntity">The related entity which was dereferenced from entity</param>
        /// <param name="mappedFieldName">Name of the mapped field onto the relation from entity to related 
        /// entity for which the related entity was referenced.</param>
        public override void AuditReferenceOfRelatedEntity(IEntityCore entity, IEntityCore relatedEntity,
            string mappedFieldName)
        {
            AuditInfoEntity auditInfo = new AuditInfoEntity();

            auditInfo.ActionBy = FSPrincipal.GetCurrentPricipal().UserGuid;
            auditInfo.CustomerGuid = FSPrincipal.GetCurrentPricipal().CustomerGuid;
            IEntity obj = (IEntity)entity;
            try
            {
                if (obj.Fields["Guid"] != null)
                    auditInfo.RequestGuid = (Guid)obj.Fields["Guid"].CurrentValue;

            }
            catch (Exception ee)
            {
            }
            auditInfo.AffectedEntityName = entity.LLBLGenProEntityName;
            auditInfo.ActionDateTime = DateTime.Now;
            auditInfo.ActionType = (int)AuditType.ReferenceOfRelatedEntity;
            auditInfo.ActionData = string.Format("RelatedEntityName: {0}. MappedFieldName: {1}",
                relatedEntity.LLBLGenProEntityName, mappedFieldName);
            _auditInfoEntities.Add(auditInfo);


        }

        /// <summary>
        /// Audits the successful update of an existing entity in the database
        /// </summary>
        /// <param name="entity">The entity updated successfully in the database.</param>
        public override void AuditUpdateOfExistingEntity(IEntityCore entity)
        {
            AuditInfoEntity auditInfo = new AuditInfoEntity();

            auditInfo.ActionBy = FSPrincipal.GetCurrentPricipal().UserGuid;
            auditInfo.CustomerGuid = FSPrincipal.GetCurrentPricipal().CustomerGuid;
            IEntity obj = (IEntity)entity;
            try
            {
                if (obj.Fields["Guid"] != null)
                    auditInfo.RequestGuid = (Guid)obj.Fields["Guid"].CurrentValue;

            }
            catch (Exception ee)
            {
            }
            auditInfo.AffectedEntityName = entity.LLBLGenProEntityName;
            auditInfo.ActionDateTime = DateTime.Now;
            auditInfo.ActionType = (int)AuditType.UpdateOfExistingEntity;
            _auditInfoEntities.Add(auditInfo);

            

       }

        /// <summary>
        /// Gets the audit entities to save. Audit entities contain the audit information stored 
        /// inside this auditor.
        /// </summary>
        /// <returns>The list of audit entities to save, or null if there are no audit entities to save</returns>
        /// <remarks>Do not remove the audit entities and audit information from this auditor when this method is 
        /// called, as the transaction in which the save takes place can fail and retried which will result in 
        /// another call to this method</remarks>
        public override IList GetAuditEntitiesToSave()
        {
            return _auditInfoEntities;
        }

        /// <summary>
        /// The transaction with which the audit entities requested from GetAuditEntitiesToSave were saved.
        /// Use this method to clear any audit data in this auditor as all audit information is persisted 
        /// successfully.
        /// </summary>
        public override void TransactionCommitted()
        {
            _auditInfoEntities.Clear();
        } 
    }

Update: It looks like I might have to iterated through the IList of AuditInfoEntities to save them one at a time.

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 24-Sep-2010 06:43:13   

Sorry but I don't understand the problem. Is that the audit info entities doenst get persisted?

GetAuditEntitiesToSave() should do the work.

Please post more info.

David Elizondo | LLBLGen Support Team
JayWare
User
Posts: 14
Joined: 23-Sep-2010
# Posted on: 24-Sep-2010 19:40:20   

daelmo wrote:

Sorry but I don't understand the problem. Is that the audit info entities doenst get persisted?

GetAuditEntitiesToSave() should do the work.

Please post more info.

You're right - I didn't realize I had to get the list, iterate through and save the List - I thought it happened automatically.. so that's why it wasn't saving. I am able to save now.

However, I have a question about order of execution;

Should I make a call to the Auditor before or after the Entity save? I'm guessing before, so the auditor can determine what is about to change.. then after the save, commit the audit.. or something like that. Is that right?

Thanks for the help,

Jay

Follow up question: It doesn't look like the GeneralAuditor is built to automatically audit/log every field change that occurs on an Update.. is that right? It looks I need to build a custom process to handle this... is there a generic way to determine what fields have changed? Or do I need to look up the entity to compare field by field with what is passed in to the Auditor method?

JayWare
User
Posts: 14
Joined: 23-Sep-2010
# Posted on: 25-Sep-2010 00:02:21   

daelmo wrote:

Sorry but I don't understand the problem. Is that the audit info entities doenst get persisted?

GetAuditEntitiesToSave() should do the work.

Please post more info.

Let me back up.. the GeneralAuditor isn't being called automatically. I've found that creating a utility to create an instance of GeneralAuditor and calling it works, but I've read that this can be configured to be called automatically by all entity save events. How do I do that exatcly?

Is it just by adding this?

<add key="autoDependencyInjectionDiscovery" value="true"/>

Thanks,

Jay

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 26-Sep-2010 13:12:48   

Hi there,

First of all, your class (at least the code you posted) doesn't have any injection information. It should look like this:

[DependencyInjectionInfo(typeof(IEntity2), "AuditorToUse")]
[Serializable]
public class GeneralAuditor : AuditorBase
{
...

You also have other options to set your Auditor: Setting AuditorToUse in your entity, or override the CreateAuditor method on your entity (or EntityBase2).

You don't have to save anything in the Auditor, when you return the list of entities in the GetAuditEntitiesToSave, the entities are auto persisted.

If you want to see an example of this, please download the Auditor example from LLBLGen site.

David Elizondo | LLBLGen Support Team
JayWare
User
Posts: 14
Joined: 23-Sep-2010
# Posted on: 27-Sep-2010 05:13:31   

daelmo wrote:

Hi there,

First of all, your class (at least the code you posted) doesn't have any injection information. It should look like this:

[DependencyInjectionInfo(typeof(IEntity2), "AuditorToUse")]
[Serializable]
public class GeneralAuditor : AuditorBase
{
...

You also have other options to set your Auditor: Setting AuditorToUse in your entity, or override the CreateAuditor method on your entity (or EntityBase2).

You don't have to save anything in the Auditor, when you return the list of entities in the GetAuditEntitiesToSave, the entities are auto persisted.

If you want to see an example of this, please download the Auditor example from LLBLGen site.

Thanks, David

I read that about Entity2 in the DependencyInjectionInfo in the Online documentation. But with the info in the doc, I don't see what / how to enable an entity to be auditable.

How would I make an entity automatically audit (ie Setting AuditorToUse in your entity). I can't find an example in the doc.

I'll I really have a handle on is;

  1. Where the Entity is changed/saved through out the business application
  2. Where the entity class files are
  3. Where the GeneralAuditor class is

So is it possible to configure these 3 files to get going with Auditing?

Thanks for the help,

Jay

Walaa avatar
Walaa
Support Team
Posts: 14995
Joined: 21-Aug-2005
# Posted on: 27-Sep-2010 08:43:18   

First thing you need to do is read about DI

Then you need to read Setting up and using Auditing

As David said, you enable an entity to be Audited by just decorating the Auditor class with the correct attribute.

If you don't want to Audit all entities, and just want to audit one entity (eg. CustomerEntity)

Do the following:

[DependencyInjectionInfo(typeof(CustomerEntity), "AuditorToUse")]
[Serializable]
public class GeneralAuditor : AuditorBase
{
...
}

The attribute can be repeated to use with other entities as well.

JayWare
User
Posts: 14
Joined: 23-Sep-2010
# Posted on: 27-Sep-2010 17:36:39   

Thanks - I was able to get this going.

I found by debugging to see exactly what "AuditorToUse" value was being set on the entity property by the DependencyInjection, and found another auditor class in this solution that is using it - which was targeting all entities (IEntity) for injection.

It looks like competing DependencyInjection attributes will default to 1 of the Auditor classes, but it's not clear which one - I was looking for a generic auditor to defer to a specifically typed Auditor instead of using the generic one.

I updated that class to have attributes for only the entities I think that class is concerned with, created a new auditor class for the specific entity I'm trying to audit, and it's hitting it.

Thanks for the help,

Jay

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 28-Sep-2010 05:45:36   

Jay. Good to hear you got that working. Come back here if you need further help wink

David Elizondo | LLBLGen Support Team
JayWare
User
Posts: 14
Joined: 23-Sep-2010
# Posted on: 30-Sep-2010 02:59:35   

daelmo wrote:

Jay. Good to hear you got that working. Come back here if you need further help wink

Thanks..

I have been issues with the DBValue and the CurrentValue on the IEntity object. The code below works, but I had to do a lot of trial and error. I found that if I accessed the obj, or the entity while moving through the fields, it would commit the entity to the database and from then on all current and dbvalues were the same.

But the auditor works great for field by field audit trails..


public override void AuditUpdateOfExistingEntity(IEntityCore entity)
        {

            try
            {
                            
                IEntity obj = (IEntity)entity;  

                string entityname = obj.LLBLGenProEntityName;
                ArrayList fieldchanges = new ArrayList();

                switch (entityname)
                {
                    case "PersonEntity":
                        {
                            PersonEntity person = (PersonEntity)obj;                        
                        
                            
                            foreach (object o in obj.Fields)
                            {
                                try
                                {                                   
                                    string fieldname = ((IEntityFieldCore)o).Name;
                                    string currentvalue = ((IEntityFieldCore)o).CurrentValue.ToString();
                                    string dbvalue = ((IEntityFieldCore)o).DbValue.ToString();

                                    if (currentvalue != dbvalue)
                                    {
                                        string[] changes =  entityname,fieldname,dbvalue,currentvalue}; 
                                        fieldchanges.Add(changes);
                                    }
                                }
                                catch
                                {
                                    //old or new field might be null
                                }

                            }

                            foreach (string[] change in fieldchanges)
                            {
                                AddAuditInfo(AuditType.UpdateOfExistingEntity, "StudentMedical", change[0], change[1], change[2], change[3], semaForm.Guid);
                            }



                        }
                        break;

                    
                }
            }
            catch(Exception e)
            {
                Utilities.AddLogEntry("Audit Failed", e.Message, LogLevel.Error);
            }

       }

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 30-Sep-2010 06:28:40   

I understand your concern. That however (AuditUpdateOfExistingEntity) is not the ideal place to track field changes. Remember that AuditUpdateOfExistingEntity is called just after the entity is saved (no tx commited, but the entity is already saved). So, in AuditUpdateOfExistingEntity you have to set some entity specific info, not field specific info.

So the ideal setup is that you track field changes through AuditOfEntityFieldSet. Please see the Auditor example to see the ideal way to audit field changes.

David Elizondo | LLBLGen Support Team
JayWare
User
Posts: 14
Joined: 23-Sep-2010
# Posted on: 30-Sep-2010 06:42:29   

daelmo wrote:

I understand your concern. That however (AuditUpdateOfExistingEntity) is not the ideal place to track field changes. Remember that AuditUpdateOfExistingEntity is called just after the entity is saved (no tx commited, but the entity is already saved). So, in AuditUpdateOfExistingEntity you have to set some entity specific info, not field specific info.

So the ideal setup is that you track field changes through AuditOfEntityFieldSet. Please see the Auditor example to see the ideal way to audit field changes.

Humm.. using the entity auditor seems to work in the state it's in, but was delicate to not touch something to refresh the dbvalue values - I'm hoping that it's stable and it's not an issue of timing or something like that.. I need to roll this feature out tomorrow.

But sounds like I need to redo it using the field auditing technique.. but I can't find it - I'm looking at this link http://www.llblgen.com/documentation/3.0/LLBLGen%20Pro%20RTF/hh_start.htm#Using%20the%20generated%20code/gencode_auditing.htm

Is there a different example?

Thanks,

Jay

Walaa avatar
Walaa
Support Team
Posts: 14995
Joined: 21-Aug-2005
# Posted on: 30-Sep-2010 09:24:28   

From the Reference Manual, AuditorBase class has the following method:

public virtual void AuditEntityFieldSet( 
   IEntityCore entity,
   int fieldIndex,
   object originalValue
)

This you will have to override to catch EntityField's changes.

JayWare
User
Posts: 14
Joined: 23-Sep-2010
# Posted on: 30-Sep-2010 21:31:27   

Walaa wrote:

From the Reference Manual, AuditorBase class has the following method:

public virtual void AuditEntityFieldSet( 
   IEntityCore entity,
   int fieldIndex,
   object originalValue
)

This you will have to override to catch EntityField's changes.

Thanks, That's perfect..

MTrinder
User
Posts: 1461
Joined: 08-Oct-2008
# Posted on: 30-Sep-2010 22:11:13   

Always happy to help (despite the fact this is my only input on this thread simple_smile ). Feel free to come back if you have any more problems.

Matt