Change the TransactionScope IsolationLevel after completing the transaction - c#

When i save data in the database i used TransactionScope with IsolationLevel set to Serializable.
TransactionOptions options = new TransactionOptions
{
IsolationLevel=IsolationLevel.Serializable
};
using (var transaction = new TransactionScope(TransactionScopeOption.Required,options))
{
transation.Complete();
}
Now after the execution is over, i want to change the TransactionScopeIsolationLevel.
Edit
What I understand is this, if IsolationLevel set to Serializable then after completing the transaction, the connection object is closed and return to connection pool and when some other request arrives it fetch that connection object from the pool and thus effected by the previous IsolationLevel. So i want to change isolation level to default after every transaction.

You're right: the isolation level is not reset when returning a connection to the pool. This is horrible behavior but we are stuck with it...
There are two strategies:
Reset the isolation level before returning: This is your approach.
Always use a connection with an explicit transaction (or TransactionScope) so that the isolation level is guaranteed.
I recommend you do the latter.
If you insist on doing (1) you can simply change the isolation level after closing the TransactionScope but you have to do this with the connection object. Example:
using (SqlConnection connection = new SqlConnection(connectionString))
{
using (var transaction = new TransactionScope(TransactionScopeOption.Required,options))
{
connection.Open(); //open inside of scope so that the conn enlists itself
transation.Complete();
}
//conn is still open but without transaction
conn.ExecuteCommand("SET TRANSACTION ISOLATION LEVEL XXX"); //pseudo-code
} //return to pool
Does this work for you?

