How to convert to ADO.NET transactions rather than SQL Server Transactions?
Right now i have code that initiates transactions on SQL Server using the intended method:
ExecuteNonQuery(connection, "BEGIN TRANSACTION");
try
{
DoABunchOnStuff(connection);
DoSomeMoreStuff(connection);
JustAFewMoreThings(connection);
ExecuteNonQuery(connection, "COMMIT TRANSACTION");
}
catch (Exception)
{
ExecuteNonQuery(connection, "ROLLBACK TRANSACTION");
throw;
}开发者_如何学JAVA
Now i'm looking at thinking about the possibility of investigating the idea of using the transaction abstraction provided by ADO.NET:
DbTransaction trans = connection.BeginTransaction();
try
{
DoABunchOnStuff(connection);
DoSomeMoreStuff(connection);
JustAFewMoreThings(connection);
trans.Commit();
}
catch (Exception)
{
trans.Rollback();
throw;
}
Problem with this simple conversion from SQL Server based transactions, to ADO.NET transactions, is the error:
ExecuteNonQuery 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.
Am i correct in assuming that if i wanted to use ADO.NET transactions i would have to completely gut the infrastructure, passing along a DbTransaction object to every method that does, or may, operate inside a transaction?
You are correct, but since you are evidently keeping the connection open the whole time, you could replace this with a TransactionScope instead; it won't promote to DTC as long as there's only one open connection.
Example:
using (TransactionScope tsc = new TransactionScope())
{
DoABunchOnStuff(connection);
DoSomeMoreStuff(connection);
JustAFewMoreThings(connection);
tsc.Complete();
}
Notes about using the TransactionScope
:
You must make sure to include
Transaction Binding = Explicit Unbind
in your connection string. By default transactions are run in implicit-unbind mode which means that they switch to auto-commit mode if the transaction times out. You almost never want the default behaviour, as it can interfere with the atomicity of your transactions and cause what some people refer to as data corruption (even though it's not actual "corruption"). As long as you use the correct parameters in your connection string, you don't need to worry about this.TransactionScope
will promote to DTC (distributed transaction) if there is more than one connection in scope, which includes linked servers andOPENROWSET
. Although this might seem like undesirable behaviour, your code isn't going to be transactionally safe any other way. Executing manualBEGIN TRAN
statements on multiple connections and putting multipleROLLBACK
statements in an exception handler does not ensure atomicity of the entire transaction.Transaction Scopes are designed to be nested and will automatically figure out the difference between beginning a new transaction and enlisting in an existing one. This is a lot more powerful than matching up
BEGIN TRAN
andCOMMIT
/ROLLBACK
statements, as the latter rely on a connection-local transaction count, whereas the former is actually... scoped. UsingTransactionScope
is similar to structured transaction handling in SQL Server usingSAVE TRAN
,TRY
/CATCH
, and namedROLLBACK
- you do not need to worry about what happens if a downstream process or procedure flubs the transactional logic, which is a serious risk when sending rawBEGIN
andROLLBACK
statements over ADO.NET.
Am i correct in assuming that if i wanted to use ADO.NET transactions i would have to completely gut the infrastructure, passing along a DbTransaction object to every method that does, or may, operate inside a transaction?
Yes, exactly - you basically need to associate the transaction you've created with each SqlCommand that ought to be executed under that transaction's umbrella - so you'd have to have something like:
DbTransaction trans = connection.BeginTransaction();
try
{
DoABunchOnStuff(connection, trans);
DoSomeMoreStuff(connection, trans);
JustAFewMoreThings(connection, trans);
trans.Commit();
}
catch (Exception)
{
trans.Rollback();
throw;
}
and inside those methods something along the lines of:
public void DoABunchOnStuff(SqlConnection connection, SqlTransaction trans)
{
using(SqlCommand cmd = new SqlCommand(--sql stmt--, connection, trans)
{
........
}
}
You may also want to take a look at Linq to SQL. As you can also "SubmitChanges()" (or not submit them) in code to the database. This means you can wrap it in a try catch just like your transaction. This is a bit of an infratstucture change as well but with SQLMetal you can auto generate all the necessary classes. It may or may not be right for your situation.
more info: http://weblogs.asp.net/scottgu/archive/2007/05/19/using-linq-to-sql-part-1.aspx
精彩评论