Entity Framework Core Creation and Update fields

asp.net-core c# entity-framework-core

Question

I have four custom fields on all of my entities. The four fields are CreatedBy, CreatedDate, UpdatedBy and UpdatedDate.

Is there a way to hook into Entity Framework core events so that on an insert it will populate the CreatedDate with the current DateTime and CreatedBy with the current user? When there is an update to the database it would populate UpdatedDate with the current DateTime and UpdatedBy with the current user?

1
7
4/10/2017 4:13:38 AM

Accepted Answer

Basically @Steve's approach is the way to go, but the current implementation of it makes it hard to unit test your project.

With a little bit of refactoring, you can make it unit test friendly and stay true to SOLID principles and encapsulation.

Here's a refactored version of Steve's example

public abstract class AuditableEntity
{
    public DateTime CreatedDate { get; set; }
    public string CreatedBy { get; set; }
    public DateTime UpdatedDate { get; set; }
    public string UpdatedBy { get; set; }
}

public class AuditableDbContext : DbContext
{
    protected readonly IUserService userService;
    protected readonly DbContextOptions options;
    protected readonly ITimeService timeService;

    public BaseDbContext(DbContextOptions options, IUserService userService, ITimeService timeService) : base(options)
    {
        userService = userService ?? throw new ArgumentNullException(nameof(userService));
        timeService = timeService ?? throw new ArgumentNullException(nameof(timeService));
    }

    public override int SaveChanges()
    {
        // get entries that are being Added or Updated
        var modifiedEntries = ChangeTracker.Entries()
                .Where(x => (x.State == EntityState.Added || x.State == EntityState.Modified));

        var identityName = userService.CurrentUser.Name;
        var now = timeService.CurrentTime;

        foreach (var entry in modifiedEntries)
        {
            var entity = entry.Entity as AuditableEntity;

            if (entry.State == EntityState.Added)
            {
                entity.CreatedBy = identityName ?? "unknown";
                entity.CreatedDate = now;
            }

            entity.UpdatedBy = identityName ?? "unknown";
            entity.UpdatedDate = now;
        }

        return base.SaveChanges();
    }
}

Now it's easy to mock time and user/principal for unit tests and model/domain/business layer is free of EF Core dependency, better encapsulating your domain logic way better.

Of course one could further refactor this to use a more modular approach by using strategy pattern, but that's out of scope. You can also use ASP.NET Core Boilerplate which also offers an implementation of an auditable (and soft delete) EF Core DbContext (here and here)

14
7/31/2019 6:57:45 PM

Popular Answer

I have exactly the same layout as you with what I call "Audit" fields.

The way I solved this was to create a base abstract class called AuditableEntity to hold the properties themselves and expose a method called PrepareSave. Inside PrepareSave I set the values of the fields as required:

public abstract class AuditableEntity
{
    public DateTime CreatedDate { get; set; }
    public string CreatedBy { get; set; }
    public DateTime UpdatedDate { get; set; }
    public string UpdatedBy { get; set; }

    public virtual void PrepareSave(EntityState state)
    {
        var identityName = Thread.CurrentPrincipal.Identity.Name;
        var now = DateTime.UtcNow;

        if (state == EntityState.Added)
        {
            CreatedBy = identityName ?? "unknown";
            CreatedDate = now;
        }

        UpdatedBy = identityName ?? "unknown";
        UpdatedDate = now;
    }
}

I made PrepareSave virtual so I can override it in my entities if I want. You may need to change how you get the Identity depending on your implementation.

To call this, I overwrote SaveChanges on my DbContext and called PrepareSave on each entity that was being added or updated (which I got from the change tracker):

public override int SaveChanges()
{
    // get entries that are being Added or Updated
    var modifiedEntries = ChangeTracker.Entries()
            .Where(x => x.State == EntityState.Added || x.State == EntityState.Modified);

    foreach (var entry in modifiedEntries)
    {
        // try and convert to an Auditable Entity
        var entity = entry.Entity as AuditableEntity;
        // call PrepareSave on the entity, telling it the state it is in
        entity?.PrepareSave(entry.State);
    }

    var result = base.SaveChanges();
    return result;
}

Now, whenever I call SaveChanges on my DbContext (either directly or through a repository), any entity that inherits AuditableEntity will have it's audit fields set as necessary.



Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow