Should ServiceBroker conversations be inside a transaction - c#

We have some code that looks a bit like this (error handling and other things removed)
using (var tran = conn.BeginTransaction())
{
var client = new Service(...);
var dialog = client.GetConversation(null, conn, tran);
var response = dialog.Receive();
// do stuff with response, including database work
dialog.Send(message, conn, tran);
dialog.EndConversation(conn, tran);
tran.Commit();
conn.Close();
}
We've inherited this code and aren't experts in ServiceBroker, would there be problems if we moved the conversation outside of the transaction like this:
var client = new Service(...);
var dialog = client.GetConversation(null, conn, tran);
var response = dialog.Receive();
using (var tran = conn.BeginTransaction())
{
// do stuff with response, including database work
tran.Commit();
}
dialog.Send(message, conn, tran);
dialog.EndConversation(conn, tran);
conn.Close();

In this case you receive message and its gets removed from the queue. You will not be able to receive it again..
If all code is in transaction and there is error in message processing- transaction never commits and message stays in queue (by default- after 5 rollbacks queue gets disabled). So you can detect the reason of error, correct it and process message again (expected exceptions should not cause rollback, there are quite a few ways to handle them).
I would say that everything should be in transaction.

Related

How do I hook into TransactionScope on completing "event"?

I want to know if there's a way to hook into a currently running transaction and have stuff be done when that transaction completes.
Currently I'm in the process of implementing an EventPublisher that uses MassTransit/RabbitMQ to publish messages, but I want to only have those messages be published when a TransactionScope is getting completed.
I would check inside the EventPublisher.PublishEvent() method if there's currently a transaction running, if no, then fire off the messages, if yes, then collect the messages and wait for the transaction to complete to send them off.
var ep = container.GetInstance<IEventPublisher>();
using(var scope = new TransactionScope())
{
... do some stuff
SaveEntity(entity);
ep.PublishEvent(new EntitySaved(entity.Id));
... do some more stuff ...
UpdateEntity(differentEntity);
ep.PublishEvent(new EntityUpdated(differentEntity.Id));
... do even more stuff ...
ep.PublishEvent(new UnrelatedMessage(someData));
scope.Complete(); // <- only want the actual sending off to RabbitMQ to happen here.
}
I found this TransactionCompleted Event here https://learn.microsoft.com/en-us/dotnet/api/system.transactions.transaction.transactioncompleted
But it seems to only be fired after the scope.Complete() bits are over and done.
I could use this to check if the transaction status is completed and then like actually fire off those messages I collected during the transaction. But my problem is that the connection to RabbitMQ could be down. And then I wouldn't be able to send those messages, but all the work above has been done already, but I never was able to send off those messages.
What I actually want is to somehow hook into the bit where the transaction is currently completing and during that process I fire off my messages and if that fails I can throw an exception and have that still running transaction go out the window.
Maybe there's a way to do that with MassTransit, but the documentation isn't really forthcoming there.
Here's some example code showing the problem.
internal class Program
{
private static void Main(string[] args)
{
const string connectionString = "Data Source=.;Initial Catalog=MyDatabase;Integrated Security=True";
var sql = #"insert into [SomeTable] (
[Id]
,[Name]
,[Index]
,[RelationId]
) values (#param1, #param2, #param3, #param4) ";
using (var scope = new TransactionScope())
{
Transaction.Current.TransactionCompleted += CurrentOnTransactionCompleted;
using (var con = new SqlConnection(connectionString))
{
con.Open();
using (var cmd = new SqlCommand(sql, con))
{
cmd.Parameters.Add("#param1", SqlDbType.UniqueIdentifier).Value = Guid.NewGuid();
cmd.Parameters.Add("#param2", SqlDbType.NVarChar, 128).Value = "Blah";
cmd.Parameters.Add("#param3", SqlDbType.SmallInt).Value = 1;
cmd.Parameters.Add("#param4", SqlDbType.UniqueIdentifier).Value = Guid.Parse("a401866d-3bdd-48a4-a78b-d40864c8471b");
cmd.CommandType = CommandType.Text;
cmd.ExecuteNonQuery();
}
}
scope.Complete();
}
}
private static void CurrentOnTransactionCompleted(object sender, TransactionEventArgs e)
{
// I want to do stuff here but if this stuff fails I need the whole transaction to roll back.
... do some stuff that can fail ...
e.Transaction.Rollback(new Exception("Bad transaction!"));
// or
throw new Exception("Bad transaction!");
}
}
I was looking in the wrong place. After looking through microsoft/referencesource I found what I was looking for in TransactionContext.cs.
What you need is to hook up an IEnlistmentNotification which is described here: https://learn.microsoft.com/en-us/dotnet/api/system.transactions.transaction.enlistvolatile
I need to put my code inside the Prepare method and if it fails call ForceRollback.

Setting CommandTimeout to solve lock wait timeout exceeded try restarting transaction

I have an error like this
lock wait timeout exceeded try restarting transaction
Maybe I didn't understand it. But I've a solution if I set CommandTimeout=1000 or something higher. I didn't try it in production yet. But I'd like to hear any opinion on this.
// 40 lines of command.Parameters here
command.Parameters.AddWithValue("sample1", sam1);
command.Parameters.AddWithValue("sample2", sam2);
command.Parameters.AddWithValue("sample3", sam2);
try
{
command.ExecuteNonQuery();
}
catch (MySqlException mex)
{
I was receiving "lock wait timeout exceeded try restarting transaction" intermittently. Then I started wrapping everything in transactions and I stopped receiving those errors. This should prevent table locks from remaining after the query is executed.
(Assuming "conn" is a MySqlConnection, "iLevel" is the isolation level you want to use, and "query" contains your query as a string)
int rowCount = 0; // In case you want the number of rows affected
try
{
if (conn.State != ConnectionState.Open)
conn.Open();
MySqlCommand command = new MySqlCommand(query, conn);
using(var transaction = conn.BeginTransaction(iLevel))
{
command.Transaction = transaction;
command.CommandTimeout = int.MaxValue;
// Set parameters etc...
try
{
rowCount = command.ExecuteNonQuery();
transaction.Commit();
}
catch(Exception ex)
{
transaction.Rollback();
// Handle query exception...
}
}
}
catch(Exception ex)
{
// Handle general exception...
}
You could try (just for testing purposes) set transaction isolation level =
"READ COMMITTED" and if this fails try set to "READ UNCOMMITTED"
MySql reference
check for dead lock:
"SHOW ENGINE INNODB STATUS" from the MySQL Command line client (not a
query browser) will give you info on deadlocks.
Deadlocks can also be caused by uncommitted transactions (usually
program bugs) and the person who is running the uncommitted
transaction will not see the problem as they will be working fine
(through their data will not be committed). Quote from here

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

SQLTransaction has completed error

I got following error once in my application.
This SQLTransaction has completed; it is no longer usable
Stack Trace is attached below – It says about Zombie Check and Rollback.
What is the mistake in the code?
Note: This error came only once.
UPDATE
From MSDN - SqlTransaction.Rollback Method
A Rollback generates an InvalidOperationException if the connection is terminated or if the transaction has already been rolled back on the server.
From Zombie check on Transaction - Error
One of the most frequent reasons I have seen this error showing up in various applications is, sharing SqlConnection across our application.
CODE
public int SaveUserLogOnInfo(int empID)
{
int? sessionID = null;
using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open();
SqlTransaction transaction = null;
try
{
transaction = connection.BeginTransaction();
sessionID = GetSessionIDForAssociate(connection, empID, transaction);
//Other Code
//Commit
transaction.Commit();
}
catch
{
//Rollback
if (transaction != null)
{
transaction.Rollback();
transaction.Dispose();
transaction = null;
}
//Throw exception
throw;
}
finally
{
if (transaction != null)
{
transaction.Dispose();
}
}
}
return Convert.ToInt32(sessionID,CultureInfo.InvariantCulture);
}
Stack Trace
REFERENCE:
What is zombie transaction?
Zombie check on Transaction - Error
SqlTransaction has completed
http://forums.asp.net/t/1579684.aspx/1
"This SqlTransaction has completed; it is no longer usable."... configuration error?
dotnet.sys-con.com - SqlClient Connection Pooling Exposed
Thread abort leaves zombie transactions and broken SqlConnection
You should leave some of the work to compiler, to wrap that in a try/catch/finally for you.
Also, you should expect that Rollback can occasionally throw an exception, if a problem occurs in Commit stage, or if a connection to server breaks. For that reason you should wrap it in a try/catch.
try
{
transaction.Rollback();
}
catch (Exception ex2)
{
// This catch block will handle any errors that may have occurred
// on the server that would cause the rollback to fail, such as
// a closed connection.
Console.WriteLine("Rollback Exception Type: {0}", ex2.GetType());
Console.WriteLine(" Message: {0}", ex2.Message);
}
This is copied exactly from MSDN documentation page for Rollback method.
I see that you're worried that you have a zombie transaction. In case you pasted, it doesn't sound like you have a problem. You're transaction has been completed, and you should no longer have anything to do with it. Remove references to it if you hold them, and forget about it.
From MSDN - SqlTransaction.Rollback Method
A Rollback generates an InvalidOperationException if the connection is terminated or if the transaction has already been rolled back on the server.
Rethrow a new exception to tell user that data may not have been saved, and ask her to refresh and review
Note: This error came only once.
then it is very hard to say much; it could be simply that the // Other Code etc simply took to long, and the entire thing got killed. Maybe your connection died, or an admin deliberately killed it because you were blocking.
What is the mistake in the code?
over-complicating it; it can be much simpler:
using (var connection = new SqlConnection(connectionString))
{
connection.Open();
using(var transaction = connection.BeginTransaction())
{
try
{
sessionID = GetSessionIDForAssociate(connection, empID, transaction);
//Other Code
transaction.Commit();
}
catch
{
transaction.Rollback();
throw;
}
}
}
much less code to get wrong.
I use code below can reproduce this error, I use 1000 tasks to execute Sql, after about 300 tasks Successfully Completed, lots of exception about timeout error start to occur on ExecuteNonQuery(),
then next error This SqlTransaction has completed will occur on transaction.RollBack(); and its call stack also contains ZombieCheck().
(If single program with 1000 tasks pressure not enough, you can execute multiple compiled exe file at the same time, or even use multi computers execute to one DataBase.)
So I guess one of the reason cause this error can be something wrong in Connection, then cause the transaction error happens as well.
Task[] tasks = new Task[1000];
for (int i = 0; i < 1000; i++)
{
int j = i;
tasks[i] = new Task(() =>
ExecuteSqlTransaction("YourConnectionString", j)
);
}
foreach (Task task in tasks)
{
task.Start();
}
/////////////
public void ExecuteSqlTransaction(string connectionString, int exeSqlCou)
{
using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open();
SqlCommand command = connection.CreateCommand();
SqlTransaction transaction;
// Start a local transaction.
transaction = connection.BeginTransaction();
// Must assign both transaction object and connection
// to Command object for a pending local transaction
command.Connection = connection;
command.Transaction = transaction;
try
{
command.CommandText =
"select * from Employee";
command.ExecuteNonQuery();
// Attempt to commit the transaction.
transaction.Commit();
Console.WriteLine("Execute Sql to database."
+ exeSqlCou);
}
catch (Exception ex)
{
Console.WriteLine("Commit Exception Type: {0}", ex.GetType());
Console.WriteLine(" Message: {0}", ex.Message);
// Attempt to roll back the transaction.
try
{
transaction.Rollback();
}
catch (Exception ex2)
{
// This catch block will handle any errors that may have occurred
// on the server that would cause the rollback to fail, such as
// a closed connection.
Console.WriteLine("Rollback Exception Type: {0}", ex2.GetType());
Console.WriteLine(" Message: {0}", ex2.Message);
}
}
}
}
Besides I find if I commit twice sequentailly will invoke this exception as well.
transaction.Commit();
transaction.Commit();
Or if Connection Closed before commit also invoke this error.
connection.Close();
transaction.Commit();
Update:
I find it strange that I create another new table and insert 500 thousand data to it,
then use 100000 tasks with select * from newtable sql, running 5 programs at the same time, this time the Timeout Error occur, but when transaction.Rollback() it didn't invoke the SQLTransaction has completed error.
but if the Timeout Error occur, jump into the catch block, and in the catch block do transaction.Commit() again, the SQLTransaction has completed error will happen.
I have experienced this error once and i was stuck and unable to know what is going wrong. Actually i was deleting a record and in the Stored procedure i was not deleting its child and specially the delete statement in Stored Procedure was inside the Transaction boundary. I removed that transaction code from stored procedure and got rid of getting this Error of “This SqlTransaction has completed; it is no longer usable.”
This message is simply because that you wrote code that throws an exception after the transaction has been already committed successfully.Please try to check the code you wrote after the Commit method or you can handle it by using Try..Catch and finally Blocks :) .

