I am trying to implement an Audit table in my .net core (2.2) using EF 2.2 code first. My intention is to log all changes made in my entities I was following this blog post to implement. Everything seems fine but I am getting same old and new value.
Please help me to find out my mistake.
here my controller, I am sharing only a piece of it
public class ProductController : Controller
{
private readonly MyDBContext _context;
private readonly IProduct productRepository;
public ProductController(MyECODBContext context,IProduct product)
{
_context = context;
productRepository = product;
}
//My Edit function
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("//my other bindings")] ECOViewModel vmECO,
string[] ApproversList, string[] ValidatorsList)
{ //POST method of the Edit Page
////My other logic
_context.Update(existingProduct);//updating product table
await _context.SaveChangesAsync(true);
}
}
and the audit tracking code from my context class[This code is from that blog post] In switch case at case EntityState.Modified:I am getting same value for property.OriginalValue and property.CurrentValue;
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.Relational().TableName;
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;
}
}
}
// Save audit entities that have all the modifications
foreach (var auditEntry in auditEntries.Where(_ => !_.HasTemporaryProperties))
{
Audits.Add(auditEntry.ToAudit());
}
// keep a list of entries where the value of some properties are unknown at this step
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)
{
// Get the final value of the temporary properties
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;
}
}
// Save the Audit entry
Audits.Add(auditEntry.ToAudit());
}
return SaveChangesAsync();
}
}
This is a known issue, and it is even mentioned right in the comments section of the post. For future reference, it seems that the following has worked for some people
Thank you for your post! I've tried to use it and would like to suggest improvement how to detect updates. On Core EF 2.2 EntityState.Modified is true even if field was not modified, so I added some code:
The state looks modified even when it's not, so this way you can filter out not true cases.
case EntityState.Modified:
if (property.IsModified)
{
if(property.OriginalValue == null && property.CurrentValue == null)
continue;
if(property.OriginalValue == null ||
property.CurrentValue == null ||
!property.OriginalValue.Equals(property.CurrentValue))
{
auditEntry.OldValues[propertyName] = property.OriginalValue;
auditEntry.NewValues[propertyName] = property.CurrentValue;
}
It looks like from this issue here, that you are going to need to make an extra call to the database to succeed in what you are trying to achieve.