We have a C# application using ADO.NET and an SQL-Server with SNAPSHOT Transaction Isolation Level. This is 'as is' and unfortunately cannot be modified.
Now we need insert stuff on a linked server.
We execute the following code (reduced to illustrate the problem):
// Create a snapshot Transaction and close the connection
using (var con = new SqlConnection(myConStr))
{
con.BeginTransaction(TransactionLevel.Snapshot);
}
// do something with a linked server
using (var con = new SqlConnection(myConStr))
{
using (var cmd = con.CreateCommand()
{
cmd.CommandText = "insert into LinkedServer.SomeDb..Table ...";
cmd.ExecuteNonQuery();
}
}
We get can an exception when trying to insert something into the linked server
'Remote access is not supported for transaction isolation level "SNAPSHOT"'
I wonder why it is not possible: We open the connection, make sure it is disposed (clearing all transactions, I guess) and use a second connection for the linked server call.
Executing the stuff in SSMS using plain SQL seems to work.
What are we missing? Is there a proper way to do it?
Thanks for any hints in the right direction.
The secret to understand the problem is the 'connection pooling' which is done by ADO.NET in the Background. The real connection is actually set to SNAPSHOT.
In the second part of the sample code, that connection simply gets reused thus still being in 'snapshot mode'.
The solution is to explicitly set the transaction isolation Level to something else right after opening the Connection.
using (var con = new SqlConnection(myConStr))
{
using (var cmd = con.CreateCommand()
{
cmd.CommandText = "set transaction isolation Level read committed";
cmd.ExecuteNonQuery();
}
using (var cmd = con.CreateCommand()
{
cmd.CommandText = "insert into LinkedServer.SomeDb..Table ...";
cmd.ExecuteNonQuery();
}
}
Related
As demonstrated by previous Stack Overflow questions (TransactionScope and Connection Pooling and How does SqlConnection manage IsolationLevel?), the transaction isolation level leaks across pooled connections with SQL Server and ADO.NET (also System.Transactions and EF, because they build on top of ADO.NET).
This means, that the following dangerous sequence of events can happen in any application:
A request happens which requires an explicit transaction to ensure data consistency
Any other request comes in which does not use an explicit transaction because it is only doing uncritical reads. This request will now execute as serializable, potentially causing dangerous blocking and deadlocks
The question: What is the best way to prevent this scenario? Is it really required to use explicit transactions everywhere now?
Here is a self-contained repro. You will see that the third query will have inherited the Serializable level from the second query.
class Program
{
static void Main(string[] args)
{
RunTest(null);
RunTest(IsolationLevel.Serializable);
RunTest(null);
Console.ReadKey();
}
static void RunTest(IsolationLevel? isolationLevel)
{
using (var tran = isolationLevel == null ? null : new TransactionScope(0, new TransactionOptions() { IsolationLevel = isolationLevel.Value }))
using (var conn = new SqlConnection("Data Source=(local); Integrated Security=true; Initial Catalog=master;"))
{
conn.Open();
var cmd = new SqlCommand(#"
select
case transaction_isolation_level
WHEN 0 THEN 'Unspecified'
WHEN 1 THEN 'ReadUncommitted'
WHEN 2 THEN 'ReadCommitted'
WHEN 3 THEN 'RepeatableRead'
WHEN 4 THEN 'Serializable'
WHEN 5 THEN 'Snapshot'
end as lvl, ##SPID
from sys.dm_exec_sessions
where session_id = ##SPID", conn);
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
Console.WriteLine("Isolation Level = " + reader.GetValue(0) + ", SPID = " + reader.GetValue(1));
}
}
if (tran != null) tran.Complete();
}
}
}
Output:
Isolation Level = ReadCommitted, SPID = 51
Isolation Level = Serializable, SPID = 51
Isolation Level = Serializable, SPID = 51 //leaked!
The connection pool calls sp_resetconnection before recycling a connection. Resetting the transaction isolation level is not in the list of things that sp_resetconnection does. That would explain why "serializable" leaks across pooled connections.
I guess you could start each query by making sure it's at the right isolation level:
if not exists (
select *
from sys.dm_exec_sessions
where session_id = ##SPID
and transaction_isolation_level = 2
)
set transaction isolation level read committed
Another option: connections with a different connection string do not share a connection pool. So if you use another connection string for the "serializable" queries, they won't share a pool with the "read committed" queries. An easy way to alter the connection string is to use a different login. You could also add a random option like Persist Security Info=False;.
Finally, you could make sure every "serializable" query resets the isolation level before it returns. If a "serializable" query fails to complete, you could clear the connection pool to force the tainted connection out of the pool:
SqlConnection.ClearPool(yourSqlConnection);
This is potentially expensive, but failing queries are rare, so you should not have to call ClearPool() often.
In SQL Server 2014 this seem to have been fixed. If using TDS protocol 7.3 or higher.
Running on SQL Server version 12.0.2000.8 the output is:
ReadCommitted
Serializable
ReadCommitted
Unfortunately this change is not mentioned in any documentation such as:
Behavior Changes to Database Engine Features in SQL Server 2014
Breaking Changes to Database Engine Features in SQL Server 2014
But the change has been documented on a Microsoft Forum.
Update 2017-03-08
Unfortunately this was later "unfixed" in SQL Server 2014 CU6 and SQL Server 2014 SP1 CU1 since it introduced a bug:
FIX: The transaction isolation level is reset incorrectly when the SQL Server connection is released in SQL Server 2014
"Assume that you use the TransactionScope class in SQL Server client-side source code, and you do not explicitly open the SQL Server connection in a transaction. When the SQL Server connection is released, the transaction isolation level is reset incorrectly."
Workaround
It appears that, since passing through a parameter makes the driver use sp_executesql, this forces a new scope, similar to a stored procedure. The scope is rolled back after the end of the batch.
Therefore, to avoid the leak, pass through a dummy parameter, as show below.
using (var conn = new SqlConnection(connString))
using (var comm = new SqlCommand(#"
SELECT transaction_isolation_level FROM sys.dm_exec_sessions where session_id = ##SPID
", conn))
{
conn.Open();
Console.WriteLine(comm.ExecuteScalar());
}
using (var conn = new SqlConnection(connString))
using (var comm = new SqlCommand(#"
SET TRANSACTION ISOLATION LEVEL SNAPSHOT;
SELECT transaction_isolation_level FROM sys.dm_exec_sessions where session_id = ##SPID
", conn))
{
comm.Parameters.Add("#dummy", SqlDbType.Int).Value = 0; // see with and without
conn.Open();
Console.WriteLine(comm.ExecuteScalar());
}
using (var conn = new SqlConnection(connString))
using (var comm = new SqlCommand(#"
SELECT transaction_isolation_level FROM sys.dm_exec_sessions where session_id = ##SPID
", conn))
{
conn.Open();
Console.WriteLine(comm.ExecuteScalar());
}
For those using EF in .NET, you can fix this for your whole application by setting a different appname per isolation level (as also stated by #Andomar):
//prevent isolationlevel leaks
//https://stackoverflow.com/questions/9851415/sql-server-isolation-level-leaks-across-pooled-connections
public static DataContext CreateContext()
{
string isolationlevel = Transaction.Current?.IsolationLevel.ToString();
string connectionString = ConfigurationManager.ConnectionStrings["yourconnection"].ConnectionString;
connectionString = Regex.Replace(connectionString, "APP=([^;]+)", "App=$1-" + isolationlevel, RegexOptions.IgnoreCase);
return new DataContext(connectionString);
}
Strange this is still an issue 8 years later ...
I just asked a question on this topic and added a piece of C# code, which can help around this problem (meaning: change isolation level only for one transaction).
Change isolation level in individual ADO.NET transactions only
It is basically a class to be wrapped in an 'using' block, which queries the original isolation level before and restores it later.
It does, however, require two additional round trips to the DB to check and restore the default isolation level, and I am not absolutely sure that it will never leak the altered isolation level, although I see very little danger of that.
I have a C#, .Net 4.5.2 project, built in VS2012, which uses Entity Framework to connect to a SQL database. I now need to access a separate Oracle database, and have been trying to use SQL Anywhere 16 to make the connection. I know that the SQL Anywhere connection works because I have a test project which successfully uses it. The problem is that the connection.Open() method errors with this message:
Network access for Distributed Transaction Manager (MSDTC) has been disabled. Please enable DTC for network access in the security configuration for MSDTC using the Component Services Administrative tool.
I suspect that Entity Framework doesn't like the additional connection, but enabling MSDTC is not an option as I only have FTP access to the server where the code will live. Is anyone able to suggest an alternative way to get this working, bearing in mind I know very little about Entity Framework?
Additional
There aren't any transactions set up as far as I can see (I only wrote the stuff for connecting to the Oracle database), nor is TransactionScope being used. Here's an edited version of my code:
var connectionString = new SAConnectionStringBuilder();
connectionString.Host = "***.***.***.***";
connectionString.DatabaseName = "*****";
connectionString.UserID = "*****";
connectionString.Password = "*****";
connectionString.ServerName = "*****";
using (SAConnection conn = new SAConnection(connectionString))
{
using (SACommand cmd = conn.CreateCommand())
{
cmd.CommandText = query;
cmd.CommandType = CommandType.Text;
conn.Open();
cmd.ExecuteNonQuery();
conn.Close();
}
}
Solution
I did some more searching, and after a bit of trial and error, managed to solve my problem. It seems the SQL Anywhere connection was automatically being put into a transaction. When this was then combined with the transactions being used by the entity framework connection, it was raised to a distributed transaction.
I have found that by putting the connection inside a transaction scope, and using the TransactionScopeOption.Suppress flag, it is no longer being put into a transaction, and therefore does not require MSDTC to be enabled. My code now looks like this:
var connectionString = new SAConnectionStringBuilder();
connectionString.Host = "***.***.***.***";
connectionString.DatabaseName = "*****";
connectionString.UserID = "*****";
connectionString.Password = "*****";
connectionString.ServerName = "*****";
using (TransactionScope scope1 = new TransactionScope(TransactionScope.Suppress))
{
using (SAConnection conn = new SAConnection(connectionString))
{
using (SACommand cmd = conn.CreateCommand())
{
cmd.CommandText = query;
cmd.CommandType = CommandType.Text;
conn.Open();
cmd.ExecuteNonQuery();
conn.Close();
}
}
scope1.Complete();
}
Thanks to everyone who responded
My current application has all database operations in a giant Using statement with a connection to ensure that the transaction is committed or rolled back in full, currently if i have common methods they pass the current open OleDbConnection so that it can be used.
I would like to use TransactionScope in place of the outer using section. Please see my test code below:
private void Test() {
string _connectionString = "Provider=OraOLEDB.Oracle.1;Password=XXXXXXXX;Persist Security Info=True;User ID=XXXXXXXX;Data Source=XXXXXXX;min pool size=1;incr pool size=5;decr pool size=2;connection timeout=60;";
using (TransactionScope _ts = new TransactionScope(TransactionScopeOption.Required))
{
using (OleDbConnection _cn = new OleDbConnection(_connectionString))
{
_cn.Open(); // Errors Here!
using (OleDbCommand _cmd = new OleDbCommand())
{
_cmd.Connection = _cn;
_cmd.CommandText = "insert into testtable (TEST) values ('FIRST')";
_cmd.CommandType = CommandType.Text;
_cmd.ExecuteNonQuery();
}
}
using (OleDbConnection _cn = new OleDbConnection(_connectionString))
{
_cn.Open();
using (OleDbCommand _cmd = new OleDbCommand())
{
_cmd.Connection = _cn;
_cmd.CommandText = "insert into testtable (TEST) values ('SECOND')";
_cmd.CommandType = CommandType.Text;
_cmd.ExecuteNonQuery();
}
}
}
}
The error i receive is "Unable to enlist in the transaction." I have read that Oracle doesn't like using a TransactionScope (Problems with TransactionScope and Oracle) , but it seems to fit with what i need to achieve. I have found very little information about how to bridge single transactions across connection pooled connections.
EDIT - 11th Feb
I switched from OleDB to ODP.Net and managed to get an official Oracle ORA error out...
ORA-02048: attempt to begin distributed transaction without logging on
Sadly from what i can find i think its an Oracle bug? I have found forum posts which suggest that version 10.2.0.2 has this bug, but i am on 10.2.0.4?
Hoping somebody can help! Thanks
To use with TransactionScope the connection string must contain "enlist=dynamic"
https://docs.oracle.com/database/121/ODPNT/InstallConfig.htm#r6c1-t14
Specifies whether the application enlists in distributed transactions explicitly after an OracleConnection.Open method invocation through EnlistTransaction() or EnlistDistributedTransaction(). To configure ODP.NET to enable dynamic enlistment programmatically, the connection string must contain "enlist=dynamic".
connection string for example:
enlist=dynamic;User Id=USER;Password=pass;Data Source=(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=127.0.0.1)(PORT=1521)))(CONNECT_DATA=(SERVICE_NAME=TestDB)));
So, my answer was a couple of things:
Firstly I needed to install the OracleMTS service on my client. Secondly i changed from OleDB (via Oracle.ManagedDataAccess v12) to Oracle.DataAccess v11, and it works!
I discovered that v12 of the ODP.Net client and 10.2.0.4 have a bug where the distributed transaction fails, but version 11 works. Still puzzled why it doesn't work with OleDB but i have resolved it now. Hope this can help somebody else in my position!
I have a T-SQL script to create a Database. I need to create this database runtime. When the application is Running.
What Connection String do I use?
How do I connect to the server and Create the Database? I am connecting to the server as a Network user. I am not using User "sa" I have a user "DBCreator"
My application is in C#.
I have this Script in T-SQL:
USE [master]
GO
CREATE DATABASE [XYZ]
-- Table Creation Code etc.
You can have two connection strings. One for master database to issue the CREATE DATABASE ... statement and another one for database created.
// You can use replace windows authentication with any user credentials who has proper permissions.
using (SqlConnection connection = new SqlConnection(#"server=(local);database=master;Integrated Security=SSPI"))
{
connection.Open();
using (SqlCommand command = connection.CreateCommand())
{
command.CommandText = "CREATE DATABASE [XYZ]";
command.ExecuteNonQuery();
}
}
// Quering the XYZ database created
using (SqlConnection connection = new SqlConnection(#"server=(local);database=XYZ;Integrated Security=SSPI"))
{
connection.Open();
using (SqlCommand command = connection.CreateCommand())
{
command.CommandText = "select * from sys.objects";
...
}
}
You can use the SMO objects to do that. I don't think i should explain what is already explained in details in a very good tutorial here
Definitely use SMO its intended to do everything that SSMS can do, and more! it actually has a command called Database.Create(), follow this MSDN page
What is the the best practice for SQL connections?
Currently I am using the following:
using (SqlConnection sqlConn = new SqlConnection(CONNECTIONSTRING))
{
sqlConn.Open();
// DB CODE GOES HERE
}
I have read that this is a very effective way of doing SQL connections. By default the SQL pooling is active, so how I understand it is that when the using code ends the SqlConnection object is closed and disposed but the actual connection to the DB is put in the SQL connection pool. Am i wrong about this?
That's most of it. Some additional points to consider:
Where do you get your connection string? You don't want that hard-coded all over the place and you may need to secure it.
You often have other objects to create as well before your really use the connection (SqlCommand, SqlParameter, DataSet, SqlDataAdapter), and you want to wait as long as possible to open the connection. The full pattern needs to account for that.
You want to make sure your database access is forced into it's own data layer class or assembly. So a common thing to do is express this as a private function call:
.
private static string connectionString = "load from encrypted config file";
private SqlConnection getConnection()
{
return new SqlConnection(connectionString);
}
And then write your sample like this:
using (SqlConnection sqlConn = getConnection())
{
// create command and add parameters
// open the connection
sqlConn.Open();
// run the command
}
That sample can only exist in your data access class. An alternative is to mark it internal and spread the data layer over an entire assembly. The main thing is that a clean separation of your database code is strictly enforced.
A real implementation might look like this:
public IEnumerable<IDataRecord> GetSomeData(string filter)
{
string sql = "SELECT * FROM [SomeTable] WHERE [SomeColumn] LIKE #Filter + '%'";
using (SqlConnection cn = getConnection())
using (SqlCommand cmd = new SqlCommand(sql, cn))
{
cmd.Parameters.Add("#Filter", SqlDbType.NVarChar, 255).Value = filter;
cn.Open();
using (IDataReader rdr = cmd.ExecuteReader())
{
while (rdr.Read())
{
yield return (IDataRecord)rdr;
}
}
}
}
Notice that I was also able to "stack" the creation of the cn and cmd objects, and thus reduce nesting and only create one scope block.
Finally, a word of caution about using the yield return code in this specific sample. If you call the method and don't complete your DataBinding or other use right away it could hold the connection open for a long time. An example of this is using it to set a data source in the Load event of an ASP.NET page. Since the actual data binding event won't occur until later you could hold the connection open much longer than needed.
Microsoft's Patterns and Practices libraries are an excellent approach to handling database connectivity. The libraries encapsulate most of the mechanisms involved with opening a connection, which in turn will make your life easier.
Your understanding of using is correct, and that method of usage is the recommended way of doing so. You can also call close in your code as well.
Also : Open late, close early.
Don't open the connection until there are no more steps left before calling the database. And close the connection as soon as you're done.