我们在具有相似(足够)架构的表中的两个数据库中跟踪相同的信息。当我们更新一个数据库中的数据时,我们希望确保数据与另一个数据库中的表保持同步。
我们在两个数据库中使用Entity Framework 5,因此我原本只想导入辅助数据库的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();
}
}
知道为什么第一个语法有效,第二个崩溃?根据我所读的online,这应该是对数据库/数据库服务器本身所做的更改。
答案 0 :(得分:0)
第二位代码导致错误,因为它在单个TransactionScope
内打开多个数据库连接。当程序在单个作用域内打开第二个数据库连接时,它将被提升为分布式事务。 You can read more information about distributed transactions here
在一个事务范围内搜索&#34;多个数据库连接&#34;将帮助您找到更多StackOverflow帖子。这是两个相关的:
在您走进分布式交易的土地之前,对于这种情况可能有一个更简单的解决方案。事务范围可以嵌套,如果任何嵌套范围失败,父范围将回滚。每个范围只需要担心一个连接或只是嵌套范围,因此我们可能不会遇到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();
}