问题: 如何将Linq,Azure和数据库连接与transactionscope相结合,而不会将事务提升为分布式?
具体来说,变通方法/解决方案2(见下文)是一个可接受的解决方案,还是有更好的方法?
背景资料:
从.config文件中获取连接字符串。
using (var db = new DBDataContext())
using (var scope = new TransactionScope())
{
// Do DB stuff, then call a method that has more Linq code..
//..accessing the _same_ DB, and needs to be inside this transaction.
}
这似乎是最佳做法,在localhost上调试时工作正常。
当部署到Azure时,只要我们使用完全相同的连接字符串,就会在执行Linq查询时将事务“提升”为分布式。这会导致运行时异常。
请注意,被调用的方法有自己的“using DBDataContext()”,但没有新的transactioncope。
似乎连接池管理器不确定新连接是否属于同一个数据库,即使连接字符串相同。
似乎有3种解决方法:
1)传递对现有连接的引用 - 不能接受的。实际上有数百种调用DB的方法。这不是来电者的责任。
2)使用全局(数据层)连接管理器 - 这不是最佳做法,应该避免。但为什么呢?
3)使用集成安全性 - 使用集成安全性时,连接池管理器可以将连接识别为与现有连接相同。 - 没有测试过,因为这个解决方案是不可接受的。由于这个问题,不应该被迫使用集成安全性。
编辑:
使用Azure SQL数据库(不是Azure VM上的SQL Server)。
Azure SQL数据库不支持分布式事务。
答案 0 :(得分:1)
你在这里回答了自己的问题:
当部署到Azure时,交易被提升并且#39;尽管我们使用的是完全相同的连接字符串,但只要执行了Linq查询,就可以分发。这会导致运行时异常。
每次涉及第二个连接时,交易都会提升。可能有一些优化案例可以绕过这一点(我不知道任何事情),但我不认为你可以在这里做很多事情。应该重用相同的连接。
想想没有TransactionScope它会如何工作。您的代码可能如下所示:
using (var cn = GetDbConnection())
using (var tx = cn.BeginTransaction())
{
// do stuff with tx...
using (var cn2 = GetDbConnection())
{
// Now think about the transaction scope here...
// There is no way for cn2 to reuse cn's transaction.
// It must begin its own transaction. The only way to manage
// disconnected transactions of this nature is to elevate to
// a distributed transaction.
}
}
编辑:关于您关于全球连接管理器的问题,我不确定这是一个坏主意,具体取决于您的实施。对于ASP.NET用例,我们通常根据请求确定数据库上下文的范围。链中需要连接的任何代码都应该注入其数据库上下文。
这可确保在整个请求中共享相同的上下文(连接)。然后可以自动或手动提交事务,或者在异常情况下自动回滚事务。这是一个非常简单的用例,并且可能不适合您的方案,但它对我们来说效果很好。
Edit2:使用轻量级事务,可以在打开下一个连接之前关闭一个连接来避免提升。交易本身保持打开状态,直到您拨打ts.Complete
,即使是跨越连接。
https://blogs.msdn.microsoft.com/adonet/2008/03/25/extending-lightweight-transactions-in-sqlclient/
您打开外部连接“A”。该池没有可用的适当连接,因此在事务中建立并登记内部连接“z”,从而建立轻量级事务。你现在关闭“A”,它会留出“z”来等待交易结束。接下来打开外部连接“B”(您也可以再次打开“A”并获得相同的结果)。 “B”在附加到事务的池中查找自由内部连接,找不到,创建内部连接“y”并尝试在事务中登记它。现在发现要尝试登记的两个不同资源的事务必须提升(一般资源,特别是sql连接,不能共享本地事务)。最后,您结束事务,该事务通过“z”发送提交或回滚,将其与事务断开连接并将其返回到池中。
因此,这将我们带到我们为Sql Server 2008支持添加的扩展。在服务器上,我们添加了一个不会回滚本地事务的新连接重置模式。这允许SqlClient将内部连接返回到池以供重用。在我们的示例中,当您打开“B”时,它将在池中找到“z”,与“A”在您关闭“A”时放置的事务相关联。 “B”挪用并重置“z”(保持交易保留重置)并愉快地继续工作。 System.Transaction和服务器都不知道应用程序将“z”视为两个单独的连接。就他们而言,只有一个连接,只处理一个本地交易,不需要升级。