One of the key things in database-intensive applications is to keep the transactions as short as possible.
Today I was wondering when this transaction would actually begin:
using (SqlConnection sqlConnection = new SqlConnection(connectionString))
{
sqlConnection.Open();
/*(1)*/ SqlTransaction sqlTransaction = sqlConnection.BeginTransaction(IsolationLevel.ReadUncommitted);
//Perform some stuff
//...
/*(2)*/ using (SqlCommand command = new SqlCommand(sqlQuery, sqlConnection, sqlTransaction))
{
//Some other stuff
//...
try
{
/*(3)*/sqlCommand.ExecuteNonQuery();
//More irrelevant code
//...
sqlCommand.CommandText = otherQuery;
sqlCommand.ExecuteNonQuery();
sqlTransaction.Commit();
}
catch(Exception)
{
sqlTransaction.Rollback();
throw;
}
}
}
In step (1), (2) or (3)? Ideally it should be in step 3.
The transaction starts at point 3, the first time you exectue a command on the connection.
You can verify this using SQL Server Profiler.
Related
I have a standard routine for executing SqlCommand with an exception handler. But if an exception is thrown within this routine then I can't make an rollback if this standard routine is called within a transaction. So how can I check that this standard routine is placed inside a transaction or not? What is the proper way? I have googled a lot so far...
Do I have to thrown a new exception in my exception handler for the standard routine for forcing the overall transaction to rollback?
My standard routine looks like:
try
using (SqlConnection con = new SqlConnection(dbCon))
{
using (SqlCommand cmd = new SqlCommand(SQL, con))
{
con.Open();
cmd.CommandTimeout = 600;
cmd.ExecuteScalar();
}
}
}
catch (Exception ex)
{
// do some stuff here
maybe check for existence in a transaction here
}
My overall transaction look like this:
using (SqlConnection con = new SqlConnection(dbCon))
{
con.Open();
string TransactionName = "TransactionName";
using (SqlTransaction sqlTransaction = con.BeginTransaction(TransactionName))
{
try
{
// do some stuff here and call the standard routine here several times...
}
catch (Exception vDBException)
{
DB.getInstance().RollbackTransaction(sqlTransaction, TransactionName);
}
}
}
I have tried to make use of select ##trancount with no success.
I have also tried to check sys.sysprocesses from SQL Server with no success.
I really hope that someone can show me the right direction.
For a long time I've been using the following structure when using TransactionScope.
using(var con = new SqlConnection(CONNECTIONSTRING))
{
con.Open();
foreach(var file in files)
{
try
{
using(var tran = new TransactionScope())
{
using(var cmd = new SqlCommand(CMDTEXT1, con)
{
//add parameters
//ExecuteScalar or ExecuteNonQuery
}
//...repeat above as needed for other inserts
//...run other C# methods like FTP upload
tran.Complete();
}
}
catch(Exception ex)
{
//log exception
}
}
}
Today, my FTP server was down but my SQL server was up. The error was properly caught and tran.Complete never ran. I would expect tran to rollback the changes but instead it had inserted all my commands.
Is this because the SqlConnection is not within the TransactionScope? Do I have to start a new SqlConnection for each transaction? I want to retain this structure so I can reuse the SqlConnection so I considered replacing new TransactionScope with con.BeginTransaction but I read that it doesn't allow the mixing of C# (I need to run C# methods like FTPUpload). Did I misintrepret this?
Answered by comments
Is this because the SqlConnection is not within the TransactionScope? Yes
Do I have to start a new SqlConnection for each transaction? Yes
Did I misintrepret this? I confused EntityFramework.BeginTransaction with SqlConnection.BeginTransaction.
I have referred this to perform rollback operation in my wpf c# application. The code that I tried is as follows:
using (OdbcConnection connection = new OdbcConnection("connectionString"))
{
OdbcCommand command = new OdbcCommand();
OdbcTransaction transaction = null;
command.Connection = connection;
try
{
connection.Open();
transaction = connection.BeginTransaction();
command.Connection = connection;
command.Transaction = transaction;
command.CommandText = "INSERT INTO TableA (A, B, C) VALUES (10,10,10)";
command.ExecuteNonQuery();
command.CommandText = "NSERT INTO TableB (D,E,F) VALUES (20,20,20)";
command.ExecuteNonQuery();
transaction.Commit();
}
catch(Exception ex)
{
Console.WriteLine(ex.Message);
try
{
transaction.Rollback();
}
catch
{
}
}
Intentionally the second query has been made wrong. My intention is that when I enter the catch block on calling transaction.Rollback() the values added due to executing of the first query in TableA are not reflected since Rollback was called. However this is not the case the values are not rolledback and are present in TableA. I have searched various resources online with no luck. I cannot use SqlConnection instead of OdbcConnection my application does not support that. Is there any work around this or alternative method that can achieve what I have in mind. Please help me out.
You have basically MSDN example. I had once another problem with ODBC and the issue was with ODBC vendor drivers. I would strongly recommend check that possibility.
would it work to dispose a command assigned to a transaction before the transaction is committed? I tested the following code myself, and it seems to have worked fine, but this is a rather small example, so I'm looking for confirmation if someone positively knows.
internal static void TestTransaction()
{
try
{
Program.dbConnection.Open();
using (SqlTransaction transaction = Program.dbConnection.BeginTransaction())
{
Boolean doRollback = false;
for (int i = 0; i < 10; i++)
{
using (SqlCommand cmd = new SqlCommand("INSERT INTO [testdb].[dbo].[transactiontest] (testvalcol) VALUES (#index)", Program.dbConnection, transaction))
{
cmd.Parameters.AddWithValue("#index", i);
try
{
cmd.ExecuteNonQuery();
}
catch (SqlException)
{
doRollback = true;
break;
}
}
}
if (doRollback)
transaction.Rollback();
else
transaction.Commit();
}
}
finally
{
Program.dbConnection.Close();
}
}
The connection, transaction and command objects are just vehicles to send commands to a database. Once a command is executed the database has received it. Whatever you do with the command object afterwards, dispose it, burn it, or shoot it to the moon, this fact does not change. (It can only be rolled back).
You can create and dispose as many commands as you like within the scope of one SqlConnection (with or without SqlTransaction). And you can start and dispose as many transactions as you like within one SqlConnection. To demonstrate this, see:
using (var conn = new SqlConnection(#"server=(local)\sql2008;database=Junk;Integrated Security=SSPI"))
{
conn.Open();
// Repeat this block as often as you like...
using (var tran = conn.BeginTransaction())
{
using (var cmd = new SqlCommand("INSERT INTO Mess VALUES ('mess1')", conn, tran))
{
cmd.ExecuteNonQuery(); // Shows in Sql profiler
}
tran.Commit(); // Commits
}
using (var cmd = new SqlCommand("INSERT INTO Mess VALUES ('mess2')", conn))
{
cmd.ExecuteNonQuery(); // Executes and commits (implicit transaction).
}
}
Of course, for healthy code you need to dispose of all objects in the correct order. Disposing a command after disposing a SqlConnection may cause the connection object to stay in memory.
Yes, it's probably safe. The using() is closing the Command, not the Connection.
But you should put that Connection in another using() block or in a try/finally construct.
Confirmed, this works very well and (at least here at our company) is even considered the correct approach.
create connection
create transaction
create command(s), use the transaction, execute
dispose command(s)
commit transaction
dispose connection
For an application we are developing we need to read n rows from a table and then selectively update those rows based on domain specific criteria. During this operation all other users of the database need to be locked out to avoid bad reads.
I begin a transaction, read the rows, and while iterating on the recordset build up a string of update statements. After I'm done reading the recordset, I close the recordset and run the updates. At this point I commit the transaction, however none of the updates are being performed on the database.
private static SQLiteConnection OpenNewConnection()
{
try
{
SQLiteConnection conn = new SQLiteConnection();
conn.ConnectionString = ConnectionString;//System.Configuration.ConfigurationManager.AppSettings["ConnectionString"];
conn.Open();
return conn;
}
catch (SQLiteException e)
{
LogEvent("Exception raised when opening connection to [" + ConnectionString + "]. Exception Message " + e.Message);
throw e;
}
}
SQLiteConnection conn = OpenNewConnection();
SQLiteCommand command = new SQLiteCommand(conn);
SQLiteTransaction transaction = conn.BeginTransaction();
// Also fails transaction = conn.BeginTransaction();
transaction = conn.BeginTransaction(IsolationLevel.ReadCommitted);
command.CommandType = CommandType.Text;
command.Transaction = transaction;
command.Connection = conn;
try
{
string sql = "select * From X Where Y;";
command.CommandText = sql;
SQLiteDataReader ranges;
ranges = command.ExecuteReader();
sql = string.Empty;
ArrayList ret = new ArrayList();
while (MemberVariable > 0 && ranges.Read())
{
// Domain stuff
sql += "Update X Set Z = 'foo' Where Y;";
}
ranges.Close();
command.CommandText = sql;
command.ExecuteNonQuery();
// UPDATES NOT BEING APPLIED
transaction.Commit();
return ret;
}
catch (Exception ex)
{
transaction.Rollback();
throw;
}
finally
{
transaction.Dispose();
command.Dispose();
conn.Close();
}
return null;
If I remove the transaction everything works as expected. The "Domain stuff" is domain specfic and other than reading values from the recordset doesn't access the database. Did I forget a step?
When you put a breakpoint on your transaction.Commit() line do you see it getting hit?
Final answer:
SQLite's locking does not work like you're assuming see http://www.sqlite.org/lockingv3.html. Given that, I think you're having a transaction scoping issue which can be easily resolved by reorganizing your code as such:
string selectSql = "select * From X Where Y;";
using(var conn = OpenNewConnection()){
StringBuilder updateBuilder = new StringBuilder();
using(var cmd = new SQLiteCommand(selectSql, conn))
using(var ranges = cmd.ExecuteReader()) {
while(MemberVariable > 0 && ranges.Read()) {
updateBuilder.Append("Update X Set Z = 'foo' Where Y;");
}
}
using(var trans = conn.BeginTransaction())
using(var updateCmd = new SQLiteCommand(updateBuilder.ToString(), conn, trans) {
cmd.ExecuteNonQuery();
trans.Commit();
}
}
Additional notes regarding some comments in this post/answer about transactions in SQLite. These apply to SQLite 3.x using Journaling and may or may not apply to different configurations - WAL is slightly different but I am not familiar with it. See locking in SQLite for the definitive information.
All transactions in SQLite are SERIALIZABLE (see the read_uncommitted pragma for one small exception). A new read won't block/fail unless the write process has started (there is an EXCLUSIVE/PENDING lock held) and a write won't start until all outstanding reads are complete and it can obtain an EXCLUSIVE lock (this is not true for WAL but the transaction isolation is still the same).
That is the entire sequence above won't be atomic in code and the sequence may be read(A) -> read(B) -> write(A) -> read(B), where A and B represent different connections (imagine on different threads). At both read(B) the data is still consistent even though there was a write in-between.
To make the sequence of code itself atomic a lock or similar synchronization mechanism is required. Alternatively, the lock/synchronization can be created with SQLite itself by using a locking_mode pragma of "exclusive". However, even if the code above is not atomic the data will adhere to the SQL serializable contract (excluding a serious bug ;-)
Happy coding
See Locking in SQLite, SQLite pragmas and Atomic Commit in SQLite