How do i use SqlTransaction across multipe methods - c#

Let's assume we have an Object A which can be deleted an Object B which hold a forendkey from A
If you want to deleted A, you have to delete the forendkey from B first and then you can delete A but if something goes wrong it should be rolled back but i also want to use the delete forendkey from B independent but at the moment i don't know how to achieve this
my current idea :
public void DeleteA(Object a)
{
using (SqlConnection con = new SqlConnection())
{
con.open();
using (SqlTransaction tr = con.BeginTransaction())
{
try
{
DeleteAfromAllB(a, con, tr);
using (SqlCommand cmd = new SqlCommand("STP_A_Delete", con))
{
cmd.Transaction = tr;
// some parameters
// some sort of Execute
// e.g.: cmd.ExecuteNonQuery();
}
tr.Commit();
}
catch (SqlException ex)
{
//ExceptionHandling
}
}
}
}
private void DeleteAfromAllB(Object a, SqlConnection con, SqlTransaction tr)
{
try
{
using (SqlCommand cmd = new SqlCommand("STP_B_Delete_Referenc_To_A", con))
{
cmd.Transaction = tr;
// some parameters
// some sort of Execute
// e.g.: cmd.ExecuteNonQuery();
}
}
catch (SqlException ex)
{
//ExceptionHandling
}
}
public void DeleteAfromAllB(Object a)
{
using (SqlConnection con = new SqlConnection())
{
con.open();
using (SqlTransaction tr = con.BeginTransaction())
{
DeleteAfromAllB(a,con,tr);
tr.Commit();
}
}
}
but like you can see this is pretty ugly

