Implementing Audit Log/Change History with MVC & Entity Framework

Implementing Audit Log / Change History with MVC & Entity Framework

IF you are using EF 4 you can subscribe to the SavingChanges event.

Since Entities is a partial class you can add additional functionality in a separate file. So create a new file named Entities and there implement the partial method OnContextCreated to hook up the event

public partial class Entities
{
partial void OnContextCreated()
{
SavingChanges += OnSavingChanges;
}

void OnSavingChanges(object sender, EventArgs e)
{

var modifiedEntities = ObjectStateManager.GetObjectStateEntries(EntityState.Modified);
foreach (var entry in modifiedEntities)
{
var modifiedProps = ObjectStateManager.GetObjectStateEntry(entry.EntityKey).GetModifiedProperties();
var currentValues = ObjectStateManager.GetObjectStateEntry(entry.EntityKey).CurrentValues;
foreach (var propName in modifiedProps)
{
var newValue = currentValues[propName];
//log changes
}
}
}
}

If you are using EF 4.1 you can go through this article to extract changes

Audit change log not recording not Primary key on Create

Your code doesn't work with database generated values. You need to read their values after saving entities to the database.

Original post (with more details): Entity Framework Core: History / Audit table

// https://www.meziantou.net/entity-framework-core-history-audit-table.htm
public class SampleContext : DbContext
{
public override async Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default(CancellationToken))
{
var auditEntries = OnBeforeSaveChanges();
var result = await base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
await OnAfterSaveChanges(auditEntries);
return result;
}

private List<AuditEntry> OnBeforeSaveChanges()
{
ChangeTracker.DetectChanges();
var auditEntries = new List<AuditEntry>();
foreach (var entry in ChangeTracker.Entries())
{
if (entry.Entity is Audit || entry.State == EntityState.Detached || entry.State == EntityState.Unchanged)
continue;

var auditEntry = new AuditEntry(entry);
auditEntry.TableName = entry.Metadata.GetTableName();
auditEntries.Add(auditEntry);

foreach (var property in entry.Properties)
{
if (property.IsTemporary)
{
// value will be generated by the database, get the value after saving
auditEntry.TemporaryProperties.Add(property);
continue;
}

string propertyName = property.Metadata.Name;
if (property.Metadata.IsPrimaryKey())
{
auditEntry.KeyValues[propertyName] = property.CurrentValue;
continue;
}

switch (entry.State)
{
case EntityState.Added:
auditEntry.NewValues[propertyName] = property.CurrentValue;
break;

case EntityState.Deleted:
auditEntry.OldValues[propertyName] = property.OriginalValue;
break;

case EntityState.Modified:
if (property.IsModified)
{
auditEntry.OldValues[propertyName] = property.OriginalValue;
auditEntry.NewValues[propertyName] = property.CurrentValue;
}
break;
}
}
}

foreach (var auditEntry in auditEntries.Where(_ => !_.HasTemporaryProperties))
{
Audits.Add(auditEntry.ToAudit());
}

return auditEntries.Where(_ => _.HasTemporaryProperties).ToList();
}

private Task OnAfterSaveChanges(List<AuditEntry> auditEntries)
{
if (auditEntries == null || auditEntries.Count == 0)
return Task.CompletedTask;

foreach (var auditEntry in auditEntries)
{
foreach (var prop in auditEntry.TemporaryProperties)
{
if (prop.Metadata.IsPrimaryKey())
{
auditEntry.KeyValues[prop.Metadata.Name] = prop.CurrentValue;
}
else
{
auditEntry.NewValues[prop.Metadata.Name] = prop.CurrentValue;
}
}

Audits.Add(auditEntry.ToAudit());
}

return SaveChangesAsync();
}
}

Audit Log in the database using Entity Farmework mvc

You are adding to the context. but you need to call the method that actually save them to the database. (SaveChanges method shown later).

..............

In general: you need to add your items to your DbSet<MyThing> MyThings on your myDbContext.

example:

myDbConext.AuditLogs.Add(theNewAuditLog);

ADD them all.........

AND then call one of the following:

myDbContext.SaveChanges();

or

myDbContext.SaveChangesAsync(token); /* token is a CancellationToken */

link:

https://learn.microsoft.com/en-us/dotnet/api/system.data.entity.dbcontext.savechangesasync?view=entity-framework-6.2.0

here is dotnet core tutorial:

https://entityframeworkcore.com/saving-data-savechangesasync

here is entity framework (dot net framework) tutorial:

https://www.entityframeworktutorial.net/entityframework6/async-query-and-save.aspx

APPEND:

You asked a different question about Edit not working.

this is my typical edit code.

public async Task<MyThing> UpdateAsync(MyThing inputItem, CancellationToken token)
{
int saveChangesAsyncValue = 0;
MyThing foundEntity = await this.entityDbContext.MyThings.FirstOrDefaultAsync(item => item.MySurrogateKey == inputItem.MySurrogateKey, token);
if (null != foundEntity)
{
foundEntity.MyStringPropertyOne = inputItem.MyStringPropertyOne;
foundEntity.MyStringPropertyTwo = inputItem.MyStringPropertyTwo;

this.entityDbContext.Entry(foundEntity).State = EntityState.Modified;

saveChangesAsyncValue = await this.entityDbContext.SaveChangesAsync(token);

/* an exception here would suggest another process changed the "context" but did not commit the changes (usually by SaveChanges() or SaveChangesAsync() */
if (1 != saveChangesAsyncValue)
{
throw new ArgumentOutOfRangeException(string.Format(ErrorMsgExpectedSaveChangesAsyncRowCount, saveChangesAsyncValue), (Exception)null);
}
}
else
{
ArgumentOutOfRangeException argEx = new ArgumentOutOfRangeException(string.Format(" SAD FACE {0} ", entity.MyThingKey), (Exception)null);
this.logger.LogError(argEx);
throw argEx;
}

return foundEntity;
}

Entity Framework 6: audit/track changes

If using EF6's DbContext you can use ChangeTracker in SaveChanges override to find added/modified entities of custom type, for example IAuditedEntity.

public interface IAuditedEntity {
string CreatedBy { get; set; }
DateTime CreatedAt { get; set; }
string LastModifiedBy { get; set; }
DateTime LastModifiedAt { get; set; }
}

public override int SaveChanges() {
var addedAuditedEntities = ChangeTracker.Entries<IAuditedEntity>()
.Where(p => p.State == EntityState.Added)
.Select(p => p.Entity);

var modifiedAuditedEntities = ChangeTracker.Entries<IAuditedEntity>()
.Where(p => p.State == EntityState.Modified)
.Select(p => p.Entity);

var now = DateTime.UtcNow;

foreach (var added in addedAuditedEntities) {
added.CreatedAt = now;
added.LastModifiedAt = now;
}

foreach (var modified in modifiedAuditedEntities) {
modified.LastModifiedAt = now;
}

return base.SaveChanges();
}


Related Topics



Leave a reply



Submit