Multiple databases (multiple DbContexts) in one transaction using TransactionScope - c#

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

Related

The transaction has aborted error though

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?

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.

Nested transaction scope .net

I'm using SQL Server 2008 R2 and trying to use transactions.
First a question about transactions in .net and SQL Server. If I have something like this
try {
var transactionOption = new TransactionOptions();
transactionOption.IsolationLevel = IsolationLevel.ReadCommitted;
transactionOption.Timeout = TransactionManager.MaximumTimeout;
using (var scope = new TransactionScope(TransactionScopeOption.RequiresNew, transactionOption)) {
//create question this creates a new question in the database
Helpers.CreateQuestionBankItem(ref mappedOldNewQuestionItemGuid, missingQuestionBankItems);
//question created
//query database for the code of the newly inserted question, will the database give me the code since Complete has not been called as yet?
scope.Complete();
}
}
catch (Exception ex) {
throw;
}
//query database for the code of the newly inserted question, will the database give me the code since Complete has been called as now?
At which point should I call the database to ask for the code of the newly inserted question. Now my second question, before I ask I found this link Nested Transaction . In the light of the above link I want to still ask that if I have something like this
try {
var transactionOption = new TransactionOptions();
transactionOption.IsolationLevel = IsolationLevel.ReadCommitted;
transactionOption.Timeout = TransactionManager.MaximumTimeout;
using (var outerscope = new TransactionScope(TransactionScopeOption.RequiresNew, transactionOption)) {
try {
var transactionOption = new TransactionOptions();
transactionOption.IsolationLevel = IsolationLevel.ReadCommitted;
transactionOption.Timeout = TransactionManager.MaximumTimeout;
using (var innerscope = new TransactionScope(TransactionScopeOption.RequiresNew, transactionOption)) {
//create question this creates a new question in the database
Helpers.CreateQuestionBankItem(ref mappedOldNewQuestionItemGuid, missingQuestionBankItems);
//question created
//query database for the code of the newly inserted question, will the database give me the code since Complete has not been called as yet?
innerscope.Complete();
}
}
catch (Exception ex) {
}
//query database for the code of the newly inserted question, will the database give me the code since Complete has been called as now?
outerscope.Complete();
}
}
catch (Exception ex) {
throw;
}
If my innerscope completes, will querying SQL Server give me the code of the newly created question.
What happens if the inner scope throws an exception and I gobble it up, will the outer scope also be disposed off?
Does calling innerscope.Complete() completes that inner scope?
If you want to recover from a failure in a transactional context you need to use transaction savepoints. Unfortunately the managed System.Transaction has no support for savepoints. Not only that, but you won't be able to use savepoints, even directly, if you use transaction scopes, because the transaction scope will escalate to distributed transactions and savepoints do not work in distributed contexts.
You can use instead the platform specific SqlTransaction which supports Save() for savepoints. See Exception Handling and Nested Transactions for an example of transaction-aware exception handling.

How to use TransactionScope?

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.

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

Categories