I see there are two main options for managing transactions with llblgen.
Method 1:
using(DataAccessAdapter adapter = new DataAccessAdapter())
{
adapter.StartTransaction(IsolationLevel.ReadCommitted, "TR");
try
{
// ...
adapter.Commit();
}
catch
{
adapter.Rollback();
throw;
}
}
Method 2:
using(TransactionScope scope = new TransactionScope())
{
// ...
scope.Complete();
}
What is your prefered method and why? (I'm using adapapter/2.6 .net/3.5)
I would lean towards using TransactionScope for managing transactions as this is what it was designed for whereas the DataAccessAdapter, while it has the ability to create transactions is designed primarily for DataAccess.
To try and be a little clearer, you could use TransactionScope to manage multiple transactions across multiple DataAccessAdapters whilst a single DataAccessAdapter appears to have a specific scope.
For example:
using(TransactionScope ts = new TransactionScope())
{
using(DataAccessAdapter d1 = new DataAccessAdapter())
{
//do some data access stuff
}
using(DataAccessAdapter d2 = new DataAccessAdapter())
{
//do some other data access stuff
}
ts.complete();
}
Another side note is that TransactionScope is thread safe, where as DataAdapters are not.
Related
I have code that looks like the example below. There's an explicit transaction involved because of some database tomfoolery that needs to be done via a SP, and a save changes in the middle of it all. (Exception handling, rollbacks, etc.. omitted):
void OuterMethod(MyDatbase context)
{
using(var dbTrans = context.Database.BeginTransaction())
{
// some stuff, the save puts the data where the SP can see it
Stuff(context);
context.SaveChanges();
// now some SP stuff
context.Database.ExecuteSqlCommand(#"spFoo", params);
// more stuff
MoreStuff(context);
AlmostUnrelatedCode(context);
context.SaveChanges();
dbTrans.Commit();
}
}
Right now the method AlmostUnrelatedCode() -- which is only marginally related to the process above -- needs a nice, fast, disposable read-only context 99% of the time. I have a factory that will serve me up the right kind of context when I need it. The 1% of the time it's called from the middle of that block above.
MyDatabase localReadOnlyContext;
void AlmostUnrelatedCode(MyDatabase context)
{
if ( context.Database.CurrentTransaction != null )
{
// Must use the context passed or everything deadlocks :(
localReadOnlyContext = context;
disposeContextLater = false;
}
else
{
// I just want to do this all the time
localReadOnlyContext = _contextFactory.CreateReadOptimized();
disposeContextLater = true;
}
// Do many, many things with my read-optimized context...
// The Dispose() on the class will check for disposeContextLater
}
What I'd like to do is to get rid of that transaction check, and in fact not need to pass the outer context at all if I can help it.
What I've tried:
Just ignoring what's going on in the outer transaction and using the context I generate all the time. Problem: deadlocks.
Trying to get the outermost transaction into the EF context I create with the _contextFactory. Problem: EF context constructors don't allow you to pass an existing transaction; also Database.CurrentTransaction has no setter.
Pulling the whole transaction out into a TransactionScope that wraps everything up. Problem: the method OuterMethod passes in the context, and I don't have control of the caller.
What I can't try:
Dirty reads/nolock. AlmostUnrelatedCode() needs the data as written so far.
I'd rather not:
Just keep using the outer context while inside of AlmostUnrelatedCode. AlmostUnrelatedCode deals with a lot of data trees and that context gets fat and unhappy really fast. It pollutes its context with crap really fast, and I'd rather just dispose of it when I'm done.
you can prevent the deadlocks by using one connection for multiple contexts.
example
var efConnectionString = ConfigurationManager.ConnectionStrings["SomeEntities"].ConnectionString;
// note EntityConnection, not SqlConnection
using (var conn = new EntityConnection(efConnectionString)) {
// important to prevent escalation
await conn.OpenAsync();
using (var c1 = new SomeEntities(conn, contextOwnsConnection: false)) {
//Use some stored procedures etc.
count1 = await c1.SomeEntity1.CountAsync();
}
using (var c2 = new SomeEntities(conn, contextOwnsConnection: false)) {
//Use some stored procedures etc.
count2 = await c2.SomeEntity21.CountAsync();
}
}
in your case just get the connection from the context and reuse it
context.Database.Connection
Can't you separate things done in AlmostUnrelatedCode like this:
void AlmostUnrelatedCode()
{
var context = _contextFactory.CreateReadOptimized();
AlmostUnrelatedCode(context);
context.Dispose();
}
void AlmostUnrelatedCode(MyDatabase context)
{
// Do many, many things with context...
}
Now you can call AlmostUnrelatedCode(with param) from your OuterMethod. And maybe there is even more to be separated. Consider SOLID.
Considering this piece of code:
using(TransactionScope tran = new TransactionScope()) {
insertStatementMethod1();
insertStatementMethod2();
// this might fail
try {
insertStatementMethod3();
} catch (Exception e) {
// nothing to do
}
tran.Complete();
}
Is anything done in insertStatementMethod1 and insertStatementMethod2 going to be rolled back? In any case?
If I want them to execute anyway, I would need to check if it insertStatementMethod3 will fail before the transaction, and build my transaction code based on that?
Update
The code looks similar to this
using(TransactionScope tran = new TransactionScope()) {
// <standard code>
yourExtraCode();
// <standard code>
tran.Complete();
}
where I get to write the yourExtraCode() method
public void yourExtraCode() {
insertStatementMethod1();
insertStatementMethod2();
// this call might fail
insertStatementMethod3();
}
I can only edit the yourExtraCode() method, so I cannot chose to be in the transaction scope or no. One simple possible solution would be this:
public void yourExtraCode() {
insertStatementMethod1();
insertStatementMethod2();
// this call might fail
if (findOutIfIcanInsert()) { // <-- this would come by executing sql query
try {
insertStatementMethod3();
} catch (Exception e) {
// nothing to do
}
}
}
But that would come with the need of looking up things in the db which would affect performance.
Is there a better way, or I need to find out before I'd call the method?
I tried out and, of course the transaction was rolled back as expected.
If you don't want your first two methods to be transacted, just move them out from the ambient transaction's scope.
If you don't have control over the code which starts an ambient transaction, you can suppress it by creating a new ambient transaction: using (var scope = new TransactionScope(TransactionScopeOption.Suppress)).
I have multiple methods inside a Parallel.Invoke() that need to run inside of a transaction. These methods all invoke instances of SqlBulkCopy The use-case is "all-or-none", so if one method fails nothing gets committed. I am getting a TransactionAbortedException ({"Transaction Timeout"}) when I call the Complete() method on the parent transaction.
This is the parent transaction:
using (var ts = new TransactionScope())
{
var saveClone = Transaction.Current.DependentClone(DependentCloneOption.BlockCommitUntilComplete);
var saveErrorsClone = Transaction.Current.DependentClone(DependentCloneOption.BlockCommitUntilComplete);
var saveADClone = Transaction.Current.DependentClone(DependentCloneOption.BlockCommitUntilComplete);
var saveEnrollmentsClone = Transaction.Current.DependentClone(DependentCloneOption.BlockCommitUntilComplete);
Parallel.Invoke(_options, () =>
{
Save(data, saveClone);
},
() =>
{
SaveErrors(saveErrorsClone);
},
() =>
{
SaveEnrollments(data, saveEnrollmentsClone);
});
ts.Complete();
}//***** GET THE EXCEPTION HERE *****
Here's a dependent transaction that makes use of SqlBulkCopy (they're all the same structure). I'm passing-in the parent and assigning it to the child's TransactionScope
private void Save(IDictionary<string, string> data, Transaction transaction)
{
var dTs = (DependentTransaction)transaction;
if (transaction.TransactionInformation.Status != TransactionStatus.Aborted)
{
using (var ts = new TransactionScope(dTs))
{
_walmartData.Save(data);
Debug.WriteLine("Completed Processing XML - {0}", _stopWatch.Elapsed);
ts.Complete();
}
}
else
{
Debug.WriteLine("Save Not Executed - Transaction Aborted - {0}", _stopWatch.Elapsed);
dTs.Complete();
}
dTs.Complete();
}
EDIT (added my SqlBulkCopy method...notice null for the transaction param)
private void SqlBulkCopy(DataTable dt, SqlBulkCopyColumnMappingCollection mappings)
{
try
{
using (var sbc = new SqlBulkCopy(_conn, SqlBulkCopyOptions.TableLock, null))
{
sbc.BatchSize = 100;
sbc.BulkCopyTimeout = 0;
sbc.DestinationTableName = dt.TableName;
foreach (SqlBulkCopyColumnMapping mapping in mappings)
{
sbc.ColumnMappings.Add(mapping);
}
sbc.WriteToServer(dt);
}
}
catch (Exception)
{
throw;
}
}
Besides fixing the error, I'm open to alternatives. Thanks.
You're creating a form of deadlock with your choice of DependentCloneOption.BlockCommitUntilComplete.
Parallel.Invoke blocks the calling thread until all of its processing is complete. The jobs trying to be completed by Parallel.Invoke are all blocking while waiting for the parent transaction to complete (due to the DependentCloneOption). So the 2 are waiting on each other... deadlock. The parent transaction eventually times out and releases the dependent transactions from blocking, which unblocks your calling thread.
Can you use DependentCloneOption.RollbackIfNotComplete ?
http://msdn.microsoft.com/en-us/library/system.transactions.transactionscope.complete.aspx says that TransactionScope.Complete only commits the transaction it contains if it was the one that created it. Since you are creating the scope from an existing transaction I believe you will need to commit the transaction before calling complete on the scope.
From MSDN:
The actual work of commit between the resources manager happens at the
End Using statement if the TransactionScope object created the
transaction. If it did not create the transaction, the commit occurs
whenever Commit is called by the owner of the CommittableTransaction
object. At that point the Transaction Manager calls the resource
managers and informs them to either commit or rollback, based on
whether this method was called on the TransactionScope object
.
After a lot of pain, research, and lack of a valid answer, I've got to believe that it's not possible with the stack that I described in my question. The pain-point, I believe, is between TransactionScope and SqlBulkCopy. I put this answer here for the benefit of future viewers. If someone can prove that it can be done, I'll gladly remove this as the answer.
I believe that how you create your _conn-instance matters a lot, if you create it and open it within your TransactionScope-instance any SqlBulkCopy-related issues should be solved.
Have a look at Can I use SqlBulkCopy inside Transaction and Is it possible to use System.Transactions.TransactionScope with SqlBulkCopy? and see if it helps you.
void MyMainMethod()
{
using (var ts = new TransactionScope())
{
Parallell.InvokeOrWhatNotOrWhatEver(() => DoStuff());
}
}
void DoStuff()
{
using (var sqlCon = new SqlConnection(conStr))
{
sqlCon.Open(); // ensure to open it before SqlBulkCopy can open it in another transactionscope.
using (var bulk = new SqlBulkCopy(sqlCon))
{
// Do you stuff
bulk.WriteToServer...
}
ts.Complete(); // finish the transaction, ie commit
}
}
In short:
Create transaction scope
Create sql-connection and open it under the transaction scope
Create and use SqlBulkCopy-instance with above created conncection
Call transaction.Complete()
Dispose of everything :-)
Suppose my project is like .net petshop.
It has a BLL, DAL and SQLHelper.
Normally, I call a BLL function in my web layer, and the BLL function calls the DAL function and finally, the DAL call the sqlhelper.
But in some situations, I nedd a transaction.
For example:
Web layer:
I need Call some BLL functions.
Code as below:
var m = BLLFunction_1();
var n= BLLFunction_2();
if (m+n<100)
{
// need rollback here
}
else
{
BLLFunction_3();
// commit here
}
So it makes me have to use a transaction object in the web layer, to pass it into the BLL function, and BLL layer pass it into DAL layer, and finally pass it into SQLHelper.
That's a little ugly.
I wonder what is a elegant methed to this situation.
I am assuming you are looking for Transaction in ADO.NET.
Basically you need to wrap your "actions" into a TransactionScope.
try
{
using(TransactionScope ts = new TransactionScope())
{
//perform SQL
using(SqlHelper sh = new SqlHelper())
{
//do stuff
}
//call new DAL function
//call other DAL function
ts.Complete();
}
}
catch(Exception ex)
{
throw ex;
}
Hi create Transaction in your BLL functions with TransactionScopeOption Required
public void BLLFunction_1()
{
using(TransactionScope ts = new TransactionScope(TransactionScopeOption.Required))
{
//do ur stuff here
ts.Complete();
}
}
public void BLLFunction_2()
{
using(TransactionScope ts = new TransactionScope(TransactionScopeOption.Required))
{
//do ur stuff here
ts.Complete();
}
}
With TransactionScopeOption Required : A transaction is required by the scope. It uses an ambient transaction if one already exists. Otherwise, it creates a new transaction before entering the scope. This is the default value. So here your BLLFunction_2 will use the Transaction of BLLFunction_1 instead of creating new.
public void SomeMethod1()
{
using (TemplateEntities ctx = new TemplateEntities())
{
//do something in this ctx
}
}
public void SomeMethod2()
{
using (TemplateEntities ctx = new TemplateEntities())
{
//do something else in this ctx
}
}
public void SomeMethod()
{
using (TemplateEntities ctx = new TemplateEntities())
{
using (TransactionScope tran = new TransactionScope())
{
SomeMethod1();
SomeMethod2();
var itemToDelete= (from x in ctx.Xxx
where x.Id==1
select x).Single();
ctx.Xxx.DeleteObject(itemToDelete);
ctx.SaveChanges();
tran.Complete();
}
}
}
What happens in SomeMethod is executed in a transaction even if there are more contexts?
I am using POCO.
If you use TransactionScope with multiple ObjectContext instances the transaction will be promoted to distributed and whole operation (SomeMethod) will be handled still as atomic. But distributed transaction requires additional NT service and its dependecies. The service is called Microsoft Distributed Transaction Coordinator (MSDTC). This service has to run on all involved servers (application server and database server). In network scenario service requires some additional configuration. For communication RPC ports have to be opened in firewalls.
Ultimately the database doesn't know about data-contexts, so simply: the rules of transactions apply. Being a serializable transaction, things like read locks and key-range-locks will be issued and honoured. As always, there is a risk of complication from deadlocks, but ultimately it should work. Note that all the contexts involved should enlist as required.