Entity Framework: How to put multiple stored procedures in a transaction?

I did a lot search already but couldn't find a straight anwser.
I have two stored procedures and they both were function imported to the DBContext object
InsertA()
InsertB()
I want to put them in a transaction. (i.e. if InsertB() failed, rolled back InsertA())
How do I do that? Can I just declare a TransactionScope object and wrap around the two stored procedures?
Thanks
You need to enlist your operations in a transaction scope, as follows:
using(TransactionScope tranScope = new TransactionScope())
{
InsertA();
InsertB();
tranScope.Complete();
}
On error, the transaction scope will automatically be rolled back. Of course, you still need to handle exceptions and do whatever your exception handling design dictates (log, etc). But unless you manually call Complete(), the transaction is rolled back when the using scope ends.
The transaction scope will not be promoted to a distributed transaction unless you open other database connections in the same transaction scope (see here).
This is important to know because otherwise you would need to configure MSDTC on all your servers involved in this operation (web, middle tier eventually, sql server). So, as long as the transaction isn't promoted to a distributed one, you'll be fine.
Note:
In order to fine-tune your transaction options, such as timeouts and isolation levels, have a look at this TransactionScope constructor. Default isolation level is serializable.
Additional sample: here.
You can use the TransactionScope object or you can use the SqlConnection.BeginTransaction Method. Be careful using TransactionScope, transactions can be esculated to distributed transactions when calling stored procedures in a different database. Distributed
transactions can be resource intensive.
How to use sqlConnection.BeginTransaction...(http://msdn.microsoft.com/en-us/library/86773566.aspx)
private static void ExecuteSqlTransaction(string connectionString)
{
using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open();
SqlCommand command = connection.CreateCommand();
SqlTransaction transaction;
// Start a local transaction.
transaction = connection.BeginTransaction("SampleTransaction");
// Must assign both transaction object and connection
// to Command object for a pending local transaction
command.Connection = connection;
command.Transaction = transaction;
try
{
command.CommandText =
"Insert into Region (RegionID, RegionDescription) VALUES (100, 'Description')";
command.ExecuteNonQuery();
command.CommandText =
"Insert into Region (RegionID, RegionDescription) VALUES (101, 'Description')";
command.ExecuteNonQuery();
// Attempt to commit the transaction.
transaction.Commit();
Console.WriteLine("Both records are written to database.");
}
catch (Exception ex)
{
Console.WriteLine("Commit Exception Type: {0}", ex.GetType());
Console.WriteLine(" Message: {0}", ex.Message);
// Attempt to roll back the transaction.
try
{
transaction.Rollback();
}
catch (Exception ex2)
{
// This catch block will handle any errors that may have occurred
// on the server that would cause the rollback to fail, such as
// a closed connection.
Console.WriteLine("Rollback Exception Type: {0}", ex2.GetType());
Console.WriteLine(" Message: {0}", ex2.Message);
}
}
}
}
How to use TransactionScope...(http://msdn.microsoft.com/en-us/library/system.transactions.transactionscope.aspx)
// This function takes arguments for 2 connection strings and commands to create a transaction
// involving two SQL Servers. It returns a value > 0 if the transaction is committed, 0 if the
// transaction is rolled back. To test this code, you can connect to two different databases
// on the same server by altering the connection string, or to another 3rd party RDBMS by
// altering the code in the connection2 code block.
static public int CreateTransactionScope(
string connectString1, string connectString2,
string commandText1, string commandText2)
{
// Initialize the return value to zero and create a StringWriter to display results.
int returnValue = 0;
System.IO.StringWriter writer = new System.IO.StringWriter();
try
{
// Create the TransactionScope to execute the commands, guaranteeing
// that both commands can commit or roll back as a single unit of work.
using (TransactionScope scope = new TransactionScope())
{
using (SqlConnection connection1 = new SqlConnection(connectString1))
{
// Opening the connection automatically enlists it in the
// TransactionScope as a lightweight transaction.
connection1.Open();
// Create the SqlCommand object and execute the first command.
SqlCommand command1 = new SqlCommand(commandText1, connection1);
returnValue = command1.ExecuteNonQuery();
writer.WriteLine("Rows to be affected by command1: {0}", returnValue);
// If you get here, this means that command1 succeeded. By nesting
// the using block for connection2 inside that of connection1, you
// conserve server and network resources as connection2 is opened
// only when there is a chance that the transaction can commit.
using (SqlConnection connection2 = new SqlConnection(connectString2))
{
// The transaction is escalated to a full distributed
// transaction when connection2 is opened.
connection2.Open();
// Execute the second command in the second database.
returnValue = 0;
SqlCommand command2 = new SqlCommand(commandText2, connection2);
returnValue = command2.ExecuteNonQuery();
writer.WriteLine("Rows to be affected by command2: {0}", returnValue);
}
}
// The Complete method commits the transaction. If an exception has been thrown,
// Complete is not called and the transaction is rolled back.
scope.Complete();
}
}
catch (TransactionAbortedException ex)
{
writer.WriteLine("TransactionAbortedException Message: {0}", ex.Message);
}
catch (ApplicationException ex)
{
writer.WriteLine("ApplicationException Message: {0}", ex.Message);
}
// Display messages.
Console.WriteLine(writer.ToString());
return returnValue;
}

Categories