Implementing Common Audit Fields with EF Core’s Shadow Property

One of the coolest feature of EF Core is, you can define properties that don’t exists in the Entity class. They are called shadow properties. While working on EF6, I needed some common audit fields that every entity will extend. Like, when a row of a table is updated, when a row is created, what is the row version etc. Since there was no concept of shadow properties in EF6, what I and many other developers did is create an interface with those common fields and implement that interface in required entities. Then add or update the values of these fields through change tracking. Like this,

public interface IAuditable
{
    DateTime Created { get; set; }
    DateTime Modified { get; set; }
}

Implement the interface where it is needed,

public class Person : IAuditable
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Address { get; set; }

    public DateTime Created { get; set; }
    public DateTime Modified { get; set; }
}

Update values of the audit fields through entity change tracking,

public override int SaveChanges()
{
    foreach (var auditableEntity in ChangeTracker.Entries<IAuditable>())
    {
        if (auditableEntity.State == EntityState.Added ||
            auditableEntity.State == EntityState.Modified)
        {
            auditableEntity.Entity.Modified = DateTime.Now;

            if (auditableEntity.State == EntityState.Added)
            {
                auditableEntity.Entity.Created = DateTime.Now;
            }
        }
    }
    return base.SaveChanges();
}

Instead of creating an interface and implement it in every entity we can also add the audit fields as shadow fields. It’s just another way, not a preferred way. So be very choosy when you are designing your entities.

protected override void OnModelCreating(ModelBuilder builder)
{
    base.OnModelCreating(builder);

    builder.Entity<Person>().Property<DateTime?>("Created");
    builder.Entity<Person>().Property<DateTime?>("Modified");
}

Here, the question mark after the data types means that there can be a null value for that mapped column type in the database. If you dont specify it, for datetime column types, ef core will set a default datetime string which sometimes can be confusing.

Since, shadow properties are not directly part of the concreate entities. So, accessing them and working with their values is pretty much different from the usual way.

public override int SaveChanges()
{
    var modifiedEntries = ChangeTracker.Entries()
        .Where(e => e.State == EntityState.Added || e.State == EntityState.Modified);

    foreach (EntityEntry entry in modifiedEntries)
    {
        var entityType = entry.Context.Model.FindEntityType(entry.Entity.GetType());

        var modifiedProperty = entityType.FindProperty("Modified");
        var createdProperty = entityType.FindProperty("Created");

        if (entry.State == EntityState.Modified && modifiedProperty != null)
        {
            entry.Property("Modified").CurrentValue = DateTime.Now;
        }

        if (entry.State == EntityState.Added && createdProperty != null)
        {
            entry.Property("Created").CurrentValue = DateTime.Now;
        }
    }

    return base.SaveChanges();
}

And that’s it! This is how you can use shadow properties for implementing audit fields for your entities in a different kind of way. But it is up to you whether you want to use them or not.