Implementing Common Audit Fields With EF Core’s Shadow Property

One of the coolest features of EF Core is, that you can define properties that don’t exist in the Entity class. They are called shadow properties. While working on EF6, I needed some common audit fields that every entity would 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 was create an interface with those common fields and implement that interface in required entities. Then we can 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 implementing 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 don’t specify it, for datetime column types, ef core will set a default datetime string which sometimes can be confusing.

Shadow properties are not directly part of the concrete entities. So, accessing them and working with their values is pretty 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.


Similar Articles