EF Core RollBack (RollBack 事务回滚失败分析[待解决])

 背景:最近生产环境出现奇怪问题,数据插入失败后unitwork手动提交回滚操作(数据库操作使用 Microsoft.EntityFrameworkCore, Version=2.2.6.0, Culture=neutral, PublicKeyToken=adb9793829ddae60),之前跟踪操作的数据不会被回滚。 

操作代码如下:

_unitOfWork.RollBackTransaction();//_unitOfWork由构造函数注入

查找SqlClient源码  Microsoft.Data.SqlClient

src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalTransaction.cs

 1  internal void Dispose()
 2         {
 3             this.Dispose(true);
 4             System.GC.SuppressFinalize(this);
 5         }
 6 
 7         private void Dispose(bool disposing)
 8         {
 9             SqlClientEventSource.Log.TryPoolerTraceEvent("SqlInternalTransaction.Dispose | RES | CPOOL | Object Id {0}, Disposing", ObjectID);
10             if (disposing)
11             {
12                 if (null != _innerConnection)
13                 {
14                     // implicitly rollback if transaction still valid
15                     _disposing = true;
16                     this.Rollback();
17                 }
18             }
19         }
Dispose方法
internal void Rollback()
        {
            var scopeID = SqlClientEventSource.Log.TryScopeEnterEvent("SqlInternalTransaction.Rollback | API | Object Id {0}", ObjectID);
            if (_innerConnection.IsLockedForBulkCopy)
            {
                throw SQL.ConnectionLockedForBcpEvent();
            }

            _innerConnection.ValidateConnectionForExecute(null);

            try
            {
                // If no arg is given to ROLLBACK it will rollback to the outermost begin - rolling back
                // all nested transactions as well as the outermost transaction.
                _innerConnection.ExecuteTransaction(SqlInternalConnection.TransactionRequest.IfRollback, null, IsolationLevel.Unspecified, null, false);

                // Since Rollback will rollback to outermost begin, no need to check
                // server transaction level.  This transaction has been completed.
                Zombie();
            }
            catch (Exception e)
            {
                if (ADP.IsCatchableExceptionType(e))
                {
                    CheckTransactionLevelAndZombie();

                    if (!_disposing)
                    {
                        throw;
                    }
                }
                else
                {
                    throw;
                }
            }
            finally
            {
                SqlClientEventSource.Log.TryScopeLeaveEvent(scopeID);
            }
        }

        internal void Rollback(string transactionName)
        {
            long scopeID = SqlClientEventSource.Log.TryScopeEnterEvent("SqlInternalTransaction.Rollback | API | Object Id {0}, Transaction Name {1}", ObjectID, transactionName);
            if (_innerConnection.IsLockedForBulkCopy)
            {
                throw SQL.ConnectionLockedForBcpEvent();
            }

            _innerConnection.ValidateConnectionForExecute(null);

            // ROLLBACK takes either a save point name or a transaction name.  It will rollback the
            // transaction to either the save point with the save point name or begin with the
            // transaction name.  NOTE: for simplicity it is possible to give all save point names
            // the same name, and ROLLBACK will simply rollback to the most recent save point with the
            // save point name.
            if (string.IsNullOrEmpty(transactionName))
                throw SQL.NullEmptyTransactionName();

            try
            {
                _innerConnection.ExecuteTransaction(SqlInternalConnection.TransactionRequest.Rollback, transactionName, IsolationLevel.Unspecified, null, false);
            }
            catch (Exception e)
            {
                if (ADP.IsCatchableExceptionType(e))
                {
                    CheckTransactionLevelAndZombie();
                }
                throw;
            }
            finally
            {
                SqlClientEventSource.Log.TryScopeLeaveEvent(scopeID);
            }
        }
Rollback

