EF Core how to implement audit log of changes to value objects
Asked Answered
K

1

10

I am using EF Core/.NET Core 2.1, and following DDD. I need to implement an audit log of all changes to my entities, and have done so using code from this blog post (relevant code from this post included below). This code works and tracks changes to any properties, however when it logs changes to my value objects, it only lists the new values, and no old values.

Some code:

public class Item
{
    protected Item(){}

    //truncated for brevity

    public Weight Weight { get; private set; }
}

public class Weight : ValueObject<Weight>
{
    public WeightUnit WeightUnit { get; private set; }
    public double WeightValue { get; private set; }

    protected Weight() { }

    public Weight(WeightUnit weightUnit, double weight)
    {
        this.WeightUnit = weightUnit;
        this.WeightValue = weight;
    }
}

and the audit tracking code from my context class

public class MyContext : DbContext
{
    //truncated for brevity

    public override int SaveChanges(bool acceptAllChangesOnSuccess)
    {
        var auditEntries = OnBeforeSaveChanges();
        var result = base.SaveChanges(acceptAllChangesOnSuccess);
        OnAfterSaveChanges(auditEntries);
        return result;
    }

    private List<AuditEntry> OnBeforeSaveChanges()
    {
        if (!this.AuditingAndEntityTimestampingEnabled)
        {
            return null;
        }

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

}

Here is a screenshot of how the audit changes persist to the database. The non-value object properties on Item have their old/new values listed, where changes to value objects only list the new values:

enter image description here

Is there a way to get the previous values of the value objects?

UPDATE:

So, the reason the OldValues column is null for changes to my value objects is due to the State of the value object being Added when it has been changed. I added a call to the IsOwned() method to the switch statement, and try to grab the property.OriginalValue within like this:

                        case EntityState.Added:

                        if (entry.Metadata.IsOwned())
                        {
                            auditEntry.OldValues[propertyName] = property.OriginalValue;
                        }
                        auditEntry.NewValues[propertyName] = property.CurrentValue;
                        break;

However, this simply logs the current value that the value object is being updated to.

enter image description here

So the question still stands - is there any way to get the previous value of a value object using the EF Core ChangeTracker, or do I need to re-think my use of DDD Value Objects due to my audit requirement?

Kone answered 16/7, 2018 at 19:40 Comment(6)
At a quick glance you'll need a recursive method on entry.Properties.Minnesinger
What would that recursive method do in order to get the previous values of the value objects?Kone
idk how EF tracks owned entity types. You probably need to ask on EF Core and provide a complete listing or project for them.Minnesinger
@Kone any idea on how you resolved this issue at that time? I am getting this issue #58299969Predator
@Predator I haven't yet resolved the value objects issue, however I added a possible answer to your issue in your question.Kone
Possible answers to the value object issue #60946996Globigerina
M
2

isstead of

auditEntry.OldValues[propertyName] = property.OriginalValue;

use

auditEntry.OldValues[propertyName] = entry.GetDatabaseValues().GetValue<object>(propertyName).ToString();
Myosotis answered 15/5, 2020 at 23:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.