Search code examples
c#.net-coreentity-framework-coreaudit-tables

EF2.2 Core 2.2 Auditing returns same old and new value how to fix?


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();
        }
    }

Solution

  • 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.