InvalidOperationException when calling Transaction.Current - c#

I have code inside my DatabaseClient class that checks whether or not there is an active TransactionScope by examining the ThreadStatic property, Transaction.Current:
if (Transaction.Current == null)
{
// open a new connection and do things
}
I have code consuming this class that creates a TransactionScope, executes two database operations, and then completes it. The application then moves on to do further database work. But now when it calls the code above, I get an exception:
System.InvalidOperationException: The current TransactionScope is already complete.
What do I need to do in order to "reset" the current transaction so that I can check Transaction.Current safely again?

When a transaction has been rolled back or committed, it can't be reused. Create a new one.
Some sample code here:
https://learn.microsoft.com/en-gb/dotnet/api/system.transactions.transactionscope?view=netframework-4.7

Related

How do I make a scope that is independent of any containing scope *and* does *not* start a new transaction?

The following code opens a new transaction scope independent of any containing scope:
using (TransactionScope scope = new(TransactionScopeOption.Suppress, asyncFlowOption: TransactionScopeAsyncFlowOption.Enabled))
{
var connection = _connectionFactory.GetConnection()
await connection.QueryAsync(...); // e.g. call DB with Dapper
await connection.QueryAsync(...); // e.g. another call
}
(using Dapper)
How do I make a scope that is independent of any containing scope and does not start a new transaction? Essentially, I want the "suppress" option as above, but in a "non-transaction scope".
You can run a thread without passing it a DependentTransaction. It will be independent. Create a function that runs the queries and use this:
using System.Threading.Tasks;
// ...
var task = new Task(FunctionWithQueries);
task.RunSynchronously();
Transaction.Current is null inside TransactionScope with Suppress option, so connection can't start new transaction because of scope - it has no way to find out scope even exists. If it starts new transaction it must be for another reason.
Note that above I assume you open fresh connection inside suppressed transaction scope, because you use:
var connection = _connectionFactory.GetConnection()
If you do NOT open fresh connection - then Suppress scope essentially does nothing in this case. Ambient transaction is null yes, but your connection already started a transaction before, and so your code inside suppress block will still run inside this same transaction (or it will just fail, for example Entity Framework will notice this situation and throw an exception).

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.

How to know if transaction scope was successful or not

I need to know if my transaction scope was successful or not. As in if the records were able to be saved in the Database or not.
Note: I am having this scope in the Service layer, and I do not wish to include a Try-Catch block.
bool txExecuted;
using (var tx = new TransactionScope())
{
//code
// 1 SAVING RECORDS IN DB
// 2 SAVING RECORDS IN DB
tx.Complete();
txExecuted = true;
}
if (txExecuted ) {
// SAVED SUCCESSFULLY
} else {
// NOT SAVED. FAILED
}
The commented code will be doing updates, and will probably be implemented using ExecuteNonQuery() - this returns an int of the number of rows affected. Keep track of all the return values to know how many rows were affected.
The transaction as a whole will either succeed or experience an exception when it completes. If no exception is encountered, the transaction was successful. If an exception occurs, some part of the transaction failed; none of it took place
By considering these two facts (records affected count, transaction exception or no) you can know if the save worked and how many rows were affected
I didn't quite understand the purpose of txExecuted- if an exception occurs it will never be set and the if will never be considered. The only code that will thus run is the stuff inside if(true). I don't see how you can decide to not use a try/catch and hope to do anything useful with a system that is geared to throw an exception if something goes wrong; you saying you don't want to catch exceptions isn't going to stop them happening and affecting the control flow of your program
To be clear, calling the Complete() method is only an indication that all operations within the scope are completed successfully.
However, you should also note that calling this method does not
guarantee a commit of the transaction. It is merely a way of informing
the transaction manager of your status. After calling this method, you
can no longer access the ambient transaction via the Current property,
and trying to do so results in an exception being thrown.
The actual work of commit between the resources manager happens at the
End Using statement if the TransactionScope object created the
transaction.
Since you are using ADO.NET, ExecuteNonQuery will return the number of rows affected. You can do a database lookup after the commit and outside of the using block.
In my opinion, its a mistake not to have a try/catch. You want to catch the TransactionAbortedException log the exception.
try
{
using (var scope = new TransactionScope())
{
using (var conn = new SqlConnection("connection string"))
{
}
// The Complete method commits the transaction. If an exception has been thrown,
// Complete is not called and the transaction is rolled back.
scope.Complete();
}
}
catch (TransactionAbortedException ex)
{
// log
}

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.

WCF Distributed Transaction Flow using normal return values

I have got a service that should use distributed transactions.
[OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)]
public bool ServiceMethod(int parameterPlaceHolder)
{
return SomeOperationResult();
}
For reasons out of my responsibility, this service should never throw faults. On success it returns one value, on failure another (abstracted to a bool here for demo purposes).
The transaction flowing works.
However, the attribute implies that any result that is not an uncaught exception will complete the transaction. That's not the behavior I want. I want to control the outcome of the transaction myself. On returning false, I want to have the transaction fail.
I have tried various methods:
The obvious one: setting TransactionAutoComplete to false. This means that I have to use a session based service. I don't want to. I don't need to. I'm perfectly fine with a single transaction scope per call. But it's not allowed. ("TransactionAutoComplete set to false requires the use of InstanceContextMode.PerSession.")
The DIY one: setting TransactionScopeRequired to false and using my own. This means the flowed transactions no longer work and I create a new local transaction every time.
The desperate one: Trying to get hold of the transaction that WCF creates and rolling it back on my own... this leads to my service throwing exceptions because it tries to AutoComplete a transaction that is long gone.
I'm out of ideas. Does anyone know how to create my own transaction scope, using a flowed distributed transaction, not using the Microsoft AutoComplete-On-Normal-Return pattern? I would like to not complete the transaction without throwing an exception.
Transaction scopes can nest. The entire transaction aborts if you don't completed any scope. So:
using (new TransactionScope()); //Doom transaction
Better comment this line.
You can also try to call stuff on Transaction.Current but I have no experience with that.

Categories

Resources