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
C# Create/Modify/Read .Xlsx Files
In Mvvmcross How to Do Custom Bind Properties
How to Urlencode Without Using System.Web
How to Tell When Httpclient Has Timed Out
How to Capture Screen to Be Video Using C# .Net
How to Add a Custom Routed Command in Wpf
How to Get Urls of Open Pages from Chrome and Firefox
How to Compare Only Date Components from Datetime in Ef
Does Distinct() Method Keep Original Ordering of Sequence Intact
How to Share Data Between Forms
How to Fix Error: "Could Not Find Schema Information for the Attribute/Element" by Creating Schema
Why Does Path.Combine Not Properly Concatenate Filenames That Start with Path.Directoryseparatorchar
How to Pass a Username/Password in the Header to a Soap Wcf Service
How to Enumerate Through a Jobject
Adding Unknown (At Design Time) Properties to an Expandoobject