How to get Changes for nested collections/entities #748
-
|
Hi, Thank you for your work on this library. It's a great one ! And we look forward to use it in our next application. But we have some requirements that I'm not able to meet at the moment. I'm wondering if it's only my fault or if it's just not possible with the library. Our backend is in .Net Core 8 and we use EF Core with a SQL Server database. Our main problem/requirement is that we have some entities with some nested collections we want to audit as one piece. So for exemple users and their roles : And our audit tables look like : The The problem is that the roles array is not included in the Is there a way to get both the old and new values of all members of my Other questions :
Here is a small repo with my entities/configuration if needed : Simply run the following commands in your package manager console to create the local database : Example of payload : Thank you for your time |
Beta Was this translation helpful? Give feedback.
Replies: 4 comments
-
|
The issue with the New and Old values being identical in the Changes collection is caused by the use of the I'll try to respond to the remaining questions shortly. |
Beta Was this translation helpful? Give feedback.
-
|
Note that the Since you are mapping only the User table using However, the AuditEvent (the first parameter in To ensure that changes to a user's role are captured, you will need to mark the User entity as modified when a role change occurs. While you might have used Ultimately, the implementation will depend on the functionality available in your API. If your POST and PUT methods are designed to insert or update a single user, you should be able to accomplish this with some adjustments to your controller logic and the logic within your Attaching a modified version, note the changes in Also, note you were calling |
Beta Was this translation helpful? Give feedback.
-
|
Note that if you're reusing the same var tran = dbContext.Database.BeginTransaction();
dbContext.SaveChanges();
tran.Commit();Another alternative is to use the Generic DbContext Data Provider. However, you'll still need custom logic to create the entities according to your requirements. There is no built-in mechanism that will do this for you. Audit.EntityFramework.Configuration.Setup()
.ForAnyContext(cfg => cfg
.ReloadDatabaseValues()
.IncludeEntityObjects())
.UseOptIn()
.Include<User>()
.Include<Role>();
Audit.Core.Configuration.Setup()
.UseDbContext<ApplicationDbContext, AuditHeader>(db => db
.DbContextBuilder(ev => (ApplicationDbContext)ev.GetEntityFrameworkEvent().GetDbContext()) // <-- Use the audited DbContext
.Mapper((auditEvent, auditHeader) =>
{
var efEvent = auditEvent.GetEntityFrameworkEvent();
var userEntry = efEvent.Entries.Find(e => e.Entity is User);
auditHeader.ResourceName = userEntry.Entity.GetType().Name;
auditHeader.ActionType = userEntry.Action;
auditHeader.ActionTime = DateTime.UtcNow;
auditHeader.ResourceId = (long)((dynamic)userEntry.Entity).Id;
var rolesEntries = efEvent.Entries.FindAll(e => e.Entity is Role);
var details = new List<AuditDetail>();
foreach (var roleEntry in rolesEntries)
{
if (roleEntry.Action == "Insert")
{
details.Add(new()
{
AttributeName = "Role.Title",
AttributeType = "Role",
OldValue = string.Empty,
NewValue = roleEntry.ColumnValues["Title"].ToString()
});
}
else if (roleEntry.Action == "Delete")
{
details.Add(new()
{
AttributeName = "Role.Title",
AttributeType = "Role",
OldValue = roleEntry.ColumnValues["Title"]?.ToString() ?? string.Empty,
NewValue = string.Empty
});
}
else if (roleEntry.Action == "Update" && roleEntry.Changes.Exists(ch => ch.ColumnName == "Title" && (string)ch.OriginalValue != (string)ch.NewValue))
{
details.Add(new()
{
AttributeName = "Role.Title",
AttributeType = "Role",
OldValue = roleEntry.Changes.Find(ch => ch.ColumnName == "Title")?.OriginalValue.ToString() ?? string.Empty,
NewValue = roleEntry.Changes.Find(ch => ch.ColumnName == "Title")?.NewValue.ToString() ?? string.Empty
});
}
}
auditHeader.Details = details;
})); |
Beta Was this translation helpful? Give feedback.
-
|
Thank you very much for all those quick and detailed answers. It's very appreciated. Can't have better than that :) I'll continue to look into our audit implementation in the following days. I'll keep you in touch if I have any other question, but so far so good. |
Beta Was this translation helpful? Give feedback.
Note that the
EventEntryclass (the second parameter inAuditEntityAction) represents a single entry from the internal EF change tracker, with each entry corresponding to one and only one entity. As a result, theAuditEntityActionis executed once for each changed mapped entry.Since you are mapping only the User table using
.Map<User, AuditHeader>(), only changes to User entities will be captured.However, the AuditEvent (the first parameter in
AuditEntityAction) contains all the changes from the EF change tracker for a singleSaveChangescall. This means it is theoretically possible to detect changes in the Roles collection for a User within yourAuditEntityAction.To ensure that change…