使用TransactionScope&;SqlCommand以保持数据库同步

本文关键字:数据库 同步 SqlCommand TransactionScope amp 使用 | 更新日期: 2023-09-27 18:22:43

我们在具有相似(足够)模式的表中的两个数据库中跟踪相同的信息。当我们更新一个数据库中的数据时,我们希望确保数据与另一个数据库的表保持同步。

我们在两个数据库中都使用EntityFramework5,所以我最初只想导入辅助数据库的DbContext,并使用TransactionsScope来确保创建/更新是原子的。

然而,我很快发现,由于表名是相同的(在该控制器中工作的任何人都必须将Product表称为<Conext>.Product),所以我为辅助表使用了SqlConnection对象,但收到了一些我不太喜欢的结果。

如果我使用下面的语法,这两个表将自动更新/一切按计划进行。

var scopeOptions = new TransactionOptions();
scopeOptions.IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted;
scopeOptions.Timeout = TimeSpan.MaxValue;
var sqlConn = new SqlConnection(ConfigurationManager.ConnectionStrings["Monet"].ConnectionString);
sqlConn.Open();
SqlCommand sqlCommand = sqlConn.CreateCommand();
sqlCommand.CommandText = InsertMonetProduct(product);
using (var ts = new TransactionScope(TransactionScopeOption.Required, scopeOptions))
{
    db.Product.Add(product);
    db.SaveChanges(); 
    sqlCommand.ExecuteNonQuery();
    ts.Complete();
}

但是,如果我在下面使用此语法,代码会在db.SaveChanges()命令上崩溃,并显示以下消息:

分布式事务管理器(MSDTC)的网络访问已被禁用。请使用组件服务管理工具在MSDTC的安全配置中启用网络访问DTC。

var scopeOptions = new TransactionOptions();
scopeOptions.IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted;
scopeOptions.Timeout = TimeSpan.MaxValue;
using (var ts = new TransactionScope(TransactionScopeOption.Required, scopeOptions))
{
    using(var sqlConn = new SqlConnection(ConfigurationManager.ConnectionStrings["Monet"].ConnectionString))
    {
        sqlConn.Open();
        using (SqlCommand sqlCommand = sqlConn.CreateCommand())
        {
            sqlCommand.CommandText = InsertMonetProduct(product);
            sqlCommand.ExecuteNonQuery();
            db.Product.Add(product);
            db.SaveChanges();                                
        }
        ts.Complete();
    }
}

知道为什么第一个语法有效而第二个语法崩溃了吗?根据我在网上读到的内容,这应该是对数据库/数据库服务器本身所做的更改。

使用TransactionScope&;SqlCommand以保持数据库同步

第二位代码导致错误,因为它在单个TransactionScope中打开了多个数据库连接。当程序在单个作用域内打开第二个数据库连接时,它将被提升为分布式事务。您可以在此处阅读有关分布式事务的更多信息。

搜索"一个事务范围内的多个数据库连接"将帮助您找到更多的StackOverflow帖子。这里有两个相关的:

  • C#控制跨多个数据库的事务
  • 如果禁用MSDTC,如何绕过TransactionScope内的多个数据库连接

不过,在您进入分布式事务领域之前,可能有一个更简单的解决方案。事务作用域可以嵌套,如果任何嵌套作用域失败,父作用域将回滚。每个作用域只需要担心一个连接或只是嵌套作用域,因此我们可能不会遇到MSDTC问题。

试试看:

var scopeOptions = new TransactionOptions();
scopeOptions.IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted;
scopeOptions.Timeout = TimeSpan.MaxValue;
using (var ts = new TransactionScope(TransactionScopeOption.Required, scopeOptions))
{
    using (var scope1 = new TransactionScope(TransactionScopeOption.Required))
    {
        // if you can wrap a using statment around the db context here that would be good
        db.Product.Add(product);
        db.SaveChanges();
        scope1.Complete();
    }
    using (var scope2 = new TransactionScope(TransactionScopeOption.Required))
    {
        // omitted the other "using" statments for the connection/command part for brevity
        var sqlConn = new SqlConnection(ConfigurationManager.ConnectionStrings["Monet"].ConnectionString);
        sqlConn.Open();
        SqlCommand sqlCommand = sqlConn.CreateCommand();
        sqlCommand.CommandText = InsertMonetProduct(product);
        sqlCommand.ExecuteNonQuery(); // if this fails, the parent scope will roll everything back
        scope2.Complete();
    }
    ts.Complete();
}