SqlException:不要中止事务

时间:2014-12-22 15:59:00

标签: c# sql-server entity-framework transactions transactionscope

我有一个代码将数据添加到两个EntityFramework 6 DataContexts,如下所示:

using(var scope = new TransactionScope())
{
    using(var requestsCtx = new RequestsContext())
    {
        using(var logsCtx = new LogsContext())
        {
            var req = new Request { Id = 1, Value = 2 };
            requestsCtx.Requests.Add(req);

            var log = new LogEntry { RequestId = 1, State = "OK" };
            logsCtx.Logs.Add(log);

            try
            {
                requestsCtx.SaveChanges();
            }
            catch(Exception ex)
            {
                log.State = "Error: " + ex.Message;
            }

            logsCtx.SaveChanges();
        }
    }
}

Requests表中有一个插入触发器,它使用RAISEERROR拒绝某些值。这种情况很正常,应该由调用try-catch方法的SaveChanges块处理。但是,如果第二个SaveChanges方法失败,则必须完全还原对两个DataContexts的更改 - 因此是事务范围。

出现错误:当requestsCtx.SaveChanges()抛出异常时,整个Transaction.Current的状态设置为Aborted,后者logsCtx.SaveChanges()失败并显示以下内容:

TransactionException:
The operation is not valid for the state of the transaction.

为什么会发生这种情况?如何告诉EF第一个例外并不重要?

2 个答案:

答案 0 :(得分:0)

真的不确定这是否有效,但可能值得尝试。

private void SaveChanges()
{
    using(var scope = new TransactionScope())
    {
        var log = CreateRequest();
        bool saveLogSuccess = CreateLogEntry(log);

        if (saveLogSuccess)
        {
            scope.Complete();
        }
    }
}    

private LogEntry CreateRequest()
{
    var req = new Request { Id = 1, Value = 2 };
    var log = new LogEntry { RequestId = 1, State = "OK" };

    using(var requestsCtx = new RequestsContext())
    {
        requestsCtx.Requests.Add(req);

        try
        {
            requestsCtx.SaveChanges();
        }
        catch(Exception ex)
        {
            log.State = "Error: " + ex.Message;
        }
        finally
        {
            return log;
        }
    }
}

private bool CreateLogEntry(LogEntry log)
{
    using(var logsCtx = new LogsContext())
    {
        try
        {
            logsCtx.Logs.Add(log);

            logsCtx.SaveChanges();
        }
        catch (Exception)
        {
            return false;
        }

        return true;
    }
}

来自transactionscope的文档:http://msdn.microsoft.com/en-us/library/system.transactions.transactionscope%28v=vs.110%29.aspx

  

如果事务范围内没有异常(即介于两者之间)   TransactionScope对象的初始化和调用   它的Dispose方法),然后是范围内的事务   参与者被允许继续。如果在其中发生异常   交易范围,它参与的交易将   回滚。

基本上只要遇到异常,就会回滚事务(因为您似乎已经意识到了) - 我认为可能会工作,但我真的不确定并且可以&#39 ; t测试确认。这似乎违背了交易范围的预期用途,而且我对异常处理/冒泡不够熟悉,但也许它会有所帮助! :)

答案 1 :(得分:0)

我想我终于明白了。诀窍是为第一个SaveChanges使用隔离的事务:

using(var requestsCtx = new RequestsContext())
using(var logsCtx = new LogsContext())
{
    var req = new Request { Id = 1, Value = 2 };
    requestsCtx.Requests.Add(req);

    var log = new LogEntry { RequestId = 1, State = "OK" };
    logsCtx.Logs.Add(log);

    using(var outerScope = new TransactionScope())
    {
        using(var innerScope = new TransactionScope(TransactionScopeOption.RequiresNew))
        {
            try
            {
                requestsCtx.SaveChanges();
                innerScope.Complete();
            }
            catch(Exception ex)
            {
                log.State = "Error: " + ex.Message;
            }
        }

        logsCtx.SaveChanges();
        outerScope.Complete();
    }
}

警告:由于性能原因,大多数关于RequiresNew模式的文章都不鼓励使用它。它适用于我的场景,但是如果有任何我不知道的副作用,请告诉我。