Data committed even though System.Transactions.TransactionScope.Commit() not called - c#

Under what circumstances can code wrapped in a System.Transactions.TransactionScope still commit, even though an exception was thrown and the outermost scope never had commit called?
There is a top-level method wrapped in using (var tx = new TransactionScope()), and that calls methods that also use TransactionScope in the same way.
I'm using typed datasets with associated tableadapters. Could it be that the commands in the adapter aren't enlisting for some reason? Do any of you know how one might check whether they are enlisting in the ambient TransactionScope or not?

The answer turned out to be because I was creating the TransactionScope object after the SqlConnection object.
I moved from this:
using (new ConnectionScope())
using (var transaction = new TransactionScope())
{
// Do something that modifies data
transaction.Complete();
}
to this:
using (var transaction = new TransactionScope())
using (new ConnectionScope())
{
// Do something that modifies data
transaction.Complete();
}
and now it works!
So the moral of the story is to create your TransactionScope first.

The obvious scenario would be where a new (RequiresNew) / null (Suppress) transaction is explicitly specified - but there is also a timeout/unbinding glitch that can cause connections to miss the transaction. See this earlier post (the fix is just a connection-string change), or full details.

Be aware how TransactionScope works:
It sets property System.Transactions.Transaction.Current at the begining of using scope and then set it back to previous value at end of using scope.
Previous value depends on where given scope is declared. It can be inside another scope.
You can modify code like this:
using (var sqlConnection = new ConnectionScope())
using (var transaction = new TransactionScope())
{
sqlConnection.EnlistTransaction(System.Transactions.Transaction.Current);
// Do something that modifies data
transaction.Complete();
}
I show this possibility for those who have their code more complicated and cannot simply change code to open DB connection first.

