Click here to Skip to main content
15,882,017 members
Articles / Programming Languages / C#
Tip/Trick

Implementing Common Audit Fields with EF Core’s Shadow Property

Rate me:
Please Sign up or sign in to vote.
4.00/5 (1 vote)
22 Mar 2017CPOL1 min read 10.9K   2   2
Know how to use Entity Framework's new shadow property feature for easily implementing common audit fields for entities

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 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:

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

Implement the interface where it is needed:

C#
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:

C#
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.

C#
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.

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

C#
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.

License

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


Written By
Architect Geek Hour
Bangladesh Bangladesh
Tech Enthusiast | Contributing Author on Microsoft Docs | Github Country Leader (C# and Typescript)

A .NET and JavaScript enthusiast. Likes to work with different technologies on different platforms. Loves the logic and structure of coding and always strives to write more elegant and efficient code. Passionate about design patterns and applying Software Engineering best practices.

I'm a young coder who did exceedingly well in my education and was offered an internship by Microsoft. I've tried a range of things and realized that what I need is a super creative challenge. I'm hungry for a real role in a challenging startup, something I can stick with for years

Comments and Discussions

 
QuestionI'd rather use viewmodels to hide audit properties Pin
Brady Kelly27-Mar-17 0:16
Brady Kelly27-Mar-17 0:16 
Thank you for a an interesting and well written article, but I differ on keeping such audit properties 'hidden' from the data model.

I prefer my data models and their config to reflect their corresponding data tables (or whatever other store) as accurately as possible.

Then, in situations where I'd prefer a class without those properties visible or accessible, I'd create a 'copy', but without them, and use something like AutoMapper[^] to copy data from one to the other. in cases like your example this only adds a minimal performance cost.
Immanentize the Eschaton!

AnswerRe: I'd rather use viewmodels to hide audit properties Pin
Fiyaz Hasan27-Mar-17 5:45
professionalFiyaz Hasan27-Mar-17 5:45 

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.