Is it possible to intercept a READ action?

c# entity-framework-core

Question

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?

1
4
10/30/2017 5:18:23 AM

Accepted Answer

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...

3
10/28/2017 3:07:27 PM

Popular Answer

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



Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow