Search code examples
c#entity-frameworkaudit.net

Using Audit.EntityFramework, how can I set the values of a foreign key to a property on the related entity?


Using Audit.EntityFramework

I'm trying to set the name of the related entity in the OriginalValue and NewValue properties of the EventEntryChange object (and possibly in the ColumnValues object as well). I have tried to use the Format and Override options like this (just simplified code to show it):

Audit.EntityFramework.Configuration.Setup()
    .ForContext<AppDbContext>(config => config
        .ForEntity<Product>(prodConfig =>
        {
            prodConfig.Format(x => x.ProductType, value =>
            {
                // Get name from related object
                var name = relatedObject?.Name;
                return name ?? value;
            });
        })
    );

In both cases, it will replace the OriginalValue with the updated value, but the NewValue and ColumnValues entry get reset to the foreign key ID. This is because of this code in DbContextHelper:

    /// <summary>
    /// Updates column values and primary keys on the Audit Event after the EF save operation completes.
    /// </summary>
    public void UpdateAuditEvent(EntityFrameworkEvent efEvent, IAuditDbContext context)
    {
        // Update PK and FK
        foreach (var efEntry in efEvent.Entries)
        {
            var entry = efEntry.Entry;
            efEntry.PrimaryKey = GetPrimaryKey(context.DbContext, entry);
            foreach (var pk in efEntry.PrimaryKey)
            {
                if (efEntry.ColumnValues.ContainsKey(pk.Key))
                {
                    efEntry.ColumnValues[pk.Key] = pk.Value;
                }
            }
            var fks = GetForeignKeys(context.DbContext, entry);
            foreach (var fk in fks)
            {
                if (efEntry.ColumnValues.ContainsKey(fk.Key))
                {
                    efEntry.ColumnValues[fk.Key] = fk.Value;
                }

                var change = efEntry.Changes?.FirstOrDefault(e => e.ColumnName == fk.Key);
                if (change != null)
                {
                    change.NewValue = fk.Value;
                }
            }
        }
        // Update ConnectionId
        var clientConnectionId = TryGetClientConnectionId(context.DbContext);
        if (clientConnectionId != null)
        {
            efEvent.ConnectionId = clientConnectionId;
        }
    }

In all honesty, I'm not really sure why it is doing this with the audit entry, but I'm sure there's a reason. However, I'm still stuck trying to figure out how I can get that logging to work, or even something similar (e.g. adding a separate change that holds the Name of the linked entity) that would fit my needs.


Solution

  • The UpdateAuditEvent method is needed in scenarios where the IDs are unknown until the entity is persisted, such as in the case of database-generated IDs for inserts. That's why the library tries to update the audit event IDs after saving the entities.

    An option could be to use a custom OnSaving action (which occurs right before saving the audit event) to update the properties.

    For example:

    Audit.Core.Configuration.AddOnSavingAction(scope =>
    {
        foreach (var entry in scope.GetEntityFrameworkEvent().Entries)
        {
            if (entry.EntityType == typeof(Product))
            {
                entry.ColumnValues["ProductType"] = relatedObject.Name;
                foreach (var change in entry.Changes)
                {
                    if (change.ColumnName == nameof(Product.ProductType))
                    {
                        change.OriginalValue = relatedObject.Name;
                        change.NewValue = ...;
                    }
                }
            }
        }
    });