/// <summary> /// If there was an exception thrown checks the database for this transaction and rethrows it if not found. /// Otherwise marks the commit as succeeded and queues the transaction information to be deleted. /// </summary> /// <param name="transaction">The transaction that was commited.</param> /// <param name="interceptionContext">Contextual information associated with the call.</param> /// <seealso cref="IDbTransactionInterceptor.Committed" /> public override void Committed(DbTransaction transaction, DbTransactionInterceptionContext interceptionContext) { TransactionRow transactionRow; if (TransactionContext == null || (interceptionContext.Connection != null && !MatchesParentContext(interceptionContext.Connection, interceptionContext)) || !Transactions.TryGetValue(transaction, out transactionRow)) { return; } Transactions.Remove(transaction); if (interceptionContext.Exception != null) { TransactionRow existingTransactionRow = null; var suspendedState = DbExecutionStrategy.Suspended; try { DbExecutionStrategy.Suspended = false; var executionStrategy = GetExecutionStrategy() ?? DbProviderServices.GetExecutionStrategy(interceptionContext.Connection); existingTransactionRow = TransactionContext.Transactions .AsNoTracking() .WithExecutionStrategy(executionStrategy) .SingleOrDefault(t => t.Id == transactionRow.Id); } catch (EntityCommandExecutionException) { // Error during verification, assume commit failed } finally { DbExecutionStrategy.Suspended = suspendedState; } if (existingTransactionRow != null) { // The transaction id is still in the database, so the commit succeeded interceptionContext.Exception = null; PruneTransactionHistory(transactionRow); } else { TransactionContext.Entry(transactionRow).State = EntityState.Detached; } } else { PruneTransactionHistory(transactionRow); } }
/// <summary> /// Stops tracking the transaction that was rolled back. /// </summary> /// <param name="transaction">The transaction that was rolled back.</param> /// <param name="interceptionContext">Contextual information associated with the call.</param> /// <seealso cref="IDbTransactionInterceptor.RolledBack" /> public override void RolledBack(DbTransaction transaction, DbTransactionInterceptionContext interceptionContext) { TransactionRow transactionRow; if (TransactionContext == null || (interceptionContext.Connection != null && !MatchesParentContext(interceptionContext.Connection, interceptionContext)) || !Transactions.TryGetValue(transaction, out transactionRow)) { return; } Transactions.Remove(transaction); TransactionContext.Entry(transactionRow).State = EntityState.Detached; }
/// <summary> /// If there was an exception thrown checks the database for this transaction and rethrows it if not found. /// Otherwise marks the commit as succeeded and queues the transaction information to be deleted. /// </summary> /// <param name="transaction">The transaction that was commited.</param> /// <param name="interceptionContext">Contextual information associated with the call.</param> /// <seealso cref="IDbTransactionInterceptor.Committed" /> public override void Committed(DbTransaction transaction, DbTransactionInterceptionContext interceptionContext) { TransactionRow transactionRow; if (TransactionContext == null || (interceptionContext.Connection != null && !MatchesParentContext(interceptionContext.Connection, interceptionContext)) || !_transactions.TryGetValue(transaction, out transactionRow)) { return; } _transactions.Remove(transaction); if (interceptionContext.Exception != null) { TransactionRow existingTransactionRow = null; try { existingTransactionRow = TransactionContext.Transactions .AsNoTracking() .WithExecutionStrategy(new DefaultExecutionStrategy()) .SingleOrDefault(t => t.Id == transactionRow.Id); } catch (EntityCommandExecutionException) { // Transaction table doesn't exist } if (existingTransactionRow != null) { // The transaction id is still in the database, so the commit succeeded interceptionContext.Exception = null; PruneTransactionHistory(transactionRow); } else { TransactionContext.Entry(transactionRow).State = EntityState.Detached; } } else { PruneTransactionHistory(transactionRow); } }
/// <summary> /// Stores the tracking information for the new transaction to the database in the same transaction. /// </summary> /// <param name="connection">The connection that began the transaction.</param> /// <param name="interceptionContext">Contextual information associated with the call.</param> /// <seealso cref="IDbConnectionInterceptor.BeganTransaction" /> public override void BeganTransaction(DbConnection connection, BeginTransactionInterceptionContext interceptionContext) { if (TransactionContext == null || !MatchesParentContext(connection, interceptionContext) || interceptionContext.Result == null) { return; } var transactionId = Guid.NewGuid(); var savedSuccesfully = false; var reinitializedDatabase = false; var objectContext = ((IObjectContextAdapter)TransactionContext).ObjectContext; ((EntityConnection)objectContext.Connection).UseStoreTransaction(interceptionContext.Result); while (!savedSuccesfully) { Debug.Assert(!Transactions.ContainsKey(interceptionContext.Result), "The transaction has already been registered"); var transactionRow = new TransactionRow { Id = transactionId, CreationTime = DateTime.Now }; Transactions.Add(interceptionContext.Result, transactionRow); TransactionContext.Transactions.Add(transactionRow); try { objectContext.SaveChangesInternal(SaveOptions.AcceptAllChangesAfterSave, executeInExistingTransaction: true); savedSuccesfully = true; } catch (UpdateException) { Transactions.Remove(interceptionContext.Result); TransactionContext.Entry(transactionRow).State = EntityState.Detached; if (reinitializedDatabase) { throw; } try { var existingTransaction = TransactionContext.Transactions .AsNoTracking() .WithExecutionStrategy(new DefaultExecutionStrategy()) .FirstOrDefault(t => t.Id == transactionId); if (existingTransaction != null) { transactionId = Guid.NewGuid(); Debug.Assert(false, "Duplicate GUID! this should never happen"); } else { // Unknown exception cause throw; } } catch (EntityCommandExecutionException) { // The necessary tables are not present. // This can happen if the database was deleted after TransactionContext has been initialized TransactionContext.Database.Initialize(force: true); reinitializedDatabase = true; } } } }