zoukankan      html  css  js  c++  java
  • 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/
  • 相关阅读:
    数据结构-循环队列(Python实现)
    数据结构-堆栈和队列最简单的实现(Python实现)
    数据结构-双向链表(Python实现)
    ArrayList集合对象的sort()方法
    编写泛型
    super通配符
    面向对象(上)综合练习1:银行存取
    面向对象(上)综合练习1:Account和Customer
    泛型之extends通配符
    Comparable<T>泛型接口之实现按name或者age进行排序
  • 原文地址:https://www.cnblogs.com/bindot/p/efcorerollback.html
Copyright © 2011-2022 走看看