How to use TransactionScope? - c#

I am trying to update a DB2 database (via NHibernate) under a distributed transaction but it keeps failing.
Here is the code I've written:
public bool ExecuteUsingDTC(List<Func<bool>> tasks)
{
var result = false;
using (var scope = new TransactionScope(TransactionScopeOption.Required))
{
using (var session = sessionFactory.OpenSession())
using (var transaction = session.BeginTransaction())
{
//carry out db modification tasks
tasks.ForEach(task => { result = result && task.Invoke(); })
transaction.Commit();
}
scope.Complete();
}
return result;
}
I keep getting the exception:
NHibernate.TransactionException was unhandled by user code
Message=Begin failed with SQL exception
Source=NHibernate
StackTrace:
at NHibernate.Transaction.AdoTransaction.Begin(IsolationLevel isolationLevel)
at NHibernate.Transaction.AdoTransaction.Begin()
at NHibernate.AdoNet.ConnectionManager.BeginTransaction()
at NHibernate.Impl.SessionImpl.BeginTransaction()
at DTCProofOfConcept_Repository.ExecuteUsingDTC(List`1 tasks) in C:\Project\Infrastructure\GlobalRepositories\DTCProofOfConcept_Repository.vb:line 20
at
InnerException: IBM.Data.DB2.DB2Exception
ErrorCode=-2147467259
Message=ERROR [HY011] [IBM] CLI0126E Operation invalid at this time. SQLSTATE=HY011
Source=IBM.Data.DB2
StackTrace:
at IBM.Data.DB2.DB2Connection.HandleError(IntPtr hHandle, SQL_HANDLE hType, RETCODE retcode)
at IBM.Data.DB2.DB2Transaction.set_AutoCommit(Boolean value)
at IBM.Data.DB2.DB2Transaction.BeginTransaction()
at IBM.Data.DB2.DB2Connection.BeginTransactionObject(IsolationLevel isolevel)
at IBM.Data.DB2.DB2Connection.BeginTransaction(IsolationLevel isolevel)
at IBM.Data.DB2.DB2Connection.BeginTransaction()
at IBM.Data.DB2.DB2Connection.System.Data.IDbConnection.BeginTransaction()
at NHibernate.Transaction.AdoTransaction.Begin(IsolationLevel isolationLevel)
InnerException:

Using TransactionScope like this
using(var scope = new TransactionScope(...))
{
transaction.Complete();
}
Begins and commits a transaction for you. If an exception is thrown then the transaction will be aborted by the scope being disposed without Complete being called.
I suspect your problem is that you have an additional explicit call to BeginTransaction/Commit. Remove those and assuming NHibernate plays nicely with TransactionScope all should be fine.
Using TransactionScope may involve use of the distributed transaction co-ordinator (DTC). If so you need to open Administrative Tools|Component Services. Navigate to Distributed Transaction Coordinator|Local DTC. Right click, properties, security tab. Enable security and as a start enable everything. If this works read up on DTC security and reduce to a minimum.
Or as suggested by DanVallejo remove TransactionScope and just use your explicit transaction.

Related

Multiple databases (multiple DbContexts) in one transaction using TransactionScope

I have got two DbContexts(One Model-first and the other one is code first) which connects to two different databases in MSSQL.
Now when you call SaveChanges from any class, it writes data to both the databases at the same time using TransactionScope class. Code looks like below.
using (TransactionScope scope = new TransactionScope())
{
using (Schema1Entities db1 = new Schema1Entities())
{
db1.SaveChanges();
}
using (Schema2Entities db2 = new Schema2Entities())
{
db2.SaveChanges();
}
scope.Complete();
}
The problem raises during runtime. it is saying that
An exception of type 'System.Data.Entity.Core.EntityException'
occurred in EntityFramework.SqlServer.dll but was not handled in user
code
Additional information: The underlying provider failed on Open.
Inner-exception message - {"The partner transaction manager has
disabled its support for remote/network transactions. (Exception from
HRESULT: 0x8004D025)"}
Turned on MSDTC, and no firewall is blocking the MSDTC.
Need help immediately.
As per the comments under the question, this solved the issue:
using (TransactionScope scope = new TransactionScope())
{
using (Schema1Entities db1 = new Schema1Entities())
using (Schema2Entities db2 = new Schema2Entities())
{
db1.SaveChanges();
db2.SaveChanges();
}
scope.Complete();
}

BeginTransaction on localdb

I have an connection to LocalDB database:
Data Source=(LocalDb)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\aspnet-mysite-20160510105433.mdf
I used code first with Entity Framework and the following piece of code:
var tran = this.context.Database.BeginTransaction();
//some operations on dbcontext
tran.Rollback();
where context variable is simple DbContext instance.
THe exception "The underlying provider failed on rollback" is thrown.
Such an exception is not thrown when using normal, SQL Server connection.
Does it mean that localdb doesn't support transaction? If yes, how to achieve it?
Or does it come from that connections run per process, so when query is over then connection is closed automatically?
Edit: sample operations that I perform:
var myEntity = new MyEntityA();
this.context.MyEntitiesA.Add(myEnttiy).
this.context.Save(); //save to retreive id
// some playing with id
var mySecondEntity= new MyEntityB(){ MyEntityAId = myEntityA.Id, //other data gethered in "playing part" } ;
this.context.MyEntitiiesB.Add(mySecondEntity).
this.context.Save(); //need to rollback first, when here fails.
So I want to enclose within transaction:
using (var tran = this.context.Database.BeginTransaction())
{
try
{
// operations
tran.Commit();
}
catch (Exception ex)
{
// stuff
tran.Rollback();
}
}
So as you can see transaction is useful here.

TransactionScope error in ambient transaction does not rollback the transaction

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

Does TransactionScope work with pre-existing connections?

I have a code like this:
try
{
using (TransactionScope scope = new TransactionScope())
{
some_db_function();
for (i = 0; i < 10; i++)
{
some_other_db_function();
}
scope.Complete();
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message + " all done transactions will rollback");
}
and inside the db functions something like this happens:
private void some_db_functions()
{
using (TransactionScope scope = new TransactionScope())
{
//some processing on db
scope.Complete();
}
}
It is supposed to be that if there was any problem in the database transactions, like an error inserting or updating in the functions; all the transactions that had been done so far get rolled back. But it does not work like that; and although it throws an exception and the scope.Complete() in the parent function never gets triggered, still nothing get rolled back.
Where is the problem?
If the open connection already exists it will not automatically enlist in an ambient transaction. You would have to set it explicitly.
Implicitly enlisting connections is not supported. To enlist in a
transaction scope, you can do the following:
Open a connection in a transaction scope.
Or, if the connection is already opened, call EnlistTransaction method
on the connection object.
Ref.
This will enlist an existing connection:
connection.EnlistTransaction(Transaction.Current)
IIRC, automatic enlisting into ambient transactions happens at connection creation/opening time; if you create the connection inside the scope of the transaction, all should be good. However:
they are all using the same connection, declared previously
if the connection exists outside of the transaction, it won't enlist.
Best practice is to create/open a connection only around a unit of work, not forever (and: let connection pooling do its job). If you follow that practice, it should work fine. So:
This won't work:
using(var conn = CreateAndOpenConnection()) {
// ...
using(var tran = new TransactionScope()) {
SomeOperations(conn);
tran.Complete();
}
// ...
}
where-as this should work:
using(var tran = new TransactionScope()) {
// ...
using(var conn = CreateAndOpenConnection()) {
SomeOperations(conn);
}
tran.Complete();
// ...
}

How to use transactions with a datacontext

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

Categories