Reputation: 6029
In Entity Framework Core, you can override SaveChanges
/SaveChangesAsync
method in the DbContext
and based on different states like: EntityState.Added
or EntityState.Modified
or EntityState.Deleted
, you can create some Audit solution about when and whom created, modified or deleted certain records. You can save the state of the entity before and after the action. All good here works perfect!
Can we do something similar for read/query/select/view actions?
Upvotes: 6
Views: 1611
Reputation: 622
I dug a little and found that the actual execution of the IQueryable
is done by EntityQueryProvider : IAsyncQueryProvider, IQueryProvider
.
So... you override the default EntityQueryProvider
to do the logging:
public class LoggingQueryProvider : EntityQueryProvider
{
public LoggingQueryProvider(IQueryCompiler queryCompiler) : base(queryCompiler) { }
public override object Execute(Expression expression)
{
var result = base.Execute(expression);
//log
return result;
}
public override TResult Execute<TResult>(Expression expression)
{
var result = base.Execute<TResult>(expression);
//log
return result;
}
public override IAsyncEnumerable<TResult> ExecuteAsync<TResult>(Expression expression)
{
var result = base.ExecuteAsync<TResult>(expression);
//log
return result;
}
public override Task<TResult> ExecuteAsync<TResult>(Expression expression, CancellationToken cancellationToken)
{
var result = base.ExecuteAsync<TResult>(expression, cancellationToken);
//log
return result;
}
}
and you register it when configuring DbContext in StartUp.ConfigureServices(IServiceCollection services)
services.AddDbContext<XPContext>(builder =>
builder
.UseSqlServer(Configuration["TryDBConnectionString"])
.ReplaceService<IAsyncQueryProvider, LoggingQueryProvider>()
);
It's not quite straight forward, but you should be able to get some information from the expression, like the entity type, and you obviously have access to the actual result. Things look a bit more complicated for the async methods, but...
Upvotes: 6
Reputation: 25429
I would suggest logging at a higher level. For example, if you're using a WebApi you can log at the OWIN pipeline level thereby logging the requests for information.
Logging lower ends up second guessing and re-iterating through data which is ugly and will end contributing to inefficiencies.
Upvotes: 0
Reputation: 7497
Most strategies rely on overriding SaveChanges()
to audit data, but you can access other data by overriding the Dispose()
method as well.
When data is queried from the database it is added to the dbContext and if it is read but not changed it should have EntityState.Unchanged
.
Assuming a typical web app style DbContext
scope of a new instance per request then a query by id would mean that there was a single entry in the ChangeTracker
with that state when the DbContext
is disposed.
You could try something like this:
public override void Dispose()
{
var unchanged = ChangeTracker.Entries()
.Where(x => x.EntityState == EntityState.Unchanged);
// log your unchanged entries here
base.Dispose();
}
This is not totally foolproof, as you might retrieve data from some tables as part of validation during a create/update process, or you might share a context across multiple repositories so you need to consider what entities need auditing carefully and what access patterns you use
Upvotes: 1