使用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
中打开了多个数据库连接。当程序在单个作用域内打开第二个数据库连接时,它将被提升为分布式事务。您可以在此处阅读有关分布式事务的更多信息。
搜索"一个事务范围内的多个数据库连接"将帮助您找到更多的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();
}