Closed VictorioBerra closed 5 years ago
I am using this library https://github.com/stevejgordon/CorrelationId so I went ahead and did something like this:
Audit.Core.Configuration.AddCustomAction(ActionType.OnScopeCreated, scope =>
{
var svcProvider = services.BuildServiceProvider();
var currentUserAccessor = svcProvider.GetRequiredService<ICurrentUserAccessor>();
var currentCorrelationAccessor = svcProvider.GetRequiredService<ICorrelationContextAccessor>();
scope.SetCustomField(Domain.Constants.Auditing.IdentityName, currentUserAccessor.GetCurrentUsername() ?? "Anonymous");
scope.SetCustomField(Domain.Constants.Auditing.CorrelationId, currentCorrelationAccessor.CorrelationContext.CorrelationId);
});
Now I know all specific auditing records happened during the same request. So I can gather up all audit rows with that same correlationId.
This still feels brittle though. Maybe one single request might not always be enough to determine that a bunch of differennt audit rows are related?
It looks good to me.
If you are in a ASP.NET Core Web API, you could use the HttpContext.TraceIdentifier value (a unique value per request). So you can add this correlation ID to the events via a Custom Action.
But I would recommend to also activate the audit logging for the controller actions(s) with Audit.WebApi library. It will generate audit events for each call to your API, including the TraceIdentifier.
Take a look at the web api template provided. You can install the template with:
dotnet new -i Audit.WebApi.Template
And then you can generate a project that uses both Audit.WebApi and Audit.EntityFramework both including the correlation:
dotnet new webapiaudit -E
@thepirat000 Thanks a lot I am exploring this now. So when I add stuff to SetCustomField
via the CustomAction
that same scope is accessible in my AuditEntityAction
? I am a little new to how all this scope stuff works all throughout the Audit libraries.
Can you chain? IE:
Audit.Core.Configuration.Setup()
.UseEntityFramework(x => {
// Run this for all audits
x.AuditTypeMapper(t => typeof(IAudit))
.AuditEntityAction<IAudit>((auditEvent, eventEntry, auditEntity) =>
{
var entityFrameworkEvent = auditEvent.GetEntityFrameworkEvent();
IEntity entity = eventEntry.GetEntry().Entity as IEntity;
if (entity != null)
{
entity.Id = 0;
}
});
x.AuditTypeNameMapper(typeName => "Audit_" + typeName)
.AuditEntityAction<IAudit>((auditEvent, eventEntry, auditEntity) =>
{
var entityFrameworkEvent = auditEvent.GetEntityFrameworkEvent();
auditEntity.Action = eventEntry.Action;
auditEntity.AuditDate = DateTime.UtcNow;
});
});
About the configuration, you can only have one entity mapper, so you should call one of the three: AuditTypeMapper
, AuditTypeNameMapper
or AuditTypeExplicitMapper
.
For the first two, you can attach only one AuditEntityAction
. For the explicit mapper, you can configure one AuditEntityAction
per table mapping.
You can see some examples here.
Also note the Custom Actions are globally applied to all the events but the AuditEntityAction
is executed for each entry (each table affected) on the EF events.
Specifically for the Entity Framework events, it will work like this:
1- Execute the OnScopeCreated
custom actions for the event.
2- Save the DbContext changes
3- Execute the AuditEntityAction for each entry on the event
4- Execute the OnEventSaving
custom actions for the event
5- Save the Audit DbContext changes
6- Execute the OnEventSaved
custom actions for the event
@thepirat000 Can I get access to the entity(s) being saved in OnScopeCreated
? I want to modify them.
Is there a better way than this?
var entities = ChangeTracker.Entries().Where(x => x.Entity is BaseEntity && (x.State == EntityState.Added || x.State == EntityState.Modified));
Yes, you can use the GetEntityFrameworkEvent
extension to get the EF part of the event:
using Audit.EntityFramework;
Audit.Core.Configuration.AddCustomAction(ActionType.OnScopeCreated, scope =>
{
EntityFrameworkEvent efEvent = scope.GetEntityFrameworkEvent();
//efEvent.Entries[0].Changes[0].OriginalValue
}
@thepirat000 Can we get a constant/enum for the StateName instead of a string so we can switch on it?
I wish I could do this:
foreach (var entry in efEvent.Entries.Where(x => x.Action == EfAuditAction.Added))
{
// ...
}
It could be, I will take note and maybe change it to an enum in the next version, but that will be a breaking change.
Anyway nothing stops you from switching on the string:
public static class EfAuditAction
{
public static string Added { get; } = "Insert";
public static string Deleted { get; } = "Delete";
public static string Modified { get; } = "Update";
}
@thepirat000 Also, looks like "Changes" is only available for update operations.
What I am trying to accomplish is updating some metadata on the entities themselves:
public override void OnScopeCreated(AuditScope auditScope)
{
var currentUsername = (string)auditScope.Event.CustomFields[Domain.Constants.Auditing.IdentityName];
var entities = ChangeTracker.Entries().Where(x => x.Entity is IAuditable && (x.State == EntityState.Added || x.State == EntityState.Modified));
foreach (var entity in entities)
{
if (entity.State == EntityState.Added)
{
((IAuditable)entity.Entity).CreatedOn = DateTime.UtcNow;
((IAuditable)entity.Entity).CreatedBy = currentUsername;
}
((IAuditable)entity.Entity).UpdatedOn = DateTime.UtcNow;
((IAuditable)entity.Entity).CreatedBy = currentUsername;
}
}
Is there an easy way to change the CreatedOn and CreatedBy through the efEvent.Entries
?
Yes, Changes
is only available for Update operations. And I guess you don't need to use the ChangeTracker. This should work:
public override void OnScopeCreated(AuditScope auditScope)
{
var currentUsername = (string)auditScope.Event.CustomFields[Domain.Constants.Auditing.IdentityName];
var entities = auditScope.GetEntityFrameworkEvent()
.Entries.Where(x => x.Action == "Insert" || x.Action == "Update");
foreach (var entity in entities)
{
// entity.GetEntry().CurrentValues, etc...
if (entity.Action == "Insert")
{
((IAuditable)entity.Entity).CreatedOn = DateTime.UtcNow;
((IAuditable)entity.Entity).CreatedBy = currentUsername;
}
((IAuditable)entity.Entity).UpdatedOn = DateTime.UtcNow;
((IAuditable)entity.Entity).CreatedBy = currentUsername;
}
}
Also note you can access the EntityFramework's EventEntry
by calling .GetEntry()
on each of the audit event entry.
Hmm, I get 'Object reference not set to an instance of an object.' on ((IAuditable)entity.Entity). It is saying entry.Entity is null.
This works. Any idea why?
if (entity.Action == "Insert")
{
((IAuditable)entity.GetEntry().Entity).CreatedOn = DateTime.UtcNow;
((IAuditable)entity.GetEntry().Entity).CreatedBy = currentUsernameString;
}
((IAuditable)entity.GetEntry().Entity).UpdatedOn = DateTime.UtcNow;
((IAuditable)entity.GetEntry().Entity).CreatedBy = currentUsernameString;
You have to set the IncludeEntityObjects
to true on the settings. By default the Entity is not included on the audit event. Or you can just access as you did, with the GetEntry().Entity
.
Ah but GetEntry()
lets you force your way around that? Seems to me like its faster to call GetEntry() as needed.
if you don't need the Entity object on your audits, I think you are good with the GetEntry()
for further discussion we can better use the gitter chat channel: https://gitter.im/Audit-NET/Lobby
I am trying to gather up all audit rows that happened as a result of the same request. I am not using transactions at the moment, but my DbContext is a scoped dependency so I can I rely on
ConnectionId
?Or, should I be passing a custom CorrelationId down all my layers every time?
Should I maybe add transactions to all my requests so I have the TransactionId? Or will that cause me great pain when it comes to scoped transactions/distributed transactions?