对于我的数据访问,我使用API级别的TransactionScopes将整个操作包装在一个事务中,以便我的SQL操作可以在某种程度上组合。我有一个Web项目,它承载一个API和一个单独的服务库,它是实现和调用SQL。在操作开始时(API入口点),我打开TransactionScope。只要在操作处理过程中需要SqlConnection,就要求AmbientConnection而不是直接建立新连接。 AmbientConnection为当前转换查找或创建新的SqlConnection。这样做应该允许良好的可组合性,但也避免调用MSDTC,因为它应该继续对事务中的每个子操作使用相同的连接。交易完成后(使用scope.complete()
),连接将自动关闭。
问题是每隔一段时间MSDTC仍然被调用,我无法弄清楚原因。我成功地使用了这个,我相信我从来没有调用过MSDTC。这次看起来有两件事情我不同:1)我正在使用SQL Server 2008 R1(10.50.4000) - 不是我的选择 - 而且我知道MSDTC行为从这个版本开始改变了,也许不是所有的纠结已经制定出来直到以后的版本。 2)async-await的使用是新的,我相信我必须使用TransactionScopeAsyncFlowOption.Enabled
来适应这个新功能,以防部分实现是异步的。也许还需要采取更多措施。
我在连接字符串中尝试了Pooling=false
,以防MSDTC被调用,因为在单个池连接下处理错误地处理了两个独立的逻辑连接。但那没用。
// Exposed API composing multiple low-level operations within a single TransactionScope
// independent of any database platform specifics.
[HttpPost]
public async Task<IHttpActionResult> GetMeTheTwoThings()
{
using (var scope = new TransactionScope(TransactionScopeOption.Required, TransactionScopeAsyncFlowOption.Enabled))
{
var result = new TwoThings(
await serviceLayer.GetThingOne(),
await serviceLayer.GetThingTwo());
scope.Complete();
return Ok(result);
}
}
public async Task<ThingOne> GetThingOne()
{
using (var cmd = connManagement.AmbientConnection.CreateCommand())
{
cmd.CommandType = System.Data.CommandType.StoredProcedure;
cmd.CommandText = "dbo.GetThingOne";
return (ThingOne)(await cmd.ExecuteScalarAsync());
}
}
public async Task<ThingTwo> GetThingTwo()
{
using (var cmd = connManagement.AmbientConnection.CreateCommand())
{
cmd.CommandType = System.Data.CommandType.StoredProcedure;
cmd.CommandText = "dbo.GetThingTwo";
return (ThingTwo)(await cmd.ExecuteScalarAsync());
}
}
internal class SQLConnManagement
{
readonly string connStr;
readonly ConcurrentDictionary<Transaction, SqlConnection> txConnections = new ConcurrentDictionary<Transaction, SqlConnection>();
private SqlConnection CreateConnection(Transaction tx)
{
var conn = new SqlConnection(this.connStr);
// When the transaction completes, close the connection as well
tx.TransactionCompleted += (s, e) =>
{
SqlConnection closing_conn;
if (txConnections.TryRemove(e.Transaction, out closing_conn))
{
closing_conn.Dispose(); // closing_conn == conn
}
};
conn.Open();
return conn;
}
internal SqlConnection AmbientConnection
{
get
{
var txCurrent = Transaction.Current;
if (txCurrent == null) throw new InvalidOperationException("An ambient transaction is required.");
return txConnections.GetOrAdd(txCurrent, CreateConnection);
}
}
public SQLConnManagement(string connStr)
{
this.connStr = connStr;
}
}
不要使帖子过于复杂,但这可能是相关的,因为在我看来,每次调用MSDTC时,记录的堆栈跟踪都会显示已涉及到下一个机制。我使用内置的ObjetCache缓存的某些数据,因为它不会经常更改,所以我每分钟最多只能获得一次或者其他任何数据。这有点花哨,但我不明白为什么Lazy生成器与更典型的调用会有任何不同,以及为什么这会特别导致MSSDTC被调用。我也试过LazyThreadSafetyMode.ExecutionAndPublication
以防万一,但无论如何都没有帮助(当然,异常只是作为后续请求的缓存结果在到期之前传递,当然,这是不可取的)。< / p>
/// <summary>
/// Cache element that gets the item by key, or if it is missing, creates, caches, and returns the item
/// </summary>
static T CacheGetWithGenerate<T>(ObjectCache cache, string key, Func<T> generator, DateTimeOffset offset) where T : class
{
var generatorWrapped = new Lazy<T>(generator, System.Threading.LazyThreadSafetyMode.PublicationOnly);
return ((Lazy<T>)cache.AddOrGetExisting(
key,
generatorWrapped,
offset))?.Value ?? generatorWrapped.Value;
}
public ThingTwo CachedThingTwo
{
get
{
return CacheGetWithGenerate(
MemoryCache.Default,
"Services.ThingTwoData",
() => GetThingTwo(), // ok, GetThingTwo isn't async this time, fudged example
DateTime.Now.Add(TimeSpan.FromMinutes(1)));
}
}
你知道为什么要调用MSDTC吗?
答案 0 :(得分:2)
PublicationOnly
表示可以创建两个连接,一个被丢弃。我很惊讶你犯了这个错误,因为你明确说明了PublicationOnly
(而不是默认的安全模式)。您明确允许此错误。
出于某种原因,我没有看到您已经尝试过ExecutionAndPublication
。由于不使用它是一个错误,请修复问题中的代码。
CreateConnection
在某种意义上也被打破,如果在打开时发生异常,连接对象就不会被处理掉。可能无害,但你永远不知道。
此外,审核此代码以查找在ASP.NET超时请求时可能发生的线程中止。你在这里做了非常危险和脆弱的事情。
我使用的模式是使用IOC容器注入为整个请求共享的连接。该连接的第一个客户端打开它。请求结束事件将其关闭。简单,并消除所有令人讨厌的共享,可变,多线程状态。
为什么要使用缓存来查找不想丢失的数据?这可能是错误。不要那样做。
?.Value ?? generatorWrapped.Value
是什么?字典永远不会返回null。删除该代码。如果它可以返回null,那么强制延迟值将创建第二个连接,这也是一个逻辑错误。