Click here to Skip to main content
15,881,581 members
Articles / .NET

Making Entity Framework Audit Itself

Rate me:
Please Sign up or sign in to vote.
5.00/5 (13 votes)
26 Feb 2017CPOL4 min read 12K   14   13
How to make Entity Framework audit itself

I wanted to throw an update out here. I've been doing a lot of different things as of late, some development related, some not so much. But needless to say, life has been busy. I have been working on a bunch of personal projects, and I wanted to throw some items out here related to that.

Firstly, I wanted to make sure I got an update out here that I will be speaking at the Central PA .NET User Group in Harrisburg PA on 3/21. Topic has yet to be announced, but more to come on that later.

Secondly, I wanted to throw some of the smaller pieces of code that I use to make my life easier out here. Let's face it, we all have written code that was specifically tailored to make our lives easier, and we all use those building blocks like the legos designed to get us moving faster with our applications. For many of these, I'm trying to package them up and will be releasing them publicly as nuget packages. I figure if they've helped me, maybe they can do the same for you.

So in that direction, the first one I wanted to tackle isn't exactly anything spectacular, but it does make life a little simpler. Many of us leverage Entity Framework, and honestly it is one of the most powerful, and equally misunderstood technologies out there for Data Access. But one of the things that always bugged me wasn't so much a problem with entity framework, but a problem with all data access.

For this post, it's a little thing. Specifically, what I call Audit Fields. Almost all of us use these in our databases. Fields like the following:

  • EffectiveDate: Specifically to track when a record becomes active and visible within an application
  • EndDate: Specifically a date and time the record becomes inactive or invisible within the application
  • DateAdded: The date the record was created
  • AddedBy: The person who created the record
  • DateModified: The date the record was last modified
  • ModifiedBy: The person who last modified the record

These fields are the kinds of things most of us developers do, and if you are like me, you always forget about these fields and end up troubleshooting null values and issues related to forgetting about them, and wasting a lot of time.

Another common issue is that of the primary key. I'll be honest with you, I do a lot of mobile development work, and if you are looking at mobile. Guids really are the only primary key you should be using. Synchronizing an integer is a painful process. But if you use Guids, the key is set at the application level, which is another thing to remember. Or another thing to forget.

Not to mention, that these fields really are a data requirement, so it always would "grind my gears" that I was handling them at a business layer level. So I modified my logic to use the following solution to resolve the issue.

Firstly, I implemented a new class in my applications called "BaseModel", which is shown below:

C#
public class BaseDataModel
   {
       [Key]
       public Guid PrimaryKey { get; set; }

       [Required]
       [DisplayName("Effective Date")]
       public DateTime EffectiveDate { get; set; }

       [DisplayName("End Date")]
       public DateTime? EndDate { get; set; }

       [Required]
       [DisplayName("Date Added")]
       public DateTime DateAdded { get; set; }

       [Required]
       [DisplayName("Added By")]
       public string AddedBy { get; set; }

       [DisplayName("Date Modified")]
       public DateTime? DateModified { get; set; }

       [DisplayName("Modified By")]
       public string ModifiedBy { get; set; }
   }

And I would add the following interface also to your code:

C#
public interface IEntity  
    {
        Guid PrimaryKey { get; set; }

        DateTime EffectiveDate { get; set; }

        DateTime? EndDate { get; set; }

        DateTime DateAdded { get; set; }

        string AddedBy { get; set; }

        DateTime? DateModified { get; set; }

        string ModifiedBy { get; set; }
    }

This provides a nice reusable template for all my models moving forward. For each model from then forward, they would look like the following. Below is a sample of a common model I use for application configuration.

C#
public class ConfigValue : BaseModel, IEntity  
    {
        [Required]
        public string ConfigKey { get; set; }
        [Required]
        public string ConfigValue { get; set; }
    }

This will ensure that all your models maintain the same structure and your database will follow suit.

Finally, I recommend the following modification to your entity framework. You can override the SaveChanges method to add custom logic to handle these audit fields and remove any extra manual effort on your part for these basic data operations.

C#
public override int SaveChanges()  
        {
            ChangeTracker.DetectChanges();

            var auditable = ChangeTracker.Entries<IEntity>().ToList();

            if (!auditable.Any()) return base.SaveChanges();

            foreach (var record in auditable)
            {
                var userIdentity = //Whatever class you leverage to get the current username

                switch (record.State)
                {
                    case System.Data.Entity.EntityState.Added:
                        if (record.Entity.Key == Guid.Empty)
                        {
                            record.Entity.Key = Guid.NewGuid();
                        }
                        record.Entity.DateAdded = DateTime.Now;
                        record.Entity.AddedBy = userIdentity.UserName;
                        if (record.Entity.EffectiveDate == DateTime.MinValue)
                        {
                            record.Entity.EffectiveDate = DateTime.Now;
                        }
                        break;
                    case System.Data.Entity.EntityState.Modified:
                        if (String.IsNullOrEmpty(record.Entity.AddedBy))
                        {
                            record.Entity.DateAdded = DateTime.Now;
                            record.Entity.AddedBy = userIdentity.UserName;
                        }

                        record.Entity.DateModified = DateTime.Now;
                        record.Entity.ModifiedBy = userIdentity.UserName;
                        break;
                }
            }

    return base.SaveChanges();
        }

The above code then makes it possible that every time "SaveChanges" is called on your data context. It will automatically scan the records coming in to see if any of them implement the IEntity interface, and if so handle the population of these fields long before it ever gets saved.

The biggest benefits of this approach being:

  • Increases consistency of the data model
  • Keeps data related operations out of the business layer
  • Protects yourself from forgetting to perform these operations
  • Ensures the audit fields are implemented in a consistent fashion

That's all for now, more to come but I wanted to share something that's helped me, and I hope it helps you too.

License

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


Written By
Software Developer (Senior)
United States United States
My name is Kevin Mack, I'm a software developer in the Harrisburg Area. I have been a software developer since 2005, and in that time have worked on a large variety of projects. Everything from small applications, to mobile and Enterprise solutions. I love technology and enjoy my work and am always looking to learn something new. In my spare time I love spending time with my family, and learning new ways to leverage technology to make people's lives better. If you ask me what I do, I'll probably tell you I can paid to solve problems all-day-every-day.

Check out my blog at https://kmack.azurewebsites.net/ and https://totalalm.azurewebsites.net/

Comments and Discussions

 
QuestionGUIDs vs. Integers Pin
Jeff Bowman6-Mar-17 22:29
professionalJeff Bowman6-Mar-17 22:29 
AnswerRe: GUIDs vs. Integers Pin
Kevin Mack8-Mar-17 4:29
Kevin Mack8-Mar-17 4:29 
Absolutely, for a lot of applications, I've found that the use of Guids for your primary keys is a much more flexible option as it makes the application that primary source of the keys rather than the database. This allows for two potential benefits:

1.) if you are dealing with a mobile database or a sync scenario, using Integers is almost impossible to reliably sync entries.

2.) Provides a method of creating keys for creating child records without the parent having to be in the database first. The scenario being this, if I create say an expense report, and want to start adding expenses. I would have to create the expense report in the database first. And then if the user cancels I would have to clean up the report record. For this option I can create a key, start defining child records and throw it all away if I want before saving, no clean up required. The use of the adding the guid in the "SaveChanges" is for the 90% scenario of not having to create the key within the application, and making sure a new key is available.

GeneralRe: GUIDs vs. Integers Pin
Jeff Bowman8-Mar-17 8:55
professionalJeff Bowman8-Mar-17 8:55 
GeneralRe: GUIDs vs. Integers Pin
Kevin Mack8-Mar-17 9:27
Kevin Mack8-Mar-17 9:27 
GeneralRe: GUIDs vs. Integers Pin
Jeff Bowman8-Mar-17 9:31
professionalJeff Bowman8-Mar-17 9:31 
GeneralRe: GUIDs vs. Integers Pin
Kevin Mack9-Mar-17 9:05
Kevin Mack9-Mar-17 9:05 
GeneralRe: GUIDs vs. Integers Pin
Jeff Bowman9-Mar-17 12:53
professionalJeff Bowman9-Mar-17 12:53 
GeneralRe: GUIDs vs. Integers Pin
Kevin Mack10-Mar-17 2:18
Kevin Mack10-Mar-17 2:18 
GeneralRe: GUIDs vs. Integers Pin
Jeff Bowman10-Mar-17 7:54
professionalJeff Bowman10-Mar-17 7:54 
BugCorrection ? Pin
Dave Brewster5-Mar-17 14:18
Dave Brewster5-Mar-17 14:18 
GeneralRe: Correction ? Pin
Kevin Mack5-Mar-17 17:10
Kevin Mack5-Mar-17 17:10 
QuestionSome doubts Pin
Pascualito2-Mar-17 6:30
professionalPascualito2-Mar-17 6:30 
AnswerRe: Some doubts Pin
Kevin Mack5-Mar-17 17:12
Kevin Mack5-Mar-17 17:12 

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

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