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 }
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); } }
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 }
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 }
解决方案如下:
当抛出异常时,不需要手动回滚
可参考: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/