How to rollback a transaction using dapper - c#

I have this:
using (var con= new SqlConnection(ConfigurationManager.ConnectionStrings["sqlcon"].ConnectionString))
{
try
{
// many transactions
}
catch (Exception e)
{
con.BeginTransaction().Rollback();
}
}
Will this work is my question.. I know another method is to make a transaction then open it then rollback.

You could use a TransactionScope variable in a using block at the same level of the using block of the SqlConnection
using (TransactionScope scope = new TransactionScope())
using (var con= new SqlConnection(ConfigurationManager.ConnectionStrings["sqlcon"].ConnectionString))
{
try
{
// many transactions
scope.Complete();
}
catch (Exception e)
{
// Not needed any rollback, if you don't call Complete
// a rollback is automatic exiting from the using block
// con.BeginTransaction().Rollback();
}
}

Related

Transient Fault Retry logic best practices

Friends, I have a question about implementing a simple retry policy around the execution of the SQL command.
My question is: should the retry loop encapsulate the construction of the connection and transaction, or should it live inside the connection.
For example:
private void RetryLogSave(DynamicParameters parameters, int retries = 3)
{
int tries = 0;
using (var connection = new SqlConnection(_connectionString))
{
connection.Open();
using (var transaction = connection.BeginTransaction())
{
var logItemCommand = new CommandDefinition(commandText: Constants.InsertLogItem,
parameters: parameters, transaction: transaction, commandType: System.Data.CommandType.Text);
do
{
try
{
tries++;
connection.Execute(logItemCommand);
transaction.Commit();
break;
}
catch (Exception exc)
{
if (tries == retries)
{
transaction.Rollback();
throw exc;
}
Task.Delay(100 * tries).Wait();
}
}
while (true);
}
}
}
Is what I've done here appropriate and acceptable, or should the retry logic live on the outside of the SqlConnection construction?
Formalizing my comments as an answer.
should the retry logic live on the outside of the SqlConnection
construction?
Yes. If doing retry logic with keeping connection opened you're wasting resources. Someone else may use it while you're waiting N seconds for re-try. Opening/closing connections is usually (for most ODBC drivers) implemented on top of Connection Pooling mechanism. You do not actually close it - you allow connection to go back in pool to be reused by someone else. Keeping connections opened during re-try will force system to create more and more new physical connections (because they are not returned to the pool) and eventually your SQL Server will be exhausted.
Regarding re-try mechanism - to not reinvent the wheel, I usually use Polly library.
You can define somewhere static class with list of your polices:
public static class MyPolices
{
// Retry, waiting a specified duration between each retry
public static Policy RetryPolicy = Policy
.Handle<Exception>() // can be more specific like SqlException
.WaitAndRetry(new[]
{
TimeSpan.FromSeconds(1),
TimeSpan.FromSeconds(2),
TimeSpan.FromSeconds(3)
});
}
Then, simplify your method to
private void LogSave(DynamicParameters parameters)
{
using (var connection = new SqlConnection(_connectionString))
{
connection.Open();
using (var transaction = connection.BeginTransaction())
{
// make sure to not forget to dispose your command
var logItemCommand = new CommandDefinition(commandText: Constants.InsertLogItem,
parameters: parameters, transaction: transaction, commandType: System.Data.CommandType.Text);
try
{
// not sure if conn.Execute is your extension method?
connection.Execute(logItemCommand);
transaction.Commit();
}
catch (Exception exc)
{
transaction.Rollback();
throw;
}
}
}
}
and call it like this
MyPolices.RetryPolicy.Execute(() => LogSave(parameters));
This approach will make your code more SOLID keeping retry logic in isolation.

How does SQLTransaction.Commit() work?