I've been bit by this. Luckily the Connection String logic was centralized. What I did was to change the connection string's application setting if Transaction.Current is not null (which would imply that we're inside a TransactionScope).
This way the TransactionScope connections don't pool with the others.

Related

How to force extenal library to use SQL transaction

I have an external library to which I pass an instance of System.Data.SqlClient.SqlConnection and I want to wrap everything that library does on that connection in a transaction. When I was working with php/doctrine I would simply do exactly that in such cases - start a transaction in my code, call stuff on the library which issues DB queries and then commit the transaction in my code. When I tried to use this approach in C#, I got the following exception:
ExecuteScalar 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.
So I took a look at the library code and it always uses SqlCommand without setting the Transaction property. Is it possible to achieve my goal somehow? (changing the library code isn't feasible)
You haven't posted your code but I assume you tried to use an explicit transaction by calling SqlConnection.BeginTransaction().
You can use a TransactionScope to create an implicit transaction. Any connection, command created inside the TransactionScope's lifetime will be enlisted in a transaction automatically.
Copying from Implementing an Implicit Transaction using Transaction Scope's example:
// Create the TransactionScope to execute the commands, guaranteeing
// that both commands can commit or roll back as a single unit of work.
using (TransactionScope scope = new TransactionScope())
{
using (SqlConnection connection1 = new SqlConnection(connectString1))
{
// Opening the connection automatically enlists it in the
// TransactionScope as a lightweight transaction.
connection1.Open();
// Create the SqlCommand object and execute the first command.
SqlCommand command1 = new SqlCommand(commandText1, connection1);
returnValue = command1.ExecuteNonQuery();
writer.WriteLine("Rows to be affected by command1: {0}", returnValue);
// If you get here, this means that command1 succeeded. By nesting
// the using block for connection2 inside that of connection1, you
// conserve server and network resources as connection2 is opened
// only when there is a chance that the transaction can commit.
using (SqlConnection connection2 = new SqlConnection(connectString2))
{
// The transaction is escalated to a full distributed
// transaction when connection2 is opened.
connection2.Open();
// Execute the second command in the second database.
returnValue = 0;
SqlCommand command2 = new SqlCommand(commandText2, connection2);
returnValue = command2.ExecuteNonQuery();
writer.WriteLine("Rows to be affected by command2: {0}", returnValue);
}
}
// The Complete method commits the transaction. If an exception has been thrown,
// Complete is not called and the transaction is rolled back.
scope.Complete();
}
The connection and both commands in this example run under a single transaction. Should an exception occur, the transaction will be rolled back.
In .NET you can use a TransationScope, and everything will happen in the same transaction:
using (TransactionScope scope = new TransactionScope())
{
// Everything inside this block will be transactional:
// Call the libraries which will use your SqlConnection here
}
Or you can use the BeginTransaction before calling the other library functions, and commit it after the function calls

What isolation level does autocommit transaction use in SQL Server?

When I work with update conflict problem with SNAPSHOT Isolation Level, it seems that autocommit transactions use the isolation level used at last time.
Condition: ALLOW_SNAPSHOT_ISOLATION is ON, READ_COMMITTED_SNAPSHOT is OFF
Step 1 : Execute update statement without transaction
using (var sqlconn = new SqlConnection("Data source=..."))
using (var sqlcmd = sqlconn.CreateCommand())
{
sqlconn.Open();
sqlcmd.CommandText = "Update ..."
sqlcmd.ExecuteNonQuery();
}
Then I take a look in sys.md_exec_sessions and found the transaction isolation level is READCOMMITTED.
Step 2 : Execute update statement with transaction in SNAPSHOT isolation level
using (var sqlconn = new SqlConnection("Data source=..."))
{
sqlconn.Open();
using (var sqltran = sqlconn.BeginTransaction(IsolationLevel.Snapshot))
using (var sqlcmd = sqlconn.CreateCommand())
{
sqlconn.Open();
sqlcmd.CommandText = "Update ..."
sqlcmd.ExecuteNonQuery();
}
}
The isolation level is Snapshot, works good.
Step 3 : do step 1 again
The isolation level is Snapshot.
I expect step 3 shows READCOMMITTED cause READ_COMMITTED_SNAPSHOT is OFF.
There is two ideas that I supposed, but I cannot conclude.
dm_exec_sessions will not contains information about autocommit transactions
autocommit transaction actually uses SNAPSHOT
Any idea will be appreciated.
Thanks,
There isn't a specific isolation level for autocommit transactions. They use whatever isolation level has last been declared for a connection (or the server default).
Unfortunately, however, in the face of connection pooling1, what you think of as a "new" connection may in fact be a re-used one. So in some circumstances, you'll pick up a connection that uses an isolation level different from the SQL Server default (Read Committed).
My advice would be - if you use explicit isolation levels anywhere, you also need to make sure that you use different connection string, or you need to turn off connection pooling, if you don't want to explicitly set the isolation level everywhere. I'd usually prefer the first, so that you can still benefit from connection pooling, but have separate pools for each required isolation level.
1 Connect Issue which seems to indicate that this may or may not be true for SQL Server 2014 and later.

Disable read/write to a table via SqlTransaction in .net?

How to use SqlTransaction in .net 2.0 so that when I start reading data from a table, that table is blocked for others (other programs) to read/write to that table?
If SqlTransaction is not a good option, than what is?
This should be allowed by using Serializable transaction together with TABLOCKX hint in initial select statement. TABLOCKX should take exclusive lock on the table so nobody else can use that table and Serializable transaction should demand HOLDLOCK which means that all locks are kept until end of the transaction (you can use HOLDLOCK directly).
Update: I just tested different scenarios in Management studio and it
looks like you do not need to
explicitly use Serializable
transaction. Using TABLOCKX within any
transaction is enough.
Be aware that such approach can be big bottleneck because only one transaction can operate on such table = no concurrency. Even if you read and work with single record from million nobody else will be able to work with the table until your transaction ends.
So the command should look like:
SELECT * FROM Table WITH (TABLOCKX) WHERE ...
To use serializable transaction you can use SqlTransaction:
using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open();
SqlTransaction transaction = connection.BeginTransaction(IsolationLevel.Serializable);
try
{
...
transaction.Commit();
}
catch (Exception)
{
transaction.Rollback();
...
}
}
Or System.Transactions.TransactionScope (default isolation level should be Serializable).
using (TransactionScope scope = new TransactionScope())
{
using (SqlConnection connection = new SqlConnection(connectionString))
{
...
}
scope.Complete();
}

Is there a way to use TransactionScope with an existing connection?

I have some code that works like the advised use of TransactionScope, but has an ambient connection instead of an ambient transaction.
Is there a way to use a TransactionScope object with an existing connection, or is there an alternative in the .Net framework for this purpose?
In fact, there is one way.
connection.EnlistTransaction(Transaction.Current)
It works and it doesnt promote transaction to distributed if not necessary (contrary to what documentation says)
HTH
To enlist a connection into a TransactionScope, you need to specify 'Enlist=true' in its connection string and open the connection in the scope of that TransactionScope object.
You can use SqlConnection.BeginTransaction on an existing connection.
Update: Can you use BeginTransaction like this:
using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open();
SqlCommand command = connection.CreateCommand();
SqlTransaction transaction;
// Start a local transaction.
transaction = connection.BeginTransaction("SampleTransaction");
// Must assign both transaction object and connection
// to Command object for a pending local transaction
command.Connection = connection;
command.Transaction = transaction;
...
...
}
After more research, the answer to my question turned out to be:
No, the connection needs to be opened after the TransactionScope object is instantiated.

Is there a way to create an ADO.NET connection and ignore the ambient Transaction?

I have a situation where I am running in a WCF service that has TransactionScopeRequired=true, which means there will always be an ambient transaction.
However, I need to start a new connection that will be around for the lifetime of the application, which means I can't have it use the abmbient transaction.
Any ideas on how to do this? Just doing this will automatically use the ambient transaction:
Assert.IsNotNull(System.Transactions.Transaction.Current);
var conn = new OracleConnection("my connection string");
conn.Open(); // <-- picks up ambient transaction, but I don't want that
Actually the example could be made simpler by saying this:
OracleConnection conn; // <-- this is actually held around in another object that has a very long lifetime, well past the TransactionScope.
using(var tx = new TransactionScope())
{
conn = new OracleConnection("my connection string");
conn.Open(); // <-- picks up ambient transaction, but I don't want that
// ... do stuff
}
I don't want my connection to actually pick up the TransactionScope. In the actual code there is a lot more going on that does do DB actions within the scope, I just have 1 that I need to keep around past the lifetime of the transaction scope.
I guess the real situation is worth mentioning. What actually happens here is that during a WCF service call, I add an object to a cache using the Enterprise Library Caching block. This object is a data table, but also holds on to an open connection to Oracle that has Continuous Notification set up. This gives me the ability to automatically refresh my cached dataset when the underlying Oracle tables change.
The data cache item can be accessed by any number of WCF initialized threads, all of which run in their own transaction scope. I guess you could think of it as putting an OracleConnection object in a cache. A better block of text/exampe code would be like:
//beginning of a WCF service call
using (var tx = new TransactionScope())
{
var conn = new OracleConnection();
var cmd = new OracleCommand();
// set up OCN on the cmd and connection
var reader = cmd.ExecuteReader();
cache.Add("conn", conn);
cache.Add("cmd", cmd);
}
//beginning of a second wcf service call
using (var tx = new TransactionScope())
{
var conn = cache.Get("conn");
var cmd = cache.Get("cmd");
var reader = cmd.ExecuteReader();
// user reader to reload some data
}
Point being I have a connection that has a long lifetime across multiple threads and transaction scopes.
Have you tried one of the TransactionScope constructors that allows you to set the scope? Setting the scope to "Requires New" creates a new transaction for your connection to enlist in. Setting the scope to "Suppress" makes it so that your connection doesn't enlist in any transaction. At least, thats how I read the documentation. I've never had that specific need, myself.
using(var tx = new TransactionScope(TransactionScopeOption.RequiresNew))
{
conn = new OracleConnection("my connection string");
conn.Open();
}

Categories

Resources