The call
public void DeleteAfromAllB(Object a)
does not need to pass the SqlConnection as you can reference from tr.Connection. So you just need the SqlTransaction as parameter. So for your original question, yes I think passing in the SqlTransaction is the way to go. Personally I prefer this way because you can easily trace the call stack / scope of the transaction (i.e. where the transaction started/finished).
Another alternative is to use a TransactionScope.
E.g.
private void DeleteAfromAllB(Object a)
{
using (var con = new SqlConnection())
{
con.open();
using (var cmd = new SqlCommand("STP_B_Delete_Referenc_To_A", con))
{
// some parameters
// some sort of Execute
// e.g.: cmd.ExecuteNonQuery();
}
}
}
public void DeleteAfromAllB_TopLevel(Object a)
{
using (var scope = new TransactionScope())
{
try
{
DeleteAfromAllB(a);
// 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 (Exception)
{
//ExceptionHandling
}
}
}

Related

Invalid Attempt To Call Read when reader is closed .NET

I currently have a DB library used for database access and I use it across several projects. I currently use the following code to get a recordset.
METHODS
public static IDataReader GetRs(string sql)
{
using (var con = NewSqlConnection())
{
con.Open();
return GetRs(sql, con);
}
}
public static IDataReader GetRs(string sql, SqlConnection dbconn)
{
using (var cmd = new SqlCommand(sql, dbconn))
{
int tries = 1;
while (tries <= 3)
{
try
{
if (dbconn.State == ConnectionState.Closed)
{
dbconn.Open();
}
DataTable myTable = new DataTable();
var reader = cmd.ExecuteReader();
myTable.Load(reader);
return myTable.CreateDataReader();
//return cmd.ExecuteReader();
}
catch (SqlException ex)
{
if (ex.Message.Contains("Timeout expired") || ex.Number == 1205) // Deadlock
{
Thread.Sleep(1000);
if (tries == 3)
{
throw ex;
}
tries += 1;
cmd.CommandTimeout *= 10;
}
else
{
throw ex;
}
}
}
}
throw new Exception("Could not get RecordSet");
}
USAGE
public static void Test()
{
using(var reader = GetRs("SELECT Col FROM TABLE"))
{
while(reader.Read())
{
// do stuff with data here e.g. var value = reader[0];
}
}
}
While this method works, as you can see it loads the entire dataset into memory thus causing issues with scaling.
I tried replacing the following code in the GetRs(string sql, SqlConnection con) method
DataTable myTable = new DataTable();
var reader = cmd.ExecuteReader();
myTable.Load(reader);
return myTable.CreateDataReader();
and tried returning just the return cmd.ExecuteReader();
However an error is thrown on the while (reader.Read()) - Invalid attempt to call read when the reader is closed. I am guessing this is because the SqlConnection property is disposed (and hence closed) after returning the IDataReader.
I'm aware that I can wrap the GetRs method with a new sql connection but this means rewriting a lot of my code, and I was hoping that I would be able to dispose the reader AND the connection with my using(var reader = GetRs()) method.
Is there any way I can still use these methods without loading the whole dataset into memory?
You can inject in your code with a Action<DataTableReader> parameter to you GetRs call.
Try this:
public static void GetRs(string sql, Action<DataTableReader> consumer)
{
using (var con = NewSqlConnection())
{
con.Open();
GetRs(sql, con, consumer);
}
}
public static void GetRs(string sql, SqlConnection dbconn, Action<DataTableReader> consumer)
{
using (var cmd = new SqlCommand(sql, dbconn))
{
if (dbconn.State == ConnectionState.Closed)
{
dbconn.Open();
}
DataTable myTable = new DataTable();
var reader = cmd.ExecuteReader();
myTable.Load(reader);
consumer(myTable.CreateDataReader());
}
}
(I removed your try/catch code for clarity.)
Then you call it like this:
public static void Test()
{
GetRs("SELECT Col FROM TABLE", reader =>
{
while(reader.Read())
{
// do stuff with data here e.g. var value = reader[0];
}
});
}
Your connection object was closed by the following line
using (var con = NewSqlConnection())
{
con.Open();
return GetRs(sql, con);
}//Connection object gets released here
But still, you are returning the reader object which was created by SqlCommand object using the above connection object. Hence the reader object is closed.

How use Transaction with more than functions using c#

How use Transaction with more than functions using c#
for example
i have three function
//first function
save();//------to save data-----
//second function
saveDetailes(); //-----to save detiales ----------
//third function
updateStauts(); //--------to update onother table ---------------
I want to ensure that all of them are implemented or not implemented using TransAction
thanks
after alot of tries and search alot of resorces i was found a solution for my problem
solution by
passing a sqlcommand to all functions and return from every fuction as aboolean value is save done return true
if three function return true
transaction be commit other ways transaction roolback
thanks
If I get it correctly you need to use a common SqlTransaction in multiple methods. This is how I do it. First you have to gather all of your methods into a common one. Then you pass your one SqlConenction and SqlTransaction to all of your methods and return a boolean flag to notify your main method whether your queries were sucessfully or not.
using System.Data.SqlClient;
namespace SqlTransationDemo
{
class Program
{
static void Main(string[] args)
{
//Do your sql logic here
DoSomething();
}
private static bool DoSomething()
{
try
{
using (var connection = new SqlConnection("SqlConnectionString"))
{
connection.Open();
//If not commited, transaction is rolled-back as soon as it is disposed
using (var transaction = connection.BeginTransaction())
{
//Either use a false loop to break or throw an exception. Your choice.
do
{
if (!Foo1(connection, transaction))
break;
if (!Foo2(connection, transaction))
break;
if (!Foo3(connection, transaction))
break;
//Commit
transaction.Commit();
return true;
}
while (false);
}
}
}
catch
{
return false;
}
}
private static bool Foo1(SqlConnection Connection, SqlTransaction Transaction)
{
try
{
using (var command = new SqlCommand())
{
command.Connection = Connection;
command.Transaction = Transaction;
command.CommandText = "Query1";
command.ExecuteNonQuery();
}
return true;
}
catch
{
return false;
}
}
private static bool Foo2(SqlConnection Connection, SqlTransaction Transaction)
{
try
{
using (var command = new SqlCommand())
{
command.Connection = Connection;
command.Transaction = Transaction;
command.CommandText = "Query2";
command.ExecuteNonQuery();
}
return true;
}
catch
{
return false;
}
}
private static bool Foo3(SqlConnection Connection, SqlTransaction Transaction)
{
try
{
using (var command = new SqlCommand())
{
command.Connection = Connection;
command.Transaction = Transaction;
command.CommandText = "Query3";
command.ExecuteNonQuery();
}
return true;
}
catch
{
return false;
}
}
}
}

Re-try to open a SQL Server connection if failed

I have a C# application which has several methods which connect to a SQL Server database in order to execute a query.
Sometimes the connection fails and then the program exits.
A db administrator is looking on the database nevertheless I have to adapt the program in order to retry 2-3 times when a connection fails before to exiting.
I don't really know who doing this "smartly".
My connection code:
using (SqlConnection SqlCon = new SqlConnection(myParam.SqlConnectionString))
{
SqlCon.Open();
string requeteFou = "select XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
using (SqlCommand command = new SqlCommand(requeteFou, SqlCon))
{
using (SqlDataReader reader = command.ExecuteReader())
{
if (reader.HasRows)
{
while (reader.Read())
{
// do job
}
}
}
}
}
Since I use several methods, is there a simply way to overwrite the "connection" or "read" method in order to retry the connection 3 times for example ?
Best regards
I would use Polly for retry logic.
Very basic example retrying 3 times when there is a SqlException (not tested):
static void Main(string[] args)
{
var policy = Policy
.Handle<SqlException>()
.Retry(3);
try
{
policy.Execute(() => DoSomething());
}
catch (SqlException exc)
{
// log exception
}
}
private static void DoSomething()
{
using (SqlConnection conn = new SqlConnection(""))
{
conn.Open();
string requeteFou = "select XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
using (SqlCommand command = new SqlCommand(requeteFou, conn))
{
using (SqlDataReader reader = command.ExecuteReader())
{
if (!reader.HasRows) return;
while (reader.Read())
{
// do job
}
}
}
}
}
private static function()
{
DataTable dt = new DataTable();
string connectionString = "//your connection string";
String strQuery = //"Yourquery";
const int NumberOfRetries = 3;
var retryCount = NumberOfRetries;
var success = false;
while (!success && retryCount > 0)
{
try
{
SqlConnection conn = new SqlConnection(connectionString);
SqlCommand cmd = new SqlCommand();
cmd.CommandType = CommandType.Text;
cmd.CommandText = strQuery;
cmd.Connection = conn;
cmd.CommandTimeout = 180;
conn.Open();
SqlDataReader dr = cmd.ExecuteReader();
dt.Load(dr);
catch (Exception ex)
{
retryCount--;
Thread.Sleep(1000 * 60 * 15);
if (retryCount == 0)
{
//yourexception
}
}
}
}
Maybe wrap your using in a try block. Log a connection error in a catch block if you want. Put whole try{ }catch{ } in a for loop that will loop 3 times. If try block runs to the end of itself, break out of loop.
for(int i = 0; i < 3; i++)
{
try {
using (SqlConnection SqlCon = new SqlConnection(myParam.SqlConnectionString))
{
// your code
}
Thread.Sleep(1000); // wait some time before retry
break; // connection established, quit the loop
}
catch(Exception e) {
// do nothing or log error
}
}
You'd however have to handle differentiating SQL connection exception from other exceptions that you might encounter in your code.

How to use Using statement for multiple instances?

I want to use using for SqlConnection and SqlCommand objects to dispose those. How can I use in this scenario?
for example:
using (sqlConnection = new SqlConnection(IRLConfigurationManager.GetConnectionString("connectionStringIRL")))
{
}
But here I am using connection based on the if condition.
SqlConnection _sqlConnection;
SqlCommand sqlCmd;
DBPersister per = (DBPersister)invoice;
if (per == null)
{
_sqlConnection = new SqlConnection(IRLConfigurationManager.GetConnectionString("connectionStringIRL"));
sqlCmd = new SqlCommand("usp_UpdateDocumentStatusInImages", _sqlConnection);
}
else
{
_sqlConnection = per.GetConnection();
sqlCmd = per.GenerateCommand("usp_UpdateDocumentStatusInImages", _sqlConnection, per);
}
sqlCmd.CommandType = CommandType.StoredProcedure;
//mycode
try
{
if (_sqlConnection.State == ConnectionState.Closed)
_sqlConnection.Open();
sqlCmd.ExecuteNonQuery();
}
catch
{
throw;
}
finally
{
if (per == null)
invoice._sqlConnection.Close();
}
You can nest them, like this:
using (var _sqlConnection = new SqlConnection(...))
{
using (var sqlCmd = new SqlCommand(...))
{
//code
}
}
Use the conditional operator to determine what to assign to each variable:
using(SqlConnection _sqlConnection = per==null?
new SqlConnection(IRLConfigurationManager.GetConnectionString("connectionStringIRL"))
: per.GetConnection())
using(SqlCommand sqlCmd = per==null?
new SqlCommand("usp_UpdateDocumentStatusInImages", _sqlConnection);
: per.GenerateCommand("usp_UpdateDocumentStatusInImages",
_sqlConnection, per))
{
//Code here using command and connection
}
Although I must say, per.GenerateCommand(..., per) looks like an odd function (it's an instance method that also must be passed an instance of the same class - must it always be the same instance?)

Using MySQLConnection in C# does not close properly

I try to write a class to make MySql Connections easier. My problem is, after I open a connection and close it. It is still open in the Database and gets aborted.
I'm using the 'using' statement' of course, but the connection is still open and gets aborted after I exit the program.
Here's what my code looks like:
using (DatabaseManager db = new DatabaseManager())
{
using (MySqlDataReader result = db.DataReader("SELECT * FROM module WHERE Active=1 ORDER BY Sequence ASC"))
{
foreach (MySqlDataReader result in db.DataReader("SELECT * FROM module WHERE Active=1 ORDER BY Sequence ASC"))
{
//Do stuff here
}
}
}
The class Database manager opens the connection and closes it when disposed:
public DatabaseManager()
{
this.connectionString = new MySqlConnectionStringBuilder("Server=localhost;Database=businessplan;Uid=root;");
connect();
}
private bool connect()
{
bool returnValue = true;
connection = new MySqlConnection(connectionString.GetConnectionString(false));
connection.Open();
}
public void Dispose()
{
Dispose(true);
}
public void Dispose(bool disposing)
{
if (disposing)
{
if (connection.State == System.Data.ConnectionState.Open)
{
connection.Close();
connection.Dispose();
}
}
//GC.SuppressFinalize(this);//Updated
}
//Updated
//~DatabaseManager()
//{
// Dispose(false);
//}
So, I checked it in the debugger and the Dispose()-method is called and executes correctly.
What am I missing? Is there something I did wrong or misunderstood?
Just in case, the DataReader()-method (Updated version):
public IEnumerable<IDataReader> DataReader(String query)
{
using (MySqlCommand com = new MySqlCommand())
{
com.Connection = connection;
com.CommandText = query;
using (MySqlDataReader result = com.ExecuteReader(System.Data.CommandBehavior.CloseConnection))
{
while (result.Read())
{
yield return (IDataReader)result;
}
}
}
}
Ok, I tried to use the yield return:
foreach (MySqlDataReader result in db.DataReader("SELECT * FROM module WHERE Active=1 ORDER BY Sequence ASC"))
{
//...
}
And I changed the DataReader-method:
public IEnumerable<IDataReader> DataReader(String query)
{
using (MySqlCommand com = new MySqlCommand())
{
com.Connection = connection;
com.CommandText = query;
using (MySqlDataReader result = com.ExecuteReader())
{
while (result.Read())
{
yield return (IDataReader)result;
}
}
}
}
It works in the way that I can retrieve the data, yet I still have the same problem: The connection isn't closed properly.
Im unsure about mysqlconnection but the sql server counter part uses Connection pooling and does not close when you call close instead it puts it in the connection pool!
Edit: Make sure you dispose the Reader, Command, and Connection object!
Edit: Solved with the ConnectionString Parameter "Pooling=false" or the static methods MySqlConnection.ClearPool(connection) and MySqlConnection.ClearAllPools()
You need to wrap the Command and the DataReader in using statements as well.
According to the mysql docs, the MySQLConnection is not closed when it goes out of scope. Therefore you must not use it inside a using.
Quote...
"If the MySqlConnection goes out of scope, it is not closed. Therefore, you must explicitly close the connection by calling MySqlConnection.Close or MySqlConnection.Dispose."
Have a look at using something like this:
private static IEnumerable<IDataRecord> SqlRetrieve(
string ConnectionString,
string StoredProcName,
Action<SqlCommand> AddParameters)
{
using (var cn = new SqlConnection(ConnectionString))
using (var cmd = new SqlCommand(StoredProcName, cn))
{
cn.Open();
cmd.CommandType = CommandType.StoredProcedure;
if (AddParameters != null)
{
AddParameters(cmd);
}
using (var rdr = cmd.ExecuteReader(CommandBehavior.CloseConnection))
{
while (rdr.Read())
yield return rdr;
}
}
}

Categories