MSDTC被调用。但为什么呢?

时间:2015-07-24 16:52:56

标签: c# sql sql-server-2008 async-await msdtc

对于我的数据访问,我使用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被调用,因为在单个池连接下处理错误地处理了两个独立的逻辑连接。但那没用。

API操作

// 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());
    }
}

AmbientConnection实施

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吗?

1 个答案:

答案 0 :(得分:2)

PublicationOnly表示可以创建两个连接,一个被丢弃。我很惊讶你犯了这个错误,因为你明确说明了PublicationOnly(而不是默认的安全模式)。您明确允许此错误。

出于某种原因,我没有看到您已经尝试过ExecutionAndPublication。由于不使用它是一个错误,请修复问题中的代码。

CreateConnection在某种意义上也被打破,如果在打开时发生异常,连接对象就不会被处理掉。可能无害,但你永远不知道。

此外,审核此代码以查找在ASP.NET超时请求时可能发生的线程中止。你在这里做了非常危险和脆弱的事情。

我使用的模式是使用IOC容器注入为整个请求共享的连接。该连接的第一个客户端打开它。请求结束事件将其关闭。简单,并消除所有令人讨厌的共享,可变,多线程状态。

为什么要使用缓存来查找不想丢失的数据?这可能是错误。不要那样做。

?.Value ?? generatorWrapped.Value是什么?字典永远不会返回null。删除该代码。如果它可以返回null,那么强制延迟值将创建第二个连接,这也是一个逻辑错误。