Calling TransactionScope.Complete when executing multiple sql statements in DAL - c#

I was reading up on this and it seemed like I shouldn't have to call scope.Complete for the following code to work.
When I try running this without using scope.Complete() the records are saved to the database. If scope.Complete() is there the records save and everything works.
public static void SaveProducts(IList<Product> products)
{
using (TransactionScope scope = new TransactionScope())
{
using (var connection = GetOpenConnection())
{
StringBuilder sqlDelete = new StringBuilder();
sqlDelete.AppendLine("MY SQL STATEMENT HERE ");
StringBuilder sqlInsert = new StringBuilder();
sqlInsert.AppendLine("MY SQL STATEMENT HERE ");
connection.Execute(sqlDelete.ToString(), new { CategoryId = categoryId });
connection.Execute(sqlInsert.ToString(), products);
}
scope.Complete();
}
}

Per the doc:
Failing to call this method aborts the transaction, because the transaction manager interprets this as a system failure, or exceptions thrown within the scope of transaction.
I think it's saying that if there is something that goes wrong, it will be disposed before the complete is signaled.

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?

c# transaction scope with yield

my requirement is to process multiple cost files, which has million of records. after processing and validation I have to add those records to database.
For better performance I am using "yield" in foreach loop and return one record at a time, process that record and immediately add that one record to database with file number. During this file reading process if I come across any data validation error, I throw InvalidRecordException.
My requirement is to delete all the records from table related that file. in short, even if one record is invalid I want to mark that file as invalid file and not add even a single record of that file to database.
can anyone help me here, how can i make use of TransactionScope here.
public class CostFiles
{
public IEnumerable<string> FinancialRecords
{
get
{
//logic to get list of DataRecords
foreach (var dataRecord in DataRecords)
{
//some processing... which can throw InvalidRecord exception
yield return dataRecord;
}
yield break;
}
}
}
public void ProcessFileRecords(CostFiles costFile, int ImportFileNumber)
{
Database db = new Database();
using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required))
{
try
{
foreach (var record in costFile.FinancialRecords)
{
db.Add(record, ImportFileNumber);
}
}
catch(InvalidRecordException ex)
{
//here i want to delete all the records from the table where import file number is same as input paramter ImportFileNumber
}
}
}
The purpose of a transaction scope is to create an "all or nothing" scenario, so either the whole transaction commits, or nothing at all commits. It looks like you already have the right idea (at least in terms of the TransactionScope. The scope won't actually commit the records to the database until you call TransactionScope.Complete(). If Complete() is not called, then the records are discarded when you leave the transaction scope. You could easily do something like this:
using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required))
{
bool errorsEncountered = false;
try
{
foreach (var record in costFile.FinancialRecords)
{
db.Add(record, ImportFileNumber);
}
}
catch(InvalidRecordException ex)
{
//here i want to delete all the records from the table where import file number is same as input paramter ImportFileNumber
errorsEncountered = true;
}
if (!errorsEncountered)
{
scope.Complete();
}
}
Or you can just let the Add throw an exception and handle it outside of the transaction scope instead, as the exception will cause Complete() not to be called, and therefore no records added. This method has the additional advantage of stopping processing of additional records when we already know it will do nothing.
try
{
using (var scope = new TransactionScope(TransactionScopeOptions.Required))
{
foreach(var record in costFile.FinancialRecords)
{
db.Add(record, ImportFileNumber);
}
// if an exception is thrown during db.Add(), then Complete is never called
scope.Complete()
}
catch(Exception ex)
{
// handle your exception here
}
}
EDIT If you don't want your transaction elevated to a distributed transaction (which may have additional security/network requirements), make sure you reuse the same SqlConnection object for every database call within your transaction scope.
using (var conn = new SqlConnection("myConnectionString"))
{
conn.Open();
using (var scope = new TransactionScope(...))
{
foreach(var foo in foos)
{
db.Add(foo, conn);
}
scope.Complete();
}
}

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

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.

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