Entity Framework 7 Audit Log

I am migrating an old project to ASP.NET 5 and Entity Framework 7. I used the basic database approach (DNX rendering) to create the model.

The old project is based on Entity Framework 4, and audit tracking is implemented by overriding the SaveChanges DbContext method:

 public override int SaveChanges(System.Data.Objects.SaveOptions options) { int? UserId = null; if (System.Web.HttpContext.Current != null) UserId = (from user in Users.Where(u => u.UserName == System.Web.HttpContext.Current.User.Identity.Name) select user.Id).SingleOrDefault(); foreach (ObjectStateEntry entry in ObjectStateManager.GetObjectStateEntries(EntityState.Added | EntityState.Modified)) { Type EntityType = entry.Entity.GetType(); PropertyInfo pCreated = EntityType.GetProperty("Created"); PropertyInfo pCreatedById = EntityType.GetProperty("CreatedById"); PropertyInfo pModified = EntityType.GetProperty("Modified"); PropertyInfo pModifiedById = EntityType.GetProperty("ModifiedById"); if (entry.State == EntityState.Added) { if (pCreated != null) pCreated.SetValue(entry.Entity, DateTime.Now, new object[0]); if (pCreatedById != null && UserId != null) pCreatedById.SetValue(entry.Entity, UserId, new object[0]); } if (pModified != null) pModified.SetValue(entry.Entity, DateTime.Now, new object[0]); if (pModifiedById != null && UserId != null) pModifiedById.SetValue(entry.Entity, UserId, new object[0]); } } return base.SaveChanges(options); } 

My question is, how can I implement this in Entity Framework 7? Should I use the first code approach?

+6
source share
2 answers

Basically, you have two ways to achieve this:

Using ChangeTracker API (EF 6+):

So we are currently doing this in EF 6, and it is still valid and works for EF 7:

First you need to make sure that your objects implement a common interface for validation fields:

 public interface IAuditableEntity { int? CreatedById { get; set; } DateTime Created { get; set; } int? ModifiedById { get; set; } DateTime Modified { get; set; } } 


You can then override SaveChanges and update each common field using audit values:

 public override int SaveChanges() { int? userId = null; if (System.Web.HttpContext.Current != null) userId = (from user in Users.Where(u => u.UserName == System.Web.HttpContext.Current.User.Identity.Name) select user.Id).SingleOrDefault(); var modifiedEntries = ChangeTracker.Entries<IAuditableEntity>() .Where(e => e.State == EntityState.Added || e.State == EntityState.Modified); foreach (EntityEntry<IAuditableEntity> entry in modifiedEntries) { entry.Entity.ModifiedById = UserId; entry.Entity.Modified = DateTime.Now; if (entry.State == EntityState.Added) { entry.Entity.CreatedById = UserId; entry.Entity.Created = DateTime.Now; } } return base.SaveChanges(); } 


Using EF 7's new Shadow Properties:

Shadow properties are properties that do not exist in your entity class. The value and state of these properties is supported exclusively in Track Track.

In other words, audit columns will not appear on your objects, which appear to be a better option, comparable to the one above, where you should include them in your objects.

To implement shadow properties, you must first configure them on your objects. Say, for example, you have a User object that must have several audit columns:

 protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<User>().Property<int>("CreatedById"); modelBuilder.Entity<User>().Property<DateTime>("Created"); modelBuilder.Entity<User>().Property<int>("ModifiedById"); modelBuilder.Entity<User>().Property<DateTime>("Modified"); } 


After setting up, now you can access them on SaveChanges () and update their values ​​accordingly:

 public override int SaveChanges() { int? userId = null; if (System.Web.HttpContext.Current != null) userId = (from user in Users.Where(u => u.UserName == System.Web.HttpContext.Current.User.Identity.Name) select user.Id).SingleOrDefault(); var modifiedBidEntries = ChangeTracker.Entries<User>() .Where(e => e.State == EntityState.Added || e.State == EntityState.Modified); foreach (EntityEntry<User> entry in modifiedBidEntries) { entry.Property("Modified").CurrentValue = DateTime.UtcNow; entry.Property("ModifiedById").CurrentValue = userId; if (entry.State == EntityState.Added) { entry.Property("Created").CurrentValue = DateTime.UtcNow; entry.Property("CreatedById").CurrentValue = userId; } } return base.SaveChanges(); } 


Final thoughts:

To implement something like audit columns, I will take the Shadow Properties approach, because these are problems with cross-sections and do not necessarily belong to my domain objects, so if they are implemented this way, my domain objects will be nice and clean.

+5
source

I was working on a library that could help.

Take a look at the Audit.EntityFramework library, it hooks SaveChanges() and is compatible with EF Core versions.

0
source

All Articles