Click here to Skip to main content
11,483,003 members (73,171 online)
Click here to Skip to main content

Implementing Audit Trail using Entity Framework Part -1

, 27 Mar 2009 CPOL 165.5K 3.6K 103
Rate this:
Please Sign up or sign in to vote.
Implementing Audit Trail using Entity Framework's caching entries

Introduction

Entity framework keeps track for those entire objects and relationships which have been deleted, added and modified in the container. EF keeps the state of the object and holds the necessary change-information and all these track information for each object or relationship resides as “objectStateEntry”. Using “ObjectStateManager” one can access all these change-information like object-state (added/modified/deleted), modified properties, original and current values and can easily do audit trail for those objects. To get the Rollback feature from that audit trail we have to consider some issues. We have to maintain the order of entity graph while insertion and deletion. That means root entity has been inserted before the children and during deletion we have to make it reverse. The most important issue is that we have to make sure that audit trail entry will be inserted according this order.

So now I am going to talk about audit trail implementation that’s capable to rollback to a certain period. To make such implementation I am going to use the Entity framework’s caching Management that is called as “ObjectStateManager”.Using this Manager I will be capable to find out the object that is currently changed or added or deleted and resides in EF cache as Object state entry. In part 1, I just going to talk about creating audit trail objects using the object state entry. In Second Part I will talk about roll back feature of this audit trial.

Using the Code

First I make table audit trail in database

DataBaseDbAudit.JPG

For this table, I am going to make an Entity set in my conceptual level as

dbAuditEF.JPG

In Entity Framework, to save my all changes into Db we have call “Context.SaveChanges()” and This Context is a container that has been inherited from “ObjectContext” Class.To create the Audit trail Objects for each “Modified/Added/Deleted”I am going to catch the event-

Context.SavingChanges +=new EventHandler(Context_SavingChanges);

In sample program I have done it with writing partial class -

public partial class AdventureWorksEntities
{ partial void OnContextCreated()
{
    this.SavingChanges += new EventHandler(AdventureWorksEntities_SavingChanges);
}

void AdventureWorksEntities_SavingChanges(object sender, EventArgs e)
{

So in my “AdventureWorksEntities_SavingChanges” method I am going to create all “dbaudit” objects those are going to save in DB.Here its takes each entry from EF cache of state- Added or Deleted or Modified and call a factory method to produce audit trail object.

public partial class AdventureWorksEntities
{
    public string UserName { get; set; }
    List<DBAudit> auditTrailList = new List<DBAudit>();

    public enum AuditActions
    {
        I,
        U,
        D
    }

    partial void OnContextCreated()
    {
        this.SavingChanges += new EventHandler(AdventureWorksEntities_SavingChanges);
    }

    void AdventureWorksEntities_SavingChanges(object sender, EventArgs e)
    {
        IEnumerable<ObjectStateEntry> changes = 
            this.ObjectStateManager.GetObjectStateEntries(
            EntityState.Added | EntityState.Deleted | EntityState.Modified);
        foreach (ObjectStateEntry stateEntryEntity in changes)
        {
            if (!stateEntryEntity.IsRelationship &&
            stateEntryEntity.Entity != null &&
            !(stateEntryEntity.Entity is DBAudit))
            {//is a normal entry, not a relationship
                DBAudit audit = this.AuditTrailFactory(stateEntryEntity, UserName);
                auditTrailList.Add(audit);
            }
        }

        if (auditTrailList.Count > 0)
        {
            foreach (var audit in auditTrailList)
            {//add all audits 
                this.AddToDBAudit(audit);
            }
        }
    }

And here “AuditTrailFactory” is Factory method to create dbaudit object.Specially for Modify state it keeps the modified properties and serialized as XML.So using these field you can easily show the changes of modified object with doing any comparison of old and new data.

private DBAudit AuditTrailFactory(ObjectStateEntry entry, string UserName)
{
    DBAudit audit = new DBAudit();
    audit.AuditId = Guid.NewGuid().ToString();
    audit.RevisionStamp = DateTime.Now;
    audit.TableName = entry.EntitySet.Name;
    audit.UserName = UserName;

    if (entry.State == EntityState.Added)
    {//entry is Added 
        audit.NewData = GetEntryValueInString(entry, false);
        audit.Actions = AuditActions.I.ToString();
    }
    else if (entry.State == EntityState.Deleted)
    {//entry in deleted
        audit.OldData = GetEntryValueInString(entry, true);
        audit.Actions = AuditActions.D.ToString();
    }
    else
    {//entry is modified
        audit.OldData = GetEntryValueInString(entry, true);
        audit.NewData = GetEntryValueInString(entry, false);
        audit.Actions = AuditActions.U.ToString();

        IEnumerable<string> modifiedProperties = entry.GetModifiedProperties();
        //assing collection of mismatched Columns name as serialized string 
        audit.ChangedColumns = XMLSerializationHelper.XmlSerialize(
            modifiedProperties.ToArray());
    }

    return audit;
}

Here “GetEntryValueInString” is for creating XML text of Previous or modified object. In Entity Framework each entry hold all change defination.First I make a clone the current object.Using entry.GetModifiedProperties() I can get only modified properties of an object and Using “OriginalValues” and “CurrentValues” I can build myself the old data and new data.Factory has told me what that wants – old or new. At the End I have done XML serialized and return back XML string .

private string GetEntryValueInString(ObjectStateEntry entry, bool isOrginal)
{
    if (entry.Entity is EntityObject)
    {
        object target = CloneEntity((EntityObject)entry.Entity);
        foreach (string propName in entry.GetModifiedProperties())
        {
            object setterValue = null;
            if (isOrginal)
            {
                //Get orginal value 
                setterValue = entry.OriginalValues[propName];
            }
            else
            {
                //Get orginal value 
                setterValue = entry.CurrentValues[propName];
            }
            //Find property to update 
            PropertyInfo propInfo = target.GetType().GetProperty(propName);
            //update property with orgibal value 
            if (setterValue == DBNull.Value)
            {//
                setterValue = null;
            }
            propInfo.SetValue(target, setterValue, null);
        }//end foreach

        XmlSerializer formatter = new XmlSerializer(target.GetType());
        XDocument document = new XDocument();

        using (XmlWriter xmlWriter = document.CreateWriter())
        {
            formatter.Serialize(xmlWriter, target);
        }
        return document.Root.ToString();
    }
    return null;
}

To clone the entity I have used the method which I have found in MSDN forum Post (Thanx toPatrick Magee )-

public EntityObject CloneEntity(EntityObject obj)
{
    DataContractSerializer dcSer = new DataContractSerializer(obj.GetType());
    MemoryStream memoryStream = new MemoryStream();

    dcSer.WriteObject(memoryStream, obj);
    memoryStream.Position = 0;

    EntityObject newObject = (EntityObject)dcSer.ReadObject(memoryStream);
    return newObject;
}

That is all for Part-1 where I just create the Audit trail objects for each CUD operation.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Morshed Anwar
Team Leader Adaptive Enterprise Limited (www.ael-bd.com)
Bangladesh Bangladesh
No Biography provided

Comments and Discussions

 
QuestionDo I need to add over ride code to all my entities Pin
Member 1159775213-Apr-15 11:07
memberMember 1159775213-Apr-15 11:07 
QuestionHow you extend this to support tracking only selected entity and selected properties of that entity?? Pin
Nirosh29-Jan-14 19:47
professionalNirosh29-Jan-14 19:47 
AnswerRe: How you extend this to support tracking only selected entity and selected properties of that entity?? Pin
Morshed Anwar30-Jan-14 5:40
memberMorshed Anwar30-Jan-14 5:40 
Questionentry.OriginalValues and CurrentValues are the same on audit Pin
Sean Brogan30-Sep-13 1:43
memberSean Brogan30-Sep-13 1:43 
AnswerRe: entry.OriginalValues and CurrentValues are the same on audit Pin
Morshed Anwar30-Sep-13 2:35
memberMorshed Anwar30-Sep-13 2:35 
GeneralRe: entry.OriginalValues and CurrentValues are the same on audit Pin
Sean Brogan30-Sep-13 3:46
memberSean Brogan30-Sep-13 3:46 
GeneralRe: entry.OriginalValues and CurrentValues are the same on audit Pin
Morshed Anwar30-Sep-13 5:21
memberMorshed Anwar30-Sep-13 5:21 
GeneralRe: entry.OriginalValues and CurrentValues are the same on audit Pin
Sean Brogan30-Sep-13 22:26
memberSean Brogan30-Sep-13 22:26 
GeneralRe: entry.OriginalValues and CurrentValues are the same on audit Pin
Morshed Anwar1-Oct-13 2:38
memberMorshed Anwar1-Oct-13 2:38 
QuestionXMLWriter not support base64 encoded data Pin
Member 102571856-Sep-13 4:09
memberMember 102571856-Sep-13 4:09 
AnswerRe: XMLWriter not support base64 encoded data Pin
Morshed Anwar6-Sep-13 5:42
memberMorshed Anwar6-Sep-13 5:42 
GeneralMy vote of 5 Pin
Аslam Iqbal23-Apr-13 21:58
memberАslam Iqbal23-Apr-13 21:58 
QuestionFor EntityState.Deleted not working Pin
Member 998096418-Apr-13 0:03
memberMember 998096418-Apr-13 0:03 
QuestionSaving changes event Pin
jportelas31-Dec-12 7:38
memberjportelas31-Dec-12 7:38 
AnswerRe: Saving changes event Pin
Morshed Anwar31-Dec-12 22:59
memberMorshed Anwar31-Dec-12 22:59 
GeneralMy vote of 5 Pin
Lee Keel21-Nov-12 8:45
memberLee Keel21-Nov-12 8:45 
GeneralRe: My vote of 5 Pin
Morshed Anwar21-Nov-12 12:07
memberMorshed Anwar21-Nov-12 12:07 
BugCloneEntity throwing Out-of-memory error Pin
Lee Keel19-Nov-12 15:09
memberLee Keel19-Nov-12 15:09 
GeneralRe: CloneEntity throwing Out-of-memory error Pin
Morshed Anwar19-Nov-12 23:29
memberMorshed Anwar19-Nov-12 23:29 
GeneralRe: CloneEntity throwing Out-of-memory error Pin
Lee Keel20-Nov-12 7:55
memberLee Keel20-Nov-12 7:55 
GeneralRe: CloneEntity throwing Out-of-memory error [modified] Pin
Morshed Anwar20-Nov-12 23:47
memberMorshed Anwar20-Nov-12 23:47 
GeneralRe: CloneEntity throwing Out-of-memory error Pin
Lee Keel21-Nov-12 8:43
memberLee Keel21-Nov-12 8:43 
GeneralRe: CloneEntity throwing Out-of-memory error Pin
Lee Keel21-Nov-12 12:01
memberLee Keel21-Nov-12 12:01 
GeneralRe: CloneEntity throwing Out-of-memory error Pin
Morshed Anwar21-Nov-12 12:11
memberMorshed Anwar21-Nov-12 12:11 
QuestionProblem with self-tracking entities Pin
Member 950081122-Oct-12 5:09
memberMember 950081122-Oct-12 5:09 
AnswerRe: Problem with self-tracking entities Pin
Morshed Anwar22-Oct-12 5:30
memberMorshed Anwar22-Oct-12 5:30 
From early look, I think you need to do some changes in your T4 template since it causing all columns to be updated -
Self-Tracking Entities: Original Values and Update Customization

Please let me know if it's not working for you.Thanks.
Md. Morshed Anwar | Senior Software Engineer
Adaptive Enterprise Limited
Blog: http://morshedanwar.wordpress.com/

GeneralRe: Problem with self-tracking entities Pin
Member 950081123-Oct-12 3:30
memberMember 950081123-Oct-12 3:30 
GeneralRe: Problem with self-tracking entities Pin
Morshed Anwar23-Oct-12 5:20
memberMorshed Anwar23-Oct-12 5:20 
QuestionRegarding master detail audit trail Pin
Tridip Bhattacharjee17-Oct-12 5:11
memberTridip Bhattacharjee17-Oct-12 5:11 
AnswerRe: Regarding master detail audit trail Pin
Morshed Anwar17-Oct-12 10:35
memberMorshed Anwar17-Oct-12 10:35 
GeneralRe: Regarding master detail audit trail Pin
Tridip Bhattacharjee17-Oct-12 21:10
memberTridip Bhattacharjee17-Oct-12 21:10 
SuggestionAudit trail for Entity Framework Code-first (DbContext) Pin
Morshed Anwar3-Oct-12 22:48
memberMorshed Anwar3-Oct-12 22:48 
QuestionGreat works, but it is not working with EntityDataSource. Pin
coderdest1-Aug-12 22:11
membercoderdest1-Aug-12 22:11 
AnswerRe: Great works, but it is not working with EntityDataSource. Pin
Morshed Anwar2-Aug-12 0:30
memberMorshed Anwar2-Aug-12 0:30 
QuestionDbContext Pin
mohammed927-Jul-12 0:30
membermohammed927-Jul-12 0:30 
GeneralRe: DbContext [modified] Pin
Morshed Anwar27-Jul-12 7:53
memberMorshed Anwar27-Jul-12 7:53 
AnswerRe: DbContext Pin
Morshed Anwar3-Oct-12 22:51
memberMorshed Anwar3-Oct-12 22:51 
GeneralMy vote of 5 Pin
Yugan_SA5-Jul-12 1:36
memberYugan_SA5-Jul-12 1:36 
QuestionOriginal Values not available for Self Tracking Entities [modified] Pin
Brett Dalldorf21-May-12 1:48
memberBrett Dalldorf21-May-12 1:48 
AnswerRe: Original Values not available for Self Tracking Entities Pin
Lipi Hardjono27-May-12 16:47
memberLipi Hardjono27-May-12 16:47 
GeneralRe: Original Values not available for Self Tracking Entities Pin
mark.deraeve13-Jun-12 21:20
membermark.deraeve13-Jun-12 21:20 
GeneralRe: Original Values not available for Self Tracking Entities Pin
Morshed Anwar23-Oct-12 5:31
memberMorshed Anwar23-Oct-12 5:31 
QuestionHow to preserve Old data in Audit Trail implementation via Entity Framework Pin
mittarah7-May-12 3:40
membermittarah7-May-12 3:40 
AnswerRe: How to preserve Old data in Audit Trail implementation via Entity Framework Pin
Morshed Anwar9-May-12 0:15
memberMorshed Anwar9-May-12 0:15 
SuggestionBrilliant Article! Just needs a bit of editing for a Context Template (See this post) Pin
Brett Dalldorf26-Apr-12 2:03
memberBrett Dalldorf26-Apr-12 2:03 
QuestionPrimary key still zero on added objects Pin
angelo carlt18-Nov-11 6:00
memberangelo carlt18-Nov-11 6:00 
AnswerRe: Primary key still zero on added objects Pin
mark.deraeve12-Apr-12 0:12
membermark.deraeve12-Apr-12 0:12 
GeneralMy vote of 5 Pin
Pravin Patil, Mumbai10-Nov-11 19:47
memberPravin Patil, Mumbai10-Nov-11 19:47 
QuestionExcellent solution for global level audtit trail but.... Pin
AnkurA28-Sep-11 10:21
memberAnkurA28-Sep-11 10:21 
QuestionExactly what we needed! one issue with "XMLSerializationHelper" Pin
tAz_mAniiAc6-Aug-11 0:07
membertAz_mAniiAc6-Aug-11 0:07 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.150520.1 | Last Updated 27 Mar 2009
Article Copyright 2009 by Morshed Anwar
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid