I need to create a simple dotnet application which is going to call a stored procedure inside a loop (the stored procedure accepts few parameters and add them to a table). The requirement is that either all the rows get inserted or non.
To ensure this I've used:
using (TransactionScope scope = new TransactionScope())
{
foreach (EditedRule editedRules in request.EditedRules)
{
...stored procedure call
}
}
I've never used TransactionScope before, can someone please let me know if this code will work and will all my rows be rolled back.
I would also appreciate if there is a better approach to this.
Assuming that your stored procedure does not create and commit its own transaction, this code will work, with one change: your code needs to code scope.Complete() before the end of the using block; otherwise, the transaction is going to roll back.
using (TransactionScope scope = new TransactionScope()) {
foreach (EditedRule editedRules in request.EditedRules) {
...stored procedure call
}
scope.Complete(); // <<== Add this line
}
The idea behind this construct is that the call of Complete will happen only if the block exits normally, i.e. there's no exception in processing the loop. If an exception is thrown, scope would detect it, and cause the transaction to roll back.
The only thing I would call out about your code is to make sure to do a scope.Complete() in order to commit the transaction:
using (TransactionScope scope = new TransactionScope())
{
foreach (EditedRule editedRules in request.EditedRules)
{
// stored proc
}
scope.Complete();
}
if any exceptions occur during the using block, the transaction would be rolled back.
I think this is what you're looking for:
using (var transaction = conn.BeginTransaction()) {
try
{
transaction.Commit();
}
catch
{
transaction.Rollback();
throw;
}
}
Related
Example: Using transaction, and only committing if a given condition is satisfied.
using (var transaction = context.Database.BeginTransaction())
// ...
if (modelState.IsValid) {
transaction.Commit();
}
}
does it need to be:
using (var transaction = context.Database.BeginTransaction())
// ...
if (modelState.IsValid) {
transaction.Commit();
} else {
transaction.Rollback();
}
}
or there is no need for the else part. What happens if I don't specify?
Any good guidelines?
You don't need to call Rollback manually because you are using the using statement.
It is not necessary to explicitly call Rollback. The transaction will be rolled-back if Commit() has not been called.
DbContextTransaction.Dispose method will be called in the end of the using block. And it will automatically rollback the transaction if the transaction is not successfully committed
I am using EF6 with code first approach. I have a case where I am trying to apply same transaction over 2 different databases. Find my code below for the reference,
using (var scope = new TransactionScope(TransactionScopeOption.RequiresNew, TimeSpan.FromSeconds(6000)))
{
using (var dbContext = new MyDbContext())
{
try
{
/*
* some operations on dbContext
*
*/
using (IDbConnection conn = new SqlConnection(NewConnectionString))
{
string query = " ALTER TABLE [dbo].[TableName]...........";
conn.Execute(query); // I am using a dapper to call this execute query
}
}
catch (Exception ex)
{
throw;
}
}
scope.Complete();
}
The first section in try block does some normal operations on tables by using linq.
The second section where I have used IDbConnection, I am executing some query using dapper on another database. I can execute everything fine without error, but I'm getting error after I complete the scope. It throws error as The transaction has aborted. Everything works fine whenever I skip dapper execution section. I have checked many solutions and also tried to put separate transaction for dapper section but nothing worked. My requirement is to put all the statements under single transaction. Is that possible? How?
I use an ambient transaction like this :
using(TransactionScope tran = new TransactionScope()) {
CallAMethod1();//INSERT
CallAMethod2();//INSERT
tran.Complete();
}
The method CallAMethod2(); returns affected rows =-264
So it fails to insert however the first Insert has been committed !
I want to know how to work with ambient transaction and what if the second method has more than one action which needs internal transaction , should i put these actions in internal transaction ?
like this :
DAL_Helper.Begin_Transaction();
//------Fill newKeysDictioanry
affectedRow = DBUtilities.InsertEntityWithTrans("table2", newKeysDictioanry, DAL_Helper);
if (affectedRow == 1)
{
if (!string.IsNullOrEmpty(sp_confirm))
{
result_dt = UserTransactionDAL.Run_PostConfirm_SP(sp_PostConfirm, OBJ.ValuesKey, DAL_Helper);
if (result_dt.Rows.Count > 0 && result_dt.Rows[0][0].ToString() == "0")
{
DAL_Helper.current_trans.Commit();
if (DAL_Helper.connectionState == ConnectionState.Open)
{
DAL_Helper.Close_Connection();
}
return 1;// affectedRow;
}
else
{
DAL_Helper.current_trans.Rollback();
if (DAL_Helper.connectionState == ConnectionState.Open)
{
DAL_Helper.Close_Connection();
}
return -2;
}
}
//etc
1) You need to check whether the tran.Complete(); is called. If the tran.Complete(); is called, the TransactionScope is considered completed successfully.
From MSDN
When your application completes all work it wants to perform in a
transaction, you should call the Complete method only once to inform
that transaction manager that it is acceptable to commit the
transaction. Failing to call this method aborts the transaction.
The call to tran.Complete(); is to inform the Transaction Manager to complete the transaction. Actually, the Transaction Manager does not track your Db adapter and does not know if an operation in a connection was successful or failed. Your application has to let it know by calling Complete
How does TransactionScope roll back transactions?
To fail your transaction, just ensure that you don't call tran.Complete(); in your code:
If no exception occurs within the transaction scope (that is, between
the initialization of the TransactionScope object and the calling of
its Dispose method), then the transaction in which the scope
participates is allowed to proceed. If an exception does occur within
the transaction scope, the transaction in which it participates will
be rolled back.
In your case, maybe you can throw an exception in your CallAMethod2(); if you think the operation has failed so the tran.Complete(); is not called and the transaction is rolled back.
2) The second thing you can check is whether your connection is enlisted in the transaction. TransactionScope does not rollback if the connection is not enlisted. The possible problems are:
If the connection exists before entering the transaction scope, it won't enlist: Does TransactionScope work with pre-existing connections?
Your underlying connection does not support auto enlistment.
In these cases, you can try enlisting your connection manually (extracted from the link above):
connection.EnlistTransaction(Transaction.Current)
Regarding your second question:
what if the second method has more than one action which need internal
transaction , should i put these actions in internal transaction ?
I would say it really depends on whether you consider your CallAMethod2(); as an automic operation which means you can call it elsewhere directly without wrapping it inside a transaction. Most of the cases, it would make sense to create internal transactions as transactions can be nested. In your case, it's recommended to also use TransactionScope in your CallAMethod2();, we have some options when creating a new transaction scope:
The TransactionScope class provides several overloaded constructors
that accept an enumeration of the type TransactionScopeOption, which
defines the transactional behavior of the scope. A TransactionScope
object has three options:
Join the ambient transaction, or create a new one if one does not exist.
Be a new root scope, that is, start a new transaction and have that transaction be the new ambient transaction inside its own scope.
Not take part in a transaction at all. There is no ambient transaction as a result.
Which one to choose really depends on your application. In your case, I guess you could go with the first option. Below is an example from MSDN
void RootMethod()
{
using(TransactionScope scope = new TransactionScope())
{
/* Perform transactional work here */
SomeMethod();
scope.Complete();
}
}
void SomeMethod()
{
using(TransactionScope scope = new TransactionScope())
{
/* Perform transactional work here */
scope.Complete();
}
}
You can use scope inner and outer for transaction:
string connectionString = ConfigurationManager.ConnectionStrings["db"].ConnectionString;
var option = new TransactionOptions
{
IsolationLevel = IsolationLevel.ReadCommitted,
Timeout = TimeSpan.FromSeconds(60)
};
using (var scopeOuter = new TransactionScope(TransactionScopeOption.Required, option))
{
using (var conn = new SqlConnection(connectionString))
{
using (SqlCommand cmd = conn.CreateCommand())
{
cmd.CommandText="INSERT INTO Data(Code, FirstName)VALUES('A-100','Mr.A')";
cmd.Connection.Open();
cmd.ExecuteNonQuery();
}
}
using (var scopeInner = new TransactionScope(TransactionScopeOption.Required, option))
{
using (var conn = new SqlConnection(connectionString))
{
using (SqlCommand cmd = conn.CreateCommand())
{
cmd.CommandText="INSERT INTO Data(Code, FirstName) VALUES('B-100','Mr.B')";
cmd.Connection.Open();
cmd.ExecuteNonQuery();
}
}
scopeInner.Complete();
}
scopeOuter.Complete();
}
Read what Khanh TO says. If your connection is opened outside the outer transaction scope the connection won't be enlisted.
That is why the first call didn't rollback when the second failed. You will have to enlist your connection:
using (TransactionScope tran = new TransactionScope(TransactionScopeOption.Required))
{
connection.EnlistTransaction(Transaction.Current);
CallAMethod1();//INSERT
CallAMethod2();//INSERT
tran.Complete();
}
I have a typed dataset for a table called People. When you call the update method of a table adapter and pass in the table, is it run as a transaction?
I'm concerned that at some point the constraints set in the xsd will pass but the database will reject this item for one reason or another. I want to make sure that the entire update is rejected and I'm not sure that it just accepts what it can until that error occurs.
If it runs as a transaction I have this
Auth_TestDataSetTableAdapters.PeopleTableAdapter tableAdapter = new Auth_TestDataSetTableAdapters.PeopleTableAdapter();
Auth_TestDataSet.PeopleDataTable table = tableAdapter.GetDataByID(1);
table.AddPeopleRow("Test Item", 5.015);
tableAdapter.Update(table);
But if I have to manually trap this in a transaction I wind up with this
Auth_TestDataSetTableAdapters.PeopleTableAdapter tableAdapter = new Auth_TestDataSetTableAdapters.PeopleTableAdapter();
Auth_TestDataSet.PeopleDataTable table = tableAdapter.GetDataByID(1);
tableAdapter.Connection.Open();
tableAdapter.Transaction = tableAdapter.Connection.BeginTransaction();
table.AddPeopleRow("Test Item", 5.015);
try
{
tableAdapter.Update(table);
tableAdapter.Transaction.Commit();
}
catch
{
tableAdapter.Transaction.Rollback();
}
finally
{
tableAdapter.Connection.Close();
}
Either way works but I am interested in the inner workings. Any other issues with the way I've decided to handle this type of row addition?
-- EDIT --
Determined that it does not work as a transaction and will commit however many records are successful until the error occurs. Thanks to the helpful post below a bit of that transactional code has been condensed to make controlling the transaction easier on the eyes:
Auth_TestDataSetTableAdapters.PeopleTableAdapter tableAdapter = new Auth_TestDataSetTableAdapters.PeopleTableAdapter();
Auth_TestDataSet.PeopleDataTable table = tableAdapter.GetDataByID(1);
try
{
using (TransactionScope ts = new TransactionScope())
{
table.AddPeopleRow("Test Item", (decimal)5.015);
table.AddPeopleRow("Test Item", (decimal)50.015);
tableAdapter.Update(table);
ts.Complete();
}
}
catch (SqlException ex)
{ /* ... */ }
Your approach should work.
You can simplify it a little though:
using (TransactionScope ts = new TransactionScope())
{
// your old code here
ts.Complete();
}
Can I use transactions with a datacontext, so that I can rollback the state of the context after an error? And if so, how does that work?
I use them in testing all the time :)
try
{
dc.Connection.Open();
dc.Transaction = dc.Connection.BeginTransaction();
dc.SubmitChanges();
}
finally
{
dc.Transaction.Rollback();
}
UPDATE
This will ALWAYS rollback after the fact. I use this in testing.
A DataContext will pick up an ambient transaction by default, so it is just a matter of ensuring there is a Transaction in scope. The details become the main issue:
What options do you need (e.g. isolation level)
Do you want a new transaction or reuse an existing transaction (e.g. an audit/logging operation might require a new transaction so it can be committed even if the overall business operation fails and thus the outer transaction is rolled back).
This is simplified some prototype code, the real code uses helpers to create the transactions with policy driven options (one of the purposes of the prototype was to examine the impact of these options).
using (var trans = new TransactionScope(
TransactionScopeOption.Required,
new TransactionOptions {
IsolationLevel = IsolationLevel.ReadCommitted
},
EnterpriseServicesInteropOption.Automatic)) {
// Perform operations using your DC, including submitting changes
if (allOK) {
trans.Complete();
}
}
If Complete() is not called then the transaction will be rolled back. If there is a containing transaction scope then both the inner and outer transactions need to Complete for the changes on the database to be committed.
It's not as simple as the TransactionScope method but, as I understand it, this is the "correct" way to do it for LINQ-to-SQL. It doesn't require any reference to System.Transactions.
dataContext.Connection.Open();
using (dataContext.Transaction = dataContext.Connection.BeginTransaction())
{
dataContext.SubmitChanges();
if (allOK)
{
dataContext.Transaction.Commit();
}
else
{
dataContext.Transaction.RollBack();
}
}
Of course, the RollBack is only required if you intend to do further data operations within the using, otherwise changes will be automatically discarded.
Something like this, probably:
try
{
using (TransactionScope scope = new TransactionScope())
{
//Do some stuff
//Submit changes, use ConflictMode to specify what to do
context.SubmitChanges(ConflictMode.ContinueOnConflict);
scope.Complete();
}
}
catch (ChangeConflictException cce)
{
//Exception, as the scope was not completed it will rollback
}
Is something like this:
using (YourDatacontext m_DB = new YourDatacontext())
using (TransactionScope tran = new TransactionScope())
{
try
{
//make here the changes
m_DB.SubmitChanges();
tran.Complete();
}
catch (Exception ex)
{
Transaction.Current.Rollback();
}
}