TransactionScope not rolling back although no complete() is called - c#

I'm using TransactionScope to rollback a transaction that fail
bool errorReported = false;
Action<ImportErrorLog> newErrorCallback = e =>
{
errorReported = true;
errorCallback(e);
};
using (var transaction = new TransactionScope())
{
foreach (ImportTaskDefinition task in taskDefinition)
{
loader.Load(streamFile, newErrorCallback, task.DestinationTable, ProcessingTaskId);
}
if (!errorReported)
transaction.Complete();
}
I'm sure there is no TransactionScope started ahead or after this code.
I'm using entity framework to insert in my DB.
Regardless the state of errorReported the transaction is never rolled back in case of error.
What am I missing ?

TransactionScope sets Transaction.Current. That's all it does. Anything that wants to be transacted must look at that property.
I believe EF does so each time the connection is opened for any reason. Probably, your connection is already open when the scope is installed.
Open the connection inside of the scope or enlist manually.
EF has another nasty "design decision": By default it opens a new connection for each query. That causes distributed transactions in a non-deterministic way. Be sure to avoid that.

Related

How to explicitly start a transaction in the flow of a TransactionScope?

In our code base, we use TransactionScope extensively to manage our transactions. We have code that could look like this in one part of our codebase:
// options declared elsewhere
using var transactionScope = new TransactionScope(TransactionScopeOption.Required, transactionScopeOptions, TransactionScopeAsyncFlowOption.Enabled);
await _repository.DeleteAll(cancellationToken);
// do more stuff, that might trigger a call to SaveChangesAsync somewhere
transactionScope.Complete()
Then, in our repository implementation, we may have something that looks like this:
public async Task DeleteAll(CancellationToken cancellationToken)
{
// This may not even be necessary
if (_dbContext.Database.GetDbConnection().State != ConnectionState.Open)
{
await _dbContext.Database.OpenConnectionAsync(cancellationToken);
}
await _dbContext.Database.ExecuteSqlRawAsync("DELETE FROM ThatTable", cancellationToken);
}
The documentation of ExecuteSqlRawAsync states that no transaction is started by that method. This leads me to my question: what is the proper way to start a transaction and have it enlisted in the transaction scope so that the call to Complete will commit this transaction along with the other work we have EF do?
As I understand, your goal is to run both DeleteAll (which uses ExecuteSqlRawAsync) and potential following SaveChangesAsync in the same transaction. If so - your code already achieves that.
Yes, ExecuteSqlRawAsync does not start a separate transaction, but you do not need another transaction, you are already inside a transaction, because you are inside TransactionScope. SqlClient (or whatever other provider for EF you use) will notice there is abmient Transaction.Current when connection is opened and will start a transaction. Both ExecuteSqlRawAsync and SaveChangesAsync will run inside that transaction and complete or rollback together (I verified it to be sure).
The comment about "doesn't start transaction" is more for situations like:
ExecuteSqlRawAsync("delete from sometable where id = 1;delete from sometable where id = 2;");
Where you indeed might want to run your sql inside a transaction (assuming one doesn't already exists), and so docs warn you that it will not do that for you.
I believe the best approach would be to do as follows (to start a transaction and stay within the transaction scope so that the call to complete will commit it):
using (var scope = new TransactionScope(...))
{
...
scope.Complete();
}
The transaction would begin as soon as you go within the brackets.

C# Rollback Outer TransactionScope Regardless of What Happens In Nested TransactionScopes

First of all, I have read this similar question: Nested/Child TransactionScope Rollback but the answer didn't provide a solution and our questions are slightly different.
Basically, I have a database integration test which uses a transaction scope (In actuality the scope is managed in setup/teardown in an abstract class)
[Test]
public void MyTest()
{
using(var outerScope = new TransactionScope())
{
Assert.True(_myService.MyMethod());
var values = _myService.AnotherMethod();
}
}
And MyService.MyMethod also uses a TransactionScope
public bool MyMethod()
{
using(var innerScope = new TransactionScope())
using(var con = conFact.GetOpenConnection())
{
var cmd = con.CreateCommand();
//set up and execute command
if (isCheck) scope.Complete();
return isCheck;
}
}
So in theory, MyMethod only commits its changes if isCheck is true, but regardless of whether that transaction commits or not, when the method is tested, it will be rolled back.
It works as expected unless isCheck is false, in which case I get the following exception: System.Transactions.TransactionException : The operation is not valid for the state of the transaction.
I think what happened here was that since innerScope used TransactionScopeOption.Required, it joined the transaction used in outerScope. Once innerScope gets disposed when isCheck is false, outerScope is also disposed (This is what I don't want to happen!) so when I try to get another connection after MyMethod has been called, the outerScope is already disposed.
Alternatively, if I specify TransactionOption.RequiresNew, I get this exception: System.Data.SqlClient.SqlException : Timeout expired.
I have tried using a SqlTransaction with a specified savepoint, and different combinations of TransactionOption to no avail.
There is no such thing as nested transactions. You can nest scopes but all that the nested scopes do is attach the the already running transaction. You cannot treat an inner scope independently from the other scope (except of course with RequiresNew which simply creates an independent transaction).
The functionality that you want does not exist in System.Transactions.
Savepoints are the only way to create something that looks like nested transactions. But then again SQL Server is prone to kill your entire transaction for arbitrary reasons. It is unpredictable what errors roll back the statement and what errors roll back the transaction. (Yes, this makes no sense.)

TransactionScope and Entity Framework

I need to wrap some pieces of code around a TransactionScope. The code inside this using statement calls a managed C++ library, which will call some unmanaged code. I do also want to update my database, which is using Entity Framework.
Here comes the problem, when doing SaveChanges on the DbContext inside the TransactionScope I always get some sort of Timeout exception in the database layer. I've googled this, and it seems to be a fairly common problem but I haven't found any applicable answers to my problem. This is a snippet of my code
using (var transactionScope = new TransactionScope())
{
try
{
//Do call to the managed C++ Library
using (var dbContext = _dbContextFactory.Create())
{
//doing some CRUD Operations on the DbContext
//Probably some more dbContext related stuff
dbContext.SaveChanges(); //Results with a timeout
}
}
catch (Exception)
{
transactionScope.Dispose();
throw;
}
}
I'm using Entity Framework 6.1.3 so I can access the BeginTransaction on the database, but I also need wrap the C++ calls inside a TransactionScope.
Any suggestions?
You will need to pass in TransactionScopeOptions defining your timeout (How long to keep the transaction open). An example for the absolute upper limit of timeouts would be:
TransactionOptions to = new TransactionOptions();
to.IsolationLevel = IsolationLevel.ReadCommitted;
to.Timeout = TransactionManager.MaximumTimeout;
using (TransactionScope ts = new TransactionScope(TransactionScopeOption.Required, to)) { }
You should definitely be aware of the impact of such a long-running transaction, this isn't typically required, and I'd highly recommend using something below MaximumTimeout which reflects how long you expect it to run. You should do your best to keep the time period for which the transaction is held as small as possible, doing any processing that doesn't have to be a single transaction outside the transaction scope.
It's also worth noting that depending on the underlying database it can enforce it's own limitations on transaction durations if configured to do so.

Why does this TransactionScope not block subsequent requests until the first request is done?

I've been looking into transactions for two days now, perhaps I'm missing something obvious after taking in so much information. The goal here is to block simultaneous request. If condition is true, data is inserted, after which condition will be false. Simultaneous requests will both check condition before data could be inserted and then will both try to insert data.
public async Task<ActionResult> Foo(Guid ID)
{
Debug.WriteLine("entering transaction scope");
using (var transaction = new TransactionScope(
TransactionScopeOption.Required,
new TransactionOptions() { IsolationLevel = IsolationLevel.Serializable },
TransactionScopeAsyncFlowOption.Enabled
))
{
Debug.WriteLine("entered transaction scope");
var context = new DbContext();
Debug.WriteLine("querying");
var foo = context.Foos.FirstOrDefault(/* condition */);
Debug.WriteLine("done querying");
context.Foos.Add(new Foo());
/* async work here */
Debug.WriteLine("saving");
context.SaveChanges();
Debug.WriteLine("saved");
Debug.WriteLine("exiting transaction scope");
transaction.Complete();
Debug.WriteLine("exited transaction scope");
return View();
}
}
This is the debug output when executing two requests at once with Fiddler:
entering transaction scope
entered transaction scope
querying
done querying
entering transaction scope
entered transaction scope
querying
done querying
saving
saving
saved
A first chance exception of type 'System.Data.SqlClient.SqlException' occurred in System.Data.dll
exiting transaction scope
exited transaction scope
This is my understanding of how the code is supposed to work:
A transaction with serialisable isolation level is required, to prevent phantom reads.
I cannot use DbContext.Database.Connection.BeginTransaction, when executing a query an error is thrown:
ExecuteReader requires the command to have a transaction when the connection assigned to the command is in a pending local transaction.
The Transaction property of the command has not been initialized.
TransactionScope normally causes causes problems when using task-based async.
http://entityframework.codeplex.com/discussions/429215
However, .NET 4.5.1 adds additional constructors to deal with this.
How to dispose TransactionScope in cancelable async/await?
EF 6 has improved transaction support, but I'm still on EF 5.
http://msdn.microsoft.com/en-us/data/dn456843.aspx
So obviously it's not working like I want it to. Is it possible to achieve my goal using TransactionScope? Or will I have to do an upgrade to EF 6?
Serialization does not solve the old 'check then insert' problem. Two serializable transactions can concurrently evaluate the condition, conclude that they have to insert, then both attempt to insert only for one to fail and one to succeed. This is because all reads are compatible with each other under serializable isolation (in fact they are compatible under all isolation levels).
There are many schools of thought how to solve this problem. Some recommend using MERGE. Some recommend using a lock hint in the check query to acquire an X or U lock instead. Personally I recommend always INSERT and gracefully recover the duplicate key violation. Another approach that does work is using explicit app locks.
EF or System.Transactions really add only noise tot he question. This is fundamentally a back-end SQL problem. As for the problem of how to flow a transaction scope between threads see Get TransactionScope to work with async / await (obviously, you already know this, from reading the OP... I didn't register in first read). You will need this to get your async code to enlist in the proper context, but the blocking/locking is fundamental back end problem, still.

Nhibernate Reading/Saving Session Pattern, what is the exact transaction behaviour?

All,
This is the pattern used to read/write data using n-hibernate session:
using(var session = factory.OpenSession())
using(var tx = session.BeginTransaction())
{
// all the code that uses the session goes here
// use session to load and/or save entity
}
My question is:
When you start a transaction, it starts a database transaction on the connection but I don't see a corresponding commit statement, rather the using statement on tx will call tx.Dispose(). Looking at n-hibernate source (from AdoTransaction class that uses SqlTransaction object), the underlying transaction object is never commited but disposed as you can see below. So, are we to assume that SqlTransaction provider will call commit before disposing? Is that somewhere document for SqlClient provider for ado.net?
if (isDisposing)
{
if (trans != null)
{
trans.Dispose();
trans = null;
log.Debug("IDbTransaction disposed.");
}
if (IsActive && session != null)
{
// Assume we are rolled back
AfterTransactionCompletion(false);
}
}
Clearly, I am missing something since I could not locate the code that flushes the session when tx.Dispose() is called.
The idea there is that you are supposed to call CommitTransaction yourself as part of the using block, as when the garbage collector comes around to Dispose the object, there could be the case that the transaction itself threw an error, in that instance you don't want the Dispose to commit it, you just want to dispose it.

Categories

Resources