I was going through a piece of code and came across the following:
using(var transactionScope = new TransactionScope(TransactionScopeOption.Required, new TransactionScopeOptions { IsolationLevel = IsolationLevel.Snapshot })
{
List<Task> tasks = new List<Task>();
try
{
// Perform some database operation to read data (These operations are happening with a transaction scope having scopeoption as "Required" and isolationlevel as "ReadCommitted")
// Filter the data
// At this point the code already has a reference to a WCF duplex callback
// Create a List<Task> and a
foreach(var data in List<SomeData>)
{
var task = Task.Factory.StartNew(() => {
**(WCF Duplex Callback Instance).Process(data);**
});
tasks.Add(task);
}
}
catch(Exception ex)
{
// Log exception details
}
transactionScope.Complete();
}
try
{
Task.WaitAll(tasks);
}
catch(AggregateException ae)
{
ae.Handle( ex => {
// log exception details
return true;
});
}
Questions:
The parent transaction isolation level is "Snapshot" while the inner database reads are using "ReadCommitted". What will be the actual transaction isolation level?
Let's say there are two tasks. Task 1 processes just fine and sends to the WCF client on the callback channel. But task 2 raises an exception. I guess at this time all the activities performed within the parent transaction scope should rollback. But I'm not sure what it means to rollback a set of data already sent over the WCF callback channel that has reached the client.
1) It depends, if you mean nested TransactionScope's then according to MSDN you cannot have them nested with different isolation level:
When using nested TransactionScope objects, all nested scopes must be
configured to use exactly the same isolation level if they want to
join the ambient transaction. If a nested TransactionScope object
tries to join the ambient transaction yet it specifies a different
isolation level, an ArgumentException is thrown
However if you are using some stored procedures, functions or just running raw SQL you may explicitly change the isolation level and it remains set for that connection until it is explicitly changed again. But please note it will not be propagated back to TransactionScope object.
2) It means that all changes done via a resource manager will be rollbacked. Of course if you just query a database and transfer the results back over a channel there is nothing to rollback but if you update a database for example the changes should be rollbacked in this case.
Hope it helps!
Related
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.)
The following code is part of my business layer:
public void IncrementHits(int ID)
{
using (var context = new MyEntities())
{
using (TransactionScope transaction = new TransactionScope())
{
Models.User userItem = context.User.First(x => x.IDUser == ID);
userItem.Hits++;
try
{
context.SaveChanges();
transaction.Complete();
}
catch (Exception ex)
{
transaction.Dispose();
throw;
}
}
}
}
Sometimes (once or twice a week) I get a TransactionInDoubtException. Stacktrace:
at System.Transactions.TransactionStateInDoubt.EndCommit(InternalTransaction tx)
at System.Transactions.CommittableTransaction.Commit()
at System.Transactions.TransactionScope.InternalDispose()
at System.Transactions.TransactionScope.Dispose()
As far as I know, the default isolation level is serializable, so there should be no problem with this atomic operation. (Assuming there is no timeout occuring because of a write lock)
How can I fix my problem?
Use transaction.Rollback instead of transaction.Dispose
If you have a transaction in a pending state always rollback on exception.
msdn says "if the transaction manager loses contact with the subordinate participant after sending the Single-Phase Commit request but before receiving an outcome notification, it has no reliable mechanism for recovering the actual outcome of the transaction. Consequently, the transaction manager sends an In Doubt outcome to any applications or voters awaiting informational outcome notification"
Please look into these links
https://msdn.microsoft.com/en-us/library/cc229955.aspx
https://msdn.microsoft.com/en-us/library/system.transactions.ienlistmentnotification.indoubt(v=vs.85).aspx
Add finally block to dispose the transaction. And set some status = true if you commit the transactions.
Check same in finally block and if status is true then don't rollback otherwise rollback and dispose the transaction.
Would it behave properly if you moved your try/catch to include the using directive for the Transaction scope? The using directive should implicitly call Dispose for you (so doing it explicitly seems redundant) and then you can throw the exception. This is what I see implement in examples from MSDN:
https://msdn.microsoft.com/en-us/library/system.transactions.transactionscope.dispose(v=vs.110).aspx
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.
I've got a doubt about transactionscope because I'd like to make a transactional operation where first I perform some CRUD operations (a transaction which inserts and updates some rows on the DataBase) and I got a result from the whole transaction (an XML).
After I got the XML I send the XML to a Web Service which my customer exposes to integrate my system with.
The point is, let's imagine that one day the WS that my customer exposes falls down due to a weekly or monthly support task that its IT Area perform, so everymoment I perform the whole thing It performs the DB operation but of course It will throw an exception at the moment that I try to call the WS.
After Searching on the Internet I started to think of Transaction Scope. My Data Access Method which is on my Data Access Layer already has a TransactionScope where I perform insert, update, delete, etc.
The following Code is what I'd like to try:
public void ProcessSomething()
{
using (TransactionScope mainScope = new TransactionScope())
{
FooDAL dl = new FooDAL();
string message = dl.ProcessTransaction();
WSClientFoo client = new WSClientFoo();
client.SendTransactionMessage(message);
mainScope.Complete();
}
}
public class FooDAL
{
public string ProcessTransaction()
{
using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions(){ IsolationLevel = IsolationLevel.ReadCommitted}))
{
///Do Insert, Update, Delete and According to the Operation Generates a message
scope.Complete();
}
return transactionMessage;
}
}
The question is, is it correct to use TransactionScope to handle what I want to do ?
Thanks a lot for your time :)
TransactionScopeOption.Required in your FooDAL.ProcessTransaction method means in fact: if there is a transaction available, reuse it in this scope; otherwise, create a new one.
So in short: yes, this is the correct way of doing this.
But be advised that if you don't call scope.Complete() in FooDAL.ProcessTransaction, a call to mainScope.Complete() will crash with a 'TransactionAbortedException' or something like that, which makes sense: if a nested scope decides that the transaction cannot be committed the outer scope should not be able to commit it.
Is there a way to know in a wcf operation that a transaction has committed?
Ok, second attempt into being more specific.
I got a WCF service with an Operation with Transaction flow allow.
Now when a client call my wcf service it can have a transaction. But my service is also interested in the fact that the transaction on the client has succeeded. Because on my wcf service level, if everything went well. It has other things to do, but only if all transactions has been committed....
Is there like an event I can subscribe to or something?
It depends on the service itself and how you are handling transactions. If you are engaging in transactions in WCF through WS-Transaction then if the call to the client succeeds without exception, you can assume the transaction took place.
However, if this is in the context of another transaction, then you can't be sure if the transaction went through until the containing transaction is completed.
Even if you are using the TransactionScope class, if you have the service enabled to use transactions, you still have to take into account the encompassing transaction (if there is one).
You will have to provide more information about where the transaction is in relation to the call in order for a more complete answer.
Try using the operation behavior attribute above, in your operation that allows TransactionFlow:
[OperationBehavior(TransactionScopeRequired=true)]
If a transaction flows from the client, then the service will use it.
bool isTransactionComplete = true;
try
{
using (TransactionScope trScope = new TransactionScope(TransactionScopeOption.Required))
{
//some work
trScope.Complete();
}
}
catch (TransactionAbortedException e)
{
//Transaction holder got exception from some service
//and canceled transaction
isTransactionComplete = false;
}
catch//other exception
{
isTransactionComplete = false;
throw;
}
if (isTransactionComplete)
{
//Success
}
As casperOne wrote it depends on the settings. But you should be aware of complex transactions like
1) session service and simultaneous transactions for one service instance
2) transaction inside transaction