title: EF Core - how to audit trail with value objects
body: <p>I'm trying to implement an Audit Trail (track what changed, when and by whom) on a selection of classes in Entity Framework Core.</p>
  
  <p>My current implementation relies on overriding OnSaveChangesAsync:</p>
  
  <pre><code>public override Task&lt;int&gt; SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default) 
  {
      var currentUserFullName = _userService.CurrentUserFullName!;
  
      foreach (var entry in ChangeTracker.Entries&lt;AuditableEntity&gt;())  
      {
          switch (entry.State) 
          {
              case EntityState.Added:
                      entry.Entity.CreatedBy = currentUserFullName;
                      entry.Entity.Created = _dateTime.Now;
                      break;
  
              case EntityState.Modified:
                      var originalValues = new Dictionary&lt;string, object?&gt;();
                      var currentValues = new Dictionary&lt;string, object?&gt;();
  
                      foreach (var prop in entry.Properties.Where(p =&gt; p.IsModified)) 
                      {
                          var name = prop.Metadata.Name;
                          originalValues.Add(name, prop.OriginalValue);
                          currentValues.Add(name, prop.CurrentValue);
                      }
                      entry.Entity.LastModifiedBy = currentUserFullName;
                      entry.Entity.LastModified = _dateTime.Now;
  
                      entry.Entity.LogEntries.Add(
                          new EntityEvent(
                              _dateTime.Now,
                              JsonConvert.SerializeObject(originalValues),
                              JsonConvert.SerializeObject(currentValues),
                              currentUserFullName));
                      break;
              }
          }
  
          return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
  }
  </code></pre>
  
  <p>This is simple, clean and very easy to use; any entity that needs an audit trail only needs to inherit <code>AuditableEntity</code>.</p>
  
  <p>However, there is a severe limitation to this approach: it cannot capture changes made to navigation properties.</p>
  
  <p>Our entities make good use of Value Objects, such as an EmailAddress:</p>
  
  <pre><code>public class EmailAddress : ValueObjectBase
  {
      public string Value { get; private set; } = null!;
  
      public static EmailAddress Create(string value) 
      {
          if (!IsValidEmail(value)) 
          {
              throw new ArgumentException("Incorrect email address format", nameof(value));
          }
  
          return new EmailAddress {
              Value = value
          };
      }
  }
  
  ... Entity.cs
  
  public EmailAddress Email { get; set; }   
  
  ... Entity EF configuration
  entity.OwnsOne(e =&gt; e.Email);
  
  ... Updating
  entity.Email = EmailAddress.Create("e@mail.com");
  </code></pre>
  
  <p>Now if the user changes an email address of this entity, the State of the Entity is never changed to modified. EF Core seems to handle ValueObjects as Navigation Properties, which are handled separately.</p>
  
  <p>So I think there are a few options:</p>
  
  <ol>
  <li><p>Stop using ValueObjects as entity properties. We could still utilize them as entity constructor parameters, but this would cause cascading complexity to the rest of the code. It would also diminish the trust in the validity of the data.</p></li>
  <li><p>Stop using SaveChangesAsync and build our own handling for auditing. Again, this would cause further complexity in the architecture and probably be less performant.</p></li>
  <li><p>Some weird hackery to the ChangeTracker - this sounds risky but might work in theory</p></li>
  <li><p>Something else, what?</p></li>
  </ol>