使用Entity Framework 6支持SQL Server更改跟踪

17
我有一个使用现有SQL Server数据库生成的Entity Framework 6 Code First模型。数据库正在使用SQL Server Change Tracking,因此为了区分由EF生成的数据操作以及其他外部进程所做的更改,我想设置Change Tracking上下文。通常情况下,在T-SQL中完成此操作,类似于
WITH CHANGE_TRACKING_CONTEXT (@source_id) UPDATE <table>... 我能想到的唯一办法是在EF生成的SQL语句前加上上述SQL子句。虽然看起来,希望修改ORM生成的SQL本身就是有问题的。即使我想,我也不知道该在哪里完成这个操作。是否可以使用EF命令拦截器实现此目的?
这个问题特别涉及到SQL Server的Change Tracking功能与EF的结合使用(不是EF的更改跟踪)。就EF而言,问题只涉及程序化地修改EF生成的SQL。

不确定我是否理解你的问题 - 从我的推断来看,你想在通过 EF 进行更新时执行 chnage_tracking。也许你可以为你的更新/添加/删除编写存储过程(具有所需的跟踪),然后将其映射到 EF 实体。更多详情请查看此处 - http://www.entityframeworktutorial.net/entityframework6/code-first-insert-update-delete-stored-procedure-mapping.aspx。如果我误解了你的问题,请告诉我。 - Developer
2个回答

8
很遗憾,Entity Framework 6没有内置对SQL Server更改跟踪的支持。然而,它提供了拦截功能,使您能够在执行之前修改生成的SQL。虽然更改ORM生成的SQL应该谨慎并且只有在必要时才应该这样做,但绝对存在适用的情况。
EF6公开了IDbCommandInterceptor类型,它为您提供了钩子来访问整个查询管道。您只需要实现此接口并将拦截器注册到EF即可。
值得注意的是,框架将在每个INSERT、UPDATE和DELETE之前调用NonQueryExecuting,这使得它成为您连接更改跟踪的好地方。
以一个简单的例子为例,考虑以下拦截器:
public class ChangeTrackingInterceptor : IDbCommandInterceptor
{
    private byte[] GetChangeTrackingContext()
    {
        // TODO: Return the appropriate change tracking context data
        return new byte[] { 0, 1, 2, 3 };
    }

    public void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
    {
        command.CommandText = "WITH CHANGE_TRACKING_CONTEXT (@change_tracking_context)\r\n" + command.CommandText;

        // Create the varbinary(128) parameter
        var parameter = command.CreateParameter();
        parameter.DbType = DbType.Binary;
        parameter.Size = 128;
        parameter.ParameterName = "@change_tracking_context";
        parameter.Value = GetChangeTrackingContext();
        command.Parameters.Add(parameter);
    }

    public void NonQueryExecuted(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
    {
    }

    public void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
    {
    }

    public void ReaderExecuted(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
    {
    }

    public void ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
    {
    }

    public void ScalarExecuted(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
    {
    }
}

当EF生成任何会改变DB状态的查询时,它会在执行查询之前调用此方法。这为您提供了一个机会,使用标准SQL注入您的自定义更改跟踪上下文。

要将拦截器注册到EF中,只需在启动代码中的某个位置调用DbInterception.Add即可:

var changeTrackingInterceptor = new ChangeTrackingInterceptor();
DbInterception.Add(changeTrackingInterceptor);

关于 IDbCommandInterceptor 接口的文档并不是很多,但是这篇MSDN文章是一个不错的起点。


1
这就是我试图让它工作的方式,但是无法做到。实际上,我根本无法让拦截器运行,并且不得不因为健康原因离开工作。一旦回到工作岗位,我会再次尝试,并在此留下反馈,所以到目前为止,从我的角度来看,这还未经测试。 - bilal.haider

2

[OP澄清后编辑]

我认为在服务器端设置更改跟踪并且不要与EF查询混淆会更容易。简单来说,更改跟踪:

优点:

  • 轻量级
  • 设置相当容易
  • 支持同步
  • 仍然是事务的一部分(如果事务失败则回滚)
  • 不依赖于 SQL 代理
  • 将捕获在正常 ORM 更改操作之外进行的更改(由代码调用的存储过程,源自应用程序之外的所有更改)

缺点:

  • 不适合需要更多信息的审计
  • 稍微慢一些,因为它是同步完成的(与 CDC 的异步性质相反)

[原答案]

一种方法是在“正常”更改中添加更改/跟踪信息,并将所有更改范围限定在一个事务内。

您可以覆盖DbContextSaveChanges方法并添加跟踪信息的代码。ChangeTracker引用允许您查找具有特定状态(添加、更新、删除、未修改)的所有实体,从而能够保存执行的更改类型。这里提供了一个完整的工作示例here
似乎有一个Nuget包可以帮助您进行审计-TrackerEnabledDbContext 这种方法的优点是您可以轻松地标记实体类型,以便不审核某些信息(实现接口或使用某些自定义属性)。
另一个优点是您可以通过在属性上明确指定跟踪属性来进一步微调更改跟踪。
我看到的一个缺点是事务将变得更长,并且锁定某些表将变得更长,可能会导致性能问题(这在单位时间内的事务数量严重取决于)。
此外,此解决方案仅会捕获来自上下文代码(EF)的更改,而不会捕获直接针对数据库或通过存储过程执行的其他更改(无论它们是从外部进程还是EF调用)。
另一种方法是使用服务器端(SQL)更改数据捕获,它捕获启用了此功能的表上进行的所有更改。CDC 的一个重要方面是在审核表结构发生变化时的行为。有关更多信息,请阅读本文
服务器端方法具有两个主要优点:
  • 速度更快,因为它是异步完成的
  • 更可靠,如果数据更改来自各种来源(手动、ETL、存储过程等)。

这个问题特别涉及到SQL Server更改跟踪,让我更新一下问题以便更清晰明了。 - bilal.haider

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接