Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Entity Framework Code First Common Database Audit Fields

I'm new to Entity Framework, in the past I've used Enterprise Library or ADO.NET directly to map models to database tables. One pattern that I've used is to put my common audit fields that appear in every table in a Base Class and then inherit that Base Class for every object.

I take two steps to protect two of the fields (Created, CreatedBy):

  1. Have a parameterless constructor private on the Base Enitity and create a second one that requires Created and CreatedBy are passed on creation.
  2. Make the setters Private so that the values cannot be changed after the object is created.

Base Class:

using System;

namespace App.Model
{
    [Serializable()]
    public abstract class BaseEntity
    {
        public bool IsActive { get; private set; }
        public DateTimeOffset Created { get; private set; }
        public string CreatedBy { get; private set; }
        public DateTimeOffset LastUpdated { get; protected set; }
        public string LastUpdatedBy { get; protected set; }

        private BaseEntity() { }

        protected BaseEntity(DateTimeOffset created, string createdBy)
        {
            IsActive = true;
            Created = created;
            CreatedBy = createdBy;
            LastUpdated = created;
            LastUpdatedBy = createdBy;
        }
    }
}

Inherited Class:

using System;

namespace App.Model
{
    [Serializable()]
    public class Person : BaseEntity
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Email { get; set; }

        public Person(DateTimeOffset created, string createdBy) : 
            base(created, createdBy) {  }
    }
}

I've run into issues with both. EF requires a parameterless constructor to create objects. EF will not create the database columns that have a private setter.

My question is if there is a better approach to accomplish my goals with EF:

  1. Require that the values for Created and CreatedBy are populated at instantiation.
  2. The values of Created and CreatedBy cannot be changed.
like image 647
Josh Avatar asked May 24 '13 11:05

Josh


1 Answers

You could instantiate a context with a constructor that accepts a string createdBy. Then in an override of SaveChanges():

public override int SaveChanges()
{
    foreach( var entity in ChangeTracker.Entries()
                                        .Where(e => e.State == EntityState.Added)
                                        .Select (e => e.Entity)
                                        .OfType<BaseEntity>())
    {
        entity.SetAuditValues(DateTimeOffset.Now, this.CreatedBy);
    }
    return base.SaveChanges();
}

With SetAuditValues() as

internal void SetAuditValues(DateTimeOffset created, string createdBy)
{
    if (this.Created == DateTimeOffset.MinValue) this.Created = created;
    if (string.IsNullOrEmpty(this.CreatedBy)) this.CreatedBy = createdBy;
}

After the entities have been materialized from the database the values won't be overwritten when someone calls SetAuditValues.

like image 56
Gert Arnold Avatar answered Oct 15 '22 10:10

Gert Arnold