【问题标题】:Is it possible to intercept a READ action?是否可以拦截 READ 操作?
【发布时间】:2017-10-30 13:18:23
【问题描述】:

在 Entity Framework Core 中,您可以覆盖 DbContext 中的 SaveChanges/SaveChangesAsync 方法,并根据不同的状态,例如:EntityState.Added 或 EntityState.Modified 或 EntityState.Deleted,您可以创建一些关于审计的解决方案何时何人创建、修改或删除某些记录。您可以在操作之前和之后保存实体的状态。这里的一切都很完美!

我们可以为读取/查询/选择/查看操作做类似的事情吗?

【问题讨论】:

  • 为什么要关闭它?这个问题有什么问题?
  • 赏金不会神奇地将问题变成明确的问题。
  • 您有一个未回答的问题。您没有提到为什么内置日志记录(ILoggerProvider 等)显然不够用。现在你突然让一个用户参与进来。太多的事情对你来说可能是显而易见的,但对其他人来说却不是。
  • 我真的不想唠叨你,但目前还不清楚你要审核什么。事务中修改的记录数量可能会受到限制,但一个用户只需打开一个 UI 功能即可轻松读取数千(或更多)记录。您想单独审核所有这些记录吗?我认为您必须审核对选择的聚合实体的读取操作,例如:“用户 A 在时间 Y 打开文件 X”,但不是所有属于该文件的数据的读取操作,甚至(可能)不是导致打开文件的读取操作(例如查询文件列表)。

标签: c# entity-framework-core


【解决方案1】:

我挖了一下,发现IQueryable的实际执行是由EntityQueryProvider : IAsyncQueryProvider, IQueryProvider完成的。

所以...您覆盖默认的EntityQueryProvider 来进行日志记录:

   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;
        }
    }

你在StartUp.ConfigureServices(IServiceCollection services)配置DbContext的时候注册

    services.AddDbContext<XPContext>(builder =>
        builder
        .UseSqlServer(Configuration["TryDBConnectionString"])
        .ReplaceService<IAsyncQueryProvider, LoggingQueryProvider>()
    );

这不是很直接,但是您应该能够从表达式中获取一些信息,例如实体类型,并且您显然可以访问实际结果。对于异步方法来说,事情看起来有点复杂,但是......

【讨论】:

  • 我喜欢扩展查询提供程序的想法,但是您打算如何在没有 dbContext 的情况下登录到数据库?
  • 绝妙的答案,在从数据库读取时,可以使用相同/相似的方式修改实体。
【解决方案2】:

大多数策略依赖于覆盖 SaveChanges() 来审核数据,但您也可以通过覆盖 Dispose() 方法来访问其他数据。

当从数据库中查询数据时,它被添加到 dbContext 中,如果它被读取但未更改,它应该有EntityState.Unchanged。

假设每个请求都有一个典型的 Web 应用样式 DbContext 范围的新实例,那么按 id 查询将意味着在处置 DbContext 时,ChangeTracker 中有一个具有该状态的条目。

你可以试试这样的:

public override void Dispose()
{
    var unchanged = ChangeTracker.Entries()
          .Where(x => x.EntityState == EntityState.Unchanged);

    // log your unchanged entries here

    base.Dispose();
 }

这并非万无一失,因为您可能会在创建/更新过程中从某些表中检索数据作为验证的一部分,或者您可能会在多个存储库之间共享一个上下文,因此您需要仔细考虑哪些实体需要仔细审核以及哪些访问权限你使用的模式

【讨论】:

  • AsNoTracking() 的查询呢?
  • @GertArnold 正如我所说 - 并非万无一失。假设 OP 可以控制所有查询。既然您提到了它,AsNoChangeTracking() 可以添加一种细粒度的方法,将一些查询排除在审计之外。
【解决方案3】:

我建议在更高级别进行日志记录。例如,如果您使用的是 WebApi,则可以在 OWIN 管道级别登录,从而记录信息请求。
记录低端会导致第二次猜测并重新迭代数据,这将导致效率低下。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-15
    • 2017-08-04
    • 2011-08-26
    • 1970-01-01
    相关资源
    最近更新 更多