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

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.

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

Change the TransactionScope IsolationLevel after completing the transaction

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.

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();
}

ado.net Closing Connection when using "using" statement

I am doing my database access methods to SQL Server like this
using (SqlConnection con = new SqlConnection(//connection string)
{
using (SqlCommand cmd = new SqlCommand(storedProcname, con))
{
try{
con.open();
//data reader code
}
catch
{
}
}
}
Do I need to be closing or disposing of SqlCommand, or will the using statement take care of that for me? I just don't want connection hanging open
Thanks
The using will take care of it for you. Under the hood, SqlConnection.Dispose() calls the SqlConnection.Close() method, and SqlCommand.Dispose() calls SqlCommand.Close().
As additional background, a using statement is syntactic sugar for a try ... finally that disposes the IDisposable object in the finally.
As an aside, you can make the code more concise and readable as follows:
using (SqlConnection con = new SqlConnection(/*connection string*/))
using (SqlCommand cmd = new SqlCommand(storedProcname, con))
{
//...
}
As Phil said, the using clause will take care of it for you. When compiled down it wraps the connection create in a try .. finally and places the connection disposal call inside the finally.
For more information you can see the using statement article at msdn.
Yes your code will close the connection, however that typcally means release back to the connection pool to be truely closed later.
If you execute this snippet of code, and then do an sp_who and observe that your connection is still there, that would be why.
If you absolutely need the connection truely closed (an edge case to be sure) then use the ClearAllPools
static method of ths SqlConnection
Using keyword will automatically close the connection for you
so you don't need to worry about calling connection.close() at the end every time.
when the scope
using (SqlConnection con = new SqlConnection(//connection string)
{
}
will over , connection will automatically be disposed by runtime. so don't worry
I think "using" was not required for SqlCommand. "Using" for SqlConnection would have done the job for you alone.
In fact you connection is submitted to Connection pool.

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