This example (C#, .NetFramework 4.7.1) shows how we can persist to the database even if the code is wrapped in a TransactionScope. The first insert will be rolled back, and the second insert will be inserted without transaction.
See my related post, where I ask for help in how to detect this.
using (var transactionScope = new TransactionScope())
{
using (var connection = new SqlConnection("Server=localhost;Database=TestDB;Trusted_Connection=True"))
{
connection.Open();
new SqlCommand($"INSERT INTO TestTable VALUES('This will be rolled back later')", connection).ExecuteNonQuery();
var someNestedTransaction = connection.BeginTransaction();
someNestedTransaction.Rollback();
new SqlCommand($"INSERT INTO TestTable VALUES('This is not in a transaction, and will be committed')", connection).ExecuteNonQuery();
}
throw new Exception("Exiting.");
transactionScope.Complete();
}

Related

EF using and BeginTransaction, when to call Connection.Close()?

After opening a connection, Do I have to explicitly close it? or does .net close it automatically? What about if an exception is thrown?
using (DataContext pContext = new DataContext())
{
pContext.Connection.Open()
using (var trx = pContext.Connection.BeginTransaction())
{
//make changes to objects
//Make calls to ExecuteStoreCommand("Update table SET pee=1 AND poop=2 WHERE ETC");
pContext.SaveChanges();
trx.Commit();
}
}
Does pContext.Connection.Close()` get called in all instances by the framework? I know that in a normal using statement it does, but what about when I manually open it?
I am using the Connection.BeginTransaction because In my tests, (using SaveChanges() alone without transactions) if this code executes and fails for some reason, the commands sent during ExecuteStoreCommand() are saved even though the changes to my objects aren't.
I have to call Connection.Open() before calling pContext.Connection.BeginTransaction() otherwise I get the error:
Inner Exception:
The connection is not open.
Any help would be appreciated.
There is no need to call DataContext.Connection.Open(). In fact, it's bad practice to do so.
The context opens a connection only when it needs to. By calling Connection.Open, you open the connection for far longer that is needed, increasing the chance that locks will accumulate and lead to blocking. It is also a good way to exhaust the connection pool in high traffic systems.
SaveChanges opens a connection and a transaction itself, so there is no need to manually open the connection or start the transaction. From the documentation:
SaveChanges operates within a transaction. SaveChanges will roll back that transaction and throw an exception if any of the dirty ObjectStateEntry objects cannot be persisted
Explicitly using connections and transactions makes sense only if you want to mix raw SQL commands inside the EF session. By itself this is not a good practice, but if you do, you don't need to close the connection because the DataContext will close the connection when it is disposed. There is a sample in the documentation, at How to: Manually Open the Connection from the Object Context
You can also handle a transaction using a TransactionScope, as described in How to: Manage Transactions in the Entity Framework. In this case, you don't need to open the connection as all changes are automatically enlisted in the current transaction. This is described in http://msdn.microsoft.com/en-us/library/bb738523(v=vs.100).aspx and an example would be :
using (DataContext pContext = new DataContext())
{
using (TransactionScope transaction = new TransactionScope())
{
//execute commands
pContext.SaveChanges();
transaction.Complete()
}
}
In any case, manually handling connections and transactions is not trivial. You should read Managing Connections and Transactions for some of the things you need to be aware
This means
using (DataContext pContext = new DataContext())
that pContext is disposed after leaving using
it's equivalent to:
DataContext pContext = new DataContext()
try
{
//do stuff here
}
finally
{
if(pContext!=null)
((IDisposable)pContext).Dispose();
}
so answer is -> you don't need to call close after your code because
Connections are released back into the pool when you call Close or Dispose on the Connection..."
from here

TransactionScope, EF DbContext, and Dirty Read

I've been Googling for an answer for the past few hours, but haven't found an answer, so I'm hoping someone here can point me in the right direction.
I am wondering how to do a dirty read with EF DbContext (Code-First) within a TransactionScope. For example
DbContext context = new MyEntities();
using(TransactionScope scope = new TransactionScope())
{
context.SomeTable.Add(someObject);
context.SaveChanges();
var objects = context.SomeTable.Where(...).Select(...); //times out here on the read, because the write above locks the tables
//modify each object
context.SaveChanges();
scope.Complete(); //transaction is a success only if the entire batch succeeds
}
I have tried wrapping the read call with the following:
using(TransactionScope scope2 = new TransactionScope(TransactionScopeOption.RequiresNew, new TransactionOptions{IsolationLEvel = IsolationLevel.ReadUncommitted}))
{
var objects = context.SomeTable.Where(...).Select(...); //times out here on the
}
What is the proper approach?
It has finally clicked for me what the problem is: EF is not integrating with TransactionScope the way L2S nicely does. This means that EF opens and closes the connection for each operation requiring the server (saving changes or querying).
This gets you distributed transactions and a distributed deadlock.
To solve this, manually open and close the EF StoreConnection to ensure that there is exactly one connection for the duration of the transaction.
You can set the IsolationLevel for TransactionScope like this...
var transactionOptions = new System.Transactions.TransactionOptions();
transactionOptions.Timeout = new TimeSpan(0, 0, 30);
transactionOptions.IsolationLevel = System.Transactions.IsolationLevel.ReadUncommitted;
using (var transactionScope = new System.Transactions.TransactionScope(System.Transactions.TransactionScopeOption.Required, transactionOptions))
{
...
}
You can just execute alter database your_db_name set READ_COMMITTED_SNAPSHOT on;(available from SQLServer 2005) on the server, and readers won't be blocked by writters (assuming reading transaction in read committed isolation level).

EF Update Formviews in a Transaction

I want to update two formviews within a transaction. if one of them fails the other should fail too. Formviews have their own entity datasources.
button1_click(..........)
{
formview1.updateItem(true);
formview2.updateItem(true);
}
Ok so this may not be the simplest thing in the world.
The basic answer is that yes you can do it. And the code would be similar to this if the updateItem method opens the db connection.
using (TransactionScope scope = new TransactionScope())
{
formview1.updateItem(true);
formview2.updateItem(true);
scope.Complete();
}
If on the other hand the connections are already open by the time updateItem is called then you will need to do something more like
using (TransactionScope scope = new TransactionScope())
{
formview1.Connection.EnlistTransaction(Transcation.Current);
formview2.Connection.EnlistTransaction(Transcation.Current);
formview1.updateItem(true);
formview2.updateItem(true);
scope.Complete();
}

Using TransactionScopeOption.Suppress with Sql Server Compact 4

I'm having trouble suppressing part of a transaction using Sql Server CE 4 with Entity Framework and System.Transactions.TransactionScope.
The simplified code below is from a unit test demonstrating the problem.
The idea is to enable the innerScope block (with no transaction) to succeed or fail without affecting the outerScope block (the "ambient" transaction). This is the stated purpose of TransactionScopeOption.Suppress.
However, the code fails because it seems that the entire SomeTable table is locked by the first insert in outerScope. At the point indicated in the code, this error is thrown:
"SQL Server Compact timed out waiting for a lock. The default lock time is 2000ms for devices and 5000ms for desktops. The default lock timeout can be increased in the connection string using the ssce: default lock timeout property. [ Session id = 2,Thread id = 2248,Process id = 13516,Table name = SomeTable,Conflict type = x lock (x blocks),Resource = PAG (idx): 1046 ]"
[TestMethod()]
[DeploymentItem("MyLocalDb.sdf")]
public void MyLocalDb_TransactionSuppressed()
{
int count = 0;
// This is the ambient transaction
using (TransactionScope outerScope = new TransactionScope(TransactionScopeOption.Required))
{
using (MyObjectContext outerContext = new MyObjectContext())
{
// Do something in the outer scope
outerContext.Connection.Open();
outerContext.AddToSomeTable(CreateSomeTableRow());
outerContext.SaveChanges();
try
{
// Ambient transaction is suppressed for the inner scope of SQLCE operations
using (TransactionScope innerScope = new TransactionScope(TransactionScopeOption.Suppress))
{
using (MyObjectContext innerContext = new MyObjectContext())
{
innerContext.Connection.Open();
// This insert will work
innerContext.AddToSomeTable(CreateSomeTableRow());
innerContext.SaveChanges(); // ====> EXCEPTION THROWN HERE
// There will be other, possibly failing operations here
}
innerScope.Complete();
}
}
catch { }
}
outerScope.Complete();
}
count = GetCountFromSomeTable();
// The insert in the outer scope should succeed, and the one from the inner scope
Assert.AreEqual(2, count);
}
So, it seems that "a transaction in a transaction scope executes with isolation level set to Serializable", according to http://msdn.microsoft.com/en-us/library/ms172001
However, using the following code snippet to change the isolation level of the TransactionScope does not help:
public void MyLocalDb_TransactionSuppressed()
{
TransactionOptions opts = new TransactionOptions();
opts.IsolationLevel = IsolationLevel.ReadCommitted;
int count = 0;
// This is the ambient transaction
using (TransactionScope outerScope = new TransactionScope(TransactionScopeOption.Required, opts))
...
The same exception is thrown at the same location.
It seems the only way to avoid this is to call outerScope.Complete() before entering the innerScope block. But this would defeat the purpose.
What am I missing here?
Thanks.
AFAIK SQL Server Compact does not support nested transactions.
And why do you do that this way? If I look at your code there is no difference between running the second transaction scope inside the first one and running them in sequence.
IMHO this is not a problem of SQL Compact, TransactionScope or isolation level. This is a problem of your wrong application logic.
Each SaveChanges runs in transaction - either outer transaction defined by TransactionScope or inner DbTransaction. Even if it would not create transaction every database command has its own implicit transaction. If you use Suppress on the inner code block you are creating two concurrent transactions which are trying to insert into same table and moreover the first cannot complete without completing the second and the second cannot complete without completing the first => deadlock.
The reason is that insert command always locks part of the table not allowing new inserts until it is committed or rolled back. I'm not sure if this can be avoided by changing transaction isolation level - if it does, you will most probably need Read.Uncommitted.

TransactionScope problem with Message queues

I'm facing the next problem. I have a piece of code that goes like this:
DoSomething(){
using (TransactionScope scope = new TransactionScope())
{
InsertSomething();
InsertSomethingElse();
InsertYetAnotherThing();
ProcessDataRelatedWithThePreviousInserts();
scope.Complete()
}
}
In ProcessDataRelatedWithThePreviousInserts I check for a condition and if needed, the rest of the work flow is redirected to a Message Queue in other server. In the other server, I recover the message, and continue the workflow (basically, make some other insertions that are related with the ones on the DoSomething method).
This is in theory, because I only manage to do that if I remove the TransactionScope in the DoSomething method. Is there a way to accomplish what I want to do or I'll need to change the way the transactions are handled?
Did you already try
using (TransactionScope scope = new TransactionScope(TransactionScopeOption.RequiresNew))
{
// ...
using (TransactionScope innerscope = new TransactionScope(TransactionScopeOption.Supress)
{
ProcessDataRelatedWithThePreviousInserts();
}
scope.Complete();
}
explicitly supressing the outer transaction for your call to ProcessDataRelatedWithThePreviousInserts().

Categories