SqlTransaction Rollback 实现逻辑

 1 /// <include file='../../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlTransaction.xml' path='docs/members[@name="SqlTransaction"]/DisposeDisposing/*' />
 2         protected override void Dispose(bool disposing)
 3         {
 4             if (disposing)
 5             {
 6                 if (!IsZombied && !IsYukonPartialZombie)
 7                 {
 8                     _internalTransaction.Dispose();
 9                 }
10             }
11             base.Dispose(disposing);
12         }
Dispose实现逻辑
 1  /// <include file='../../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlTransaction.xml' path='docs/members[@name="SqlTransaction"]/Rollback2/*' />
 2         override public void Rollback()
 3         {
 4             Exception e = null;
 5             Guid operationId = s_diagnosticListener.WriteTransactionRollbackBefore(_isolationLevel, _connection, InternalTransaction);
 6 
 7             if (IsYukonPartialZombie)
 8             {
 9                 // Put something in the trace in case a customer has an issue
10                 SqlClientEventSource.Log.TryAdvancedTraceEvent("SqlTransaction.Rollback | ADV | Object Id {0}, partial zombie no rollback required", ObjectID);
11                 _internalTransaction = null; // yukon zombification
12             }
13             else
14             {
15                 ZombieCheck();
16 
17                 SqlStatistics statistics = null;
18                 long scopeID = SqlClientEventSource.Log.TryScopeEnterEvent("SqlTransaction.Rollback | API | Object Id {0}", ObjectID);
19                 SqlClientEventSource.Log.TryCorrelationTraceEvent("SqlTransaction.Rollback | API | Correlation | Object Id {0}, ActivityID {1}, Client Connection Id {2}", ObjectID, ActivityCorrelator.Current, Connection?.ClientConnectionId);
20                 try
21                 {
22                     statistics = SqlStatistics.StartTimer(Statistics);
23 
24                     _isFromAPI = true;
25 
26                     _internalTransaction.Rollback();
27                 }
28                 catch (Exception ex)
29                 {
30                     e = ex;
31                     throw;
32                 }
33                 finally
34                 {
35                     SqlStatistics.StopTimer(statistics);
36                     SqlClientEventSource.Log.TryScopeLeaveEvent(scopeID);
37                     if (e != null)
38                     {
39                         s_diagnosticListener.WriteTransactionRollbackError(operationId, _isolationLevel, _connection, InternalTransaction, e);
40                     }
41                     else
42                     {
43                         s_diagnosticListener.WriteTransactionRollbackAfter(operationId, _isolationLevel, _connection, InternalTransaction);
44                     }
45                     _isFromAPI = false;
46                 }
47             }
48         }
49 
50         /// <include file='../../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlTransaction.xml' path='docs/members[@name="SqlTransaction"]/RollbackTransactionName/*' />
51         public void Rollback(string transactionName)
52         {
53             Exception e = null;
54             Guid operationId = s_diagnosticListener.WriteTransactionRollbackBefore(_isolationLevel, _connection, InternalTransaction, transactionName);
55 
56             ZombieCheck();
57             long scopeID = SqlClientEventSource.Log.TryScopeEnterEvent("SqlTransaction.Rollback | API | Object Id {0}, Transaction Name='{1}', ActivityID {2}, Client Connection Id {3}", ObjectID, transactionName, ActivityCorrelator.Current, Connection?.ClientConnectionId);
58             SqlStatistics statistics = null;
59             try
60             {
61                 statistics = SqlStatistics.StartTimer(Statistics);
62 
63                 _isFromAPI = true;
64 
65                 _internalTransaction.Rollback(transactionName);
66             }
67             catch (Exception ex)
68             {
69                 e = ex;
70                 throw;
71             }
72             finally
73             {
74                 SqlStatistics.StopTimer(statistics);
75                 SqlClientEventSource.Log.TryScopeLeaveEvent(scopeID);
76                 if (e != null)
77                 {
78                     s_diagnosticListener.WriteTransactionRollbackError(operationId, _isolationLevel, _connection, InternalTransaction, e, transactionName);
79                 }
80                 else
81                 {
82                     s_diagnosticListener.WriteTransactionRollbackAfter(operationId, _isolationLevel, _connection, InternalTransaction, transactionName);
83                 }
84 
85                 _isFromAPI = false;
86 
87             }
88         }
Rollback实现逻辑

解决方案如下:

当抛出异常时,不需要手动回滚

可参考:Entity Framework 6 transaction rollback

事务提交,只要没有SaveChange()就不会将之前的修改提交到数据库

参考:

https://docs.microsoft.com/en-us/ef/core/saving/transactions

https://stackoverflow.com/questions/22486489/entity-framework-6-transaction-rollback

原创不易,转载请声明 bindot https://www.cnblogs.com/bindot/
原文地址:https://www.cnblogs.com/bindot/p/efcorerollback.html