A few day ago, I have studied SqlTransaction and I know the purpose of SqlTransaction.Commit() - it should "commit the database transaction." - MSDN.
But HOW DOES IT WORK?
For example: I wrote a piece of code like this:
using (SqlTransaction tran = connection.BeginTransaction())
{
try
{
using (SqlCommand cmd = connection.CreateCommand())
{
cmd.CommandText = msg.command;
cmd.Transaction = tran;
cmd.ExecuteNonQuery();
}
}
catch (Exception)
{
// if all of above have any exception, that's mean my transaction is
// failure and my database has no change.
return false;
}
tran.Commit();
// if all of above have no problems, that's mean my transaction is successful
return true;
connection.Dispose();
}
In this case, SQL Server is on another computer.
I guess: commit method has two periods, Period 1: when I implement tran.Commit(), compiler will signal SQL Server and talk to SQL Server that: "I'm ok, please help me commit (change) data", and then SQL Server will implement compiler's request. Period 2: when SQL Server implement compiler's request completely, implement result will be return to our compiler. When our compiler receive implement result, our compiler will continue compile the next command line ("return true").
But if in second period, the connection is broken and implement result isn't transferred back to our compiler. In this case, our transaction is success or not? Is data persisted in SQL Server or not?
Additional question: my prediction about two period of SQLTransaction.Commit() is true or not?
Thanks!
try
{
using (var conn = new SqlConnection(/* connection string or whatever */))
{
conn.Open();
using (var trans = conn.BeginTransaction())
{
try
{
using (var cmd = conn.CreateCommand())
{
cmd.Transaction = trans;
/* setup command type, text */
/* execute command */
}
trans.Commit();
}
catch (Exception ex)
{
trans.Rollback();
/* log exception and the fact that rollback succeeded */
}
}
}
}
catch (Exception ex)
{
/* log or whatever */
}
and read this also
https://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqltransaction.commit(v=vs.110).aspx

The operation is not valid for the state of the transaction

I have a TransactionScope() block. It always gets stuck in an insert statement. It appears in the Activity Monitor as a Blocking Task, so it blocks the SQL server, and after the timeout, I get this error:
The operation is not valid for the state of the transaction.
What’s going wrong?
const TransactionScopeOption opt = new TransactionScopeOption();
TimeSpan span = new TimeSpan(0, 0, 1, 30);
try
{
using (TransactionScope scope01 = new TransactionScope(opt, span))
{
using (var sqlcon = new SqlConnection(sSqlCon))
{
//select,insert , update statements
}
}
}
catch (Exception ex)
{
}
It probably means it aborted. Did you call transaction complete within transaction bracket?
try
{
using (TransactionScope scope01 = new TransactionScope(opt, span))
{
using (var sqlcon = new SqlConnection(sSqlCon))
{
//select,insert , update statements
}
scope01.Complete();
}
}
If it doesn't call the Complete, it will automatically rollback.
The transaction might have been timed out. check the maching.config for the default time out
<configuration>
<system.transactions>
<machinesettings maxtimeout="00:30:00" />
</system.transactions>
</configuration>

Understanding how transaction.complete function?

Hi All we have started to use transaction scope and below is the code snippet. What we need to understand is based on our understading after each of the using for connection the particular connection will be disposed/closed right? So since its closed how does the transaction.complete works?
using (TransactionScope transScope = new TransactionScope())
{
try
{
string myConnStringLocal = "User Id=***;Password=****;Host=" + globalSettings.settingLocalIP + ";Database=" + globalSettings.settingLocalDB;
using (MySqlConnection connectionLocal = new MySqlConnection(myConnStringLocal))
{
try{
connectionLocal.Open();
}
Catch(Exception e)
{
}
finally{
connectionLocal.Close();
}
}
string myConnStringCentral = "User Id=***;Password=*****;Host=" + globalSettings.settingCentralIP + ";Database=" + globalSettings.settingCentralDB;
using (MySqlConnection connectionCentral = new MySqlConnection(myConnStringCentral))
{
try{
connectionCentral.Open();
}
Catch(Exception e)
{
}
finally{
connectionCentral.Close();
}
}
string myConnStringCentralCopy = "User Id=*****;Password=*****;Host=" + globalSettings.settingCentralCopyIP + ";Database=" + globalSettings.settingCentralCopyDB;
using (MySqlConnection connectionCentralCopy = new MySqlConnection(myConnStringCentralCopy))
{
try{
connectionCentralCopy.Open();
}
Catch(Exception e)
{
}
finally{
connectionCentralCopy.Close();
}
}
transScope.Complete();
Console.WriteLine("Transaction is completed");
}
catch (Exception)
{
Console.WriteLine("Transaction is rolled back");
}
}
TransactionScope.Complete tells all of the Transaction Managers that they are good to commit this transaction. It is not a guarantee that everything will actually commit. After the Complete method is called all the transaction managers initiate a commit individually and if all of the transaction Managers succeed then the transaction is considered as completed successfully.
You may refer to this link for further details
When you connect to multiple databases in a single TransactionScope, the transaction is escalated to a distributed transaction and coordinated by MSDTC using 2-phase commit.
Regarding connection close - this is special case when connection is closed inside a TransactionScope, internally it is managed by System.Transactions infrastructure, so db session may still be open even though connection closed from your end.
See this note on MSDN:
Pending transactions started using Transact-SQL or BeginTransaction are automatically rolled back when the connection is reset if connection pooling is enabled. If connection pooling is off, the transaction is rolled back after SqlConnection.Close is called. Transactions started through System.Transactions are controlled through the System.Transactions infrastructure, and are not affected by SqlConnection.Close
EDIT Based on your comments, here is what you can do:
try
{
using (TransactionScope transScope = new TransactionScope())
{
string myConnStringLocal = ...;
using (var connectionLocal = new MySqlConnection(myConnStringLocal))
{
connectionLocal.Open();
// do your DB update
} //no need to close connection explicitly, the using() {..} statement does that for you
string myConnStringCentral = ...;
using (var connectionCentral = new MySqlConnection(myConnStringCentral))
{
connectionCentral.Open();
// do your DB update
} //no need to close connection explicitly, the using() {..} statement does that for you
string myConnStringCentralCopy = ...;
using (var connectionCentralCopy = new MySqlConnection(myConnStringCentralCopy))
{
connectionCentralCopy.Open();
// do your DB update
} //no need to close connection explicitly, the using() {..} statement does that for you
transScope.Complete();
Console.WriteLine("Transaction is completed");
} //no need to dispose transactionScope explicitly, the using() {..} statement does that for you
}
catch (Exception)
{
// If any exception occurs in the try block above transScope.Complete() line will be caught here
// and will automatically cause the transaction to rollback.
Console.WriteLine("Transaction is rolled back");
}
// You can then start new TransactionScope if you want to further update more than one DB in a transactional manner.
try
{
using (TransactionScope transScope = new TransactionScope())
{
//...
}
}
catch (Exception)
{
//...
}
When the Complete method is called, then everything in the scope will be committed, if no Exception is thrown. If the code gets out of scope without Complete, then no commit will occur. In short, if you call the Complete method, then, in case there are no Exceptions thrown, your transaction(s) in the scope of the given TransactionScope will be committed.
Also, I must add, that there might also be a hierarchy, a tree of TransactionScopes. You can also set the behavior of a TransactionScope for the case when a sub-scope of the TransactionScope rolled back.

TransactionScope and Npgsql - Prepared Transaction Issue

I have following code:
//...
using (TransactionScope tScope = new TransactionScope())
{
using (NpgsqlConnection myConnection = new NpgsqlConnection(_MyConnectionString))
{
try
{
myConnection.Open();
//...
tScope.Complete();
}
catch (Exception ex)
{
logger.Error(ex);
}
finally
{
myConnection.Close();
}
}
}
//...
The problem is, when some error occurred transactionscope do rollback, but, on the PostgreSQL server I have prepared transaction. And applications can not work anything with database server until I delete prepared transaction manualy.
Any solution?
BTW, I use Npgsql version: 2.0.11.91
Best regards

Categories