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:
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.
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?
entry.Properties
. – Minnesinger