Does transaction scope operate across multiple regions? If I have 2 Processes, one that opens a connection and transaction, performs a insert command then calls another process before the transaction is closed, Will the original transaction take into account if transaction 2 fails? Here is the example code below.
Region 1:
public static void process1()
{
using (SqlConnection conn = new SqlConnection(Connections.conn()))
{
//Open the connection
conn.Open();
try
{
//Create A new Sql transaction.
using (var trans = new System.Transactions.TransactionScope())
{
using (SqlCommand insert = new SqlCommand
{
CommandType = CommandType.Text,
CommandText = sql,
Connection = conn,
CommandTimeout = 300
})
{
insert.ExecuteNonQuery();
}
process2()
trans.complete();
}
}
catch (System.Data.SqlClient.SqlException ex)
{
string msg = "Insert Error:";
msg += ex.Message;
throw new Exception(msg);
}
}
}
Region 2
public static void process2()
{
using (SqlConnection conn = new SqlConnection(Connections.conn()))
{
//Open the connection
conn.Open();
try
{
//Create A new Sql transaction.
using (var trans2 = new System.Transactions.TransactionScope())
{
using (SqlCommand insert = new SqlCommand
{
CommandType = CommandType.Text,
CommandText = sql,
Connection = conn,
CommandTimeout = 300
})
{
insert.ExecuteNonQuery();
}
trans2.complete();
}
}
catch (System.Data.SqlClient.SqlException ex)
{
string msg = "Insert Error:";
msg += ex.Message;
throw new Exception(msg);
}
}
}
In my application process 2 is dependant on process 1's success. If process 1 is successful and process 2 is not then the result of process 1 is useless.
I understand that when the commans execute in the same transaction block they will both fail if one does but as I coded my application to do it accorss different regions I was wondering if the same applied in this situation. I would rather not having to recode two different processes together.
Thanks in advance,
Dan
If this is just two blocks of code within a single C# file, then to make sure either both processes happen or neither happen, you would want to wrap your call to those functions in a transaction scope as well. Like so:
using (var tx = new TransactionScope()) {
process1();
process2();
tx.Complete();
}
This way if either process1 or process2 fails, both will be rolled back.
Related
I'm trying to execute a query to multiple databases by using a TransactionScope.
Here is my code; I want to execute the query on two different SQL Server databases, but in my code, while the query on the first connection executes without a problem, the second connection.Open() gets stuck.
// connList : connection string List
// query : to execute query
public DataSet SendQueryMultiConnection(List<string> connList, string query)
{
using(TransactionScope scope = new TransactionScope())
{
DataSet retDs = new DataSet();
try
{
foreach(string conn in connList)
{
if (string.IsNullOrEmpty(conn))
throw new Exception("No ConnectionData");
using (SqlConnection connection = new SqlConnection(conn))
{
connection.Open();
using (SqlCommand command = new SqlCommand(query, connection))
{
using (SqlDataAdapter adapter = new SqlDataAdapter(command))
{
DataTable dt = new DataTable();
adapter.Fill(dt);
retDs.Tables.Add(dt);
}
}
}
}
scope.Complete();
}
catch (Exception ex)
{
//...
}
return redDs;
}
}
I can't guess a clue, what is the problem in my code?
Thanks for reading.
I am using EF6 to query a backend database. User can customize a temporary table and query the data from the temporary table. I am using
DataTable result = context.Query(queryStatement);
to get the result and it has been working fine.
Now the query is needed among a serious of other sqlcommand and a transaction is needed. So I have
public static DataTable GetData()
{
using (MyDbContext context = new MyDbContext())
using (DbContextTransaction tran = context.Database.BeginTransaction())
{
try
{
int rowAffected = context.Database.ExecuteSqlCommand(
"UPDATE [MyDb].dbo.[TableLocks] SET RefCount = RefCount + 1 WHERE TableName = 'TESTTABLE1'");
if (rowAffected != 1)
throw new Exception("Cannot find 'TestTable1'");
//The following line will raise an exception
DataTable result = context.Query("SELECT TOP 100 * FROM [MyDb].dbo.[TestTable1]");
//This line will work if I change it to
//context.Database.ExecuteSqlCommand("SELECT TOP 100 * FROM [MyDb].dbo.[TestTable1]");
//but I don't know how to get the result out of it.
context.Database.ExecuteSqlCommand(
"UPDATE [MyDb].dbo.[TableLocks] SET RefCount = RefCount - 1 WHERE TableName = 'TestTable1'");
tran.Commit();
return result;
}
catch (Exception ex)
{
tran.Rollback();
throw (ex);
}
}
}
But this throws an exception while executing context.Query
ExecuteReader requires the command to have a transaction when the connection
assigned to the command is in a pending local transaction. The Transaction
property of the command has not been initialized.
And when I read this article: https://learn.microsoft.com/en-us/ef/ef6/saving/transactions
It says:
Entity Framework does not wrap queries in a transaction.
Is it the reason cause this issue?
How can I use context.Query() inside a transaction?
What else I can use?
I tried all other method, none of them work - because the return datatype cannot be predicted before hand.
I just realized that, the Query method is defined in MyDbContext!
public DataTable Query(string sqlQuery)
{
DbProviderFactory dbFactory = DbProviderFactories.GetFactory(Database.Connection);
using (var cmd = dbFactory.CreateCommand())
{
cmd.Connection = Database.Connection;
cmd.CommandType = CommandType.Text;
cmd.CommandText = sqlQuery;
using (DbDataAdapter adapter = dbFactory.CreateDataAdapter())
{
adapter.SelectCommand = cmd;
DataTable dt = new DataTable();
adapter.Fill(dt);
return dt;
}
}
}
May be you are missing this section -
you are free to execute database operations either directly on the
SqlConnection itself, or on the DbContext. All such operations are
executed within one transaction. You take responsibility for
committing or rolling back the transaction and for calling Dispose()
on it, as well as for closing and disposing the database connection
And then this codebase -
using (var conn = new SqlConnection("..."))
{
conn.Open();
using (var sqlTxn =
conn.BeginTransaction(System.Data.IsolationLevel.Snapshot))
{
try
{
var sqlCommand = new SqlCommand();
sqlCommand.Connection = conn;
sqlCommand.Transaction = sqlTxn;
sqlCommand.CommandText =
#"UPDATE Blogs SET Rating = 5" +
" WHERE Name LIKE '%Entity Framework%'";
sqlCommand.ExecuteNonQuery();
using (var context =
new BloggingContext(conn, contextOwnsConnection: false))
{
context.Database.UseTransaction(sqlTxn);
var query = context.Posts.Where(p => p.Blog.Rating >= 5);
foreach (var post in query)
{
post.Title += "[Cool Blog]";
}
context.SaveChanges();
}
sqlTxn.Commit();
}
catch (Exception)
{
sqlTxn.Rollback();
}
}
}
Specially this one -
context.Database.UseTransaction(sqlTxn);
Sorry guys, as mentioned above, I thought the Query method is from EF, but I examined the code and found it is actually coded by another developer, defined in class MyDbContext. Since this class is generated by EF, and I never think somebody have added a method.
It is
public DataTable Query(string sqlQuery)
{
DbProviderFactory dbFactory = DbProviderFactories.GetFactory(Database.Connection);
using (var cmd = dbFactory.CreateCommand())
{
cmd.Connection = Database.Connection;
cmd.CommandType = CommandType.Text;
cmd.CommandText = sqlQuery;
//And I added this line, then problem solved.
if (Database.CurrentTransaction != null)
cmd.Transaction = Database.CurrentTransaction.UnderlyingTransaction;
using (DbDataAdapter adapter = dbFactory.CreateDataAdapter())
{
adapter.SelectCommand = cmd;
DataTable dt = new DataTable();
adapter.Fill(dt);
return dt;
}
}
}
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.
Hi all: I have a program that is running 4 threads that talk to an Oracle database. I have the Oracle connections local to each thread, and I'm employing the USING statement as well as manually closing the recordset and closing the connection. As I understand it, the ORA-01000 error arises when there are more open recordsets than configured cursors on the database. I do not understand why my recordsets are staying open or why I'm getting this error. Here's the code:
static void CheckPaths()
{
int pathcount = paths.Count; //paths is a typed list
Parallel.ForEach(paths, new ParallelOptions { MaxDegreeOfParallelism = 4 }, (p) =>
{
try
{
CheckSinglePathAllHours(p);
}
catch (Exception ex)
{
//there is logging here, this is where the exception hits
}
});
}
static void CheckSinglePathAllHours(Path p)
{
string sqlBase = #"Select * from table ";//this is actually a big SQL statement
using (DBManager localdbm = new DBManager())
{
string sql = sqlBase;
OracleDataReader reader = localdbm.GetData(sql);
while (reader.Read())
{
//process the path, query always returns 24 or less rows
}
reader.Close();
reader = null; //is this even necessary?
localdbm.Close(); //is this necessary in conjunction with the USING statement?
}
}
class DBManager : IDisposable
{
OracleConnection conn;
OracleCommand cmd;
public DBManager()
{
string connStr = "blah blah blah";
conn = new OracleConnection(connStr);
conn.Open();
cmd = conn.CreateCommand();
}
public OracleDataReader GetData(string sql)
{
cmd.CommandText = sql;
cmd.CommandTimeout = 900;
return cmd.ExecuteReader();
}
public void RunSQL(string sql)
{
cmd.CommandText = sql;
cmd.CommandTimeout = 900;
cmd.ExecuteNonQuery();
}
public void Close()
{
conn.Close();
}
public void Dispose()
{
try
{
conn.Close();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
The code will usually run for about a minute or two before the exception. The exception message is two-fold: ORA-00604: error occured at recursive SQL level 1; and ORA-01000: maximum open cursors exceeded. Any ideas what I'm doing wrong?
Changed the code to call .Dispose() on OracleDataReader and OracleConnection as suggested by Paul Abbott. Also increased the number of cursors per session from 50 to 150 on the database.
I have two problems. One is that it is only pulling one row and sending it to ms fax when there are a few hundred to be sent. The other is that it doesn't pull any more after that first and it throws an error. I thought I was closing out my connections. I don't understand what the problem is. I have included the code and error.
Service1.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using System.Timers;
using MySql.Data.MySqlClient;
using FAXCOMLib;
using FAXCOMEXLib;
namespace ProcessFaxes
{
public partial class Service1 : ServiceBase
{
public Service1()
{
InitializeComponent();
}
public static Timer timer = new Timer();
protected override void OnStart(string[] args)
{
timer.Elapsed += new ElapsedEventHandler(Tick);
timer.Interval = 600000; // every 10 minutes
timer.Enabled = true;
// Console.ReadLine();
}
protected override void OnStop()
{
}
public static void Tick(object source, ElapsedEventArgs e)
{
string connString = "Server=localhost;Port=3306;Database=communications;Uid=root;password=pass;";
MySqlConnection conn = new MySqlConnection(connString);
MySqlCommand command = conn.CreateCommand();
MySqlConnection connupdate = new MySqlConnection(connString);
MySqlCommand commandupdate = connupdate.CreateCommand();
command.CommandText = "SELECT * FROM outbox WHERE `faxstat` = 'Y' AND `fax` <> '' AND `faxpro` = 'PENDING'";
//command.CommandText = "UPDATE blah blah";
//conn.Open();
//conn.ExecuteNonQuery();
//conn.Close();
try
{
conn.Open();
connupdate.Open();
}
catch (Exception ex)
{
// Console.WriteLine(Ex.Message);
LogException(ex.ToString());
throw; // or whatever you want to do with it
}
MySqlDataReader reader = command.ExecuteReader();
if (reader.HasRows)
{
while (reader.Read())
{
//Console.WriteLine(reader["filepath"].ToString());
SendFax(reader["id"].ToString(), reader["filepath"].ToString(), #"C:\FAXDOC\" + reader["filepath"].ToString(), reader["account"].ToString(), reader["fax"].ToString(), reader["fax_orig"].ToString());
string id = reader["id"].ToString();
commandupdate.CommandText = "UPDATE outbox SET `faxpro` = 'DONE' WHERE `id` = '" + id + "'";
commandupdate.ExecuteNonQuery();
}
}
conn.Close();
connupdate.Close();
}
public static void SendFax(string DocumentId, string DocumentName, string FileName, string RecipientName, string FaxNumber, string RecipientHomePhone2)
{
if (FaxNumber != "")
{
try
{
FAXCOMLib.FaxServer faxServer = new FAXCOMLib.FaxServerClass();
faxServer.Connect(Environment.MachineName);
FAXCOMLib.FaxDoc faxDoc = (FAXCOMLib.FaxDoc)faxServer.CreateDocument(FileName);
faxDoc.RecipientName = RecipientName;
faxDoc.FaxNumber = FaxNumber;
faxDoc.BillingCode = DocumentId;
faxDoc.DisplayName = DocumentName;
faxDoc.RecipientHomePhone = RecipientHomePhone2;
int Response = faxDoc.Send();
faxServer.Disconnect();
}
catch (Exception Ex) {
// Console.WriteLine(Ex.Message);
LogException(Ex.ToString());
throw; // or whatever you want to do with it
}
}
}
public static void LogException(string ErrorDescription)
{
// The name of our log in the event logs
string Log = "Process Faxes";
// Check to see fi the log for AspNetError exists on the machine
// If not, create it
if ((!(EventLog.SourceExists(Log))))
{
EventLog.CreateEventSource(Log, Log);
}
// Now insert your exception information into the AspNetError event log
EventLog logEntry = new EventLog();
logEntry.Source = Log;
logEntry.WriteEntry(ErrorDescription, EventLogEntryType.Error);
}
}
}
error
Event Type: Error
Event Source: Process Faxes
Event Category: None
Event ID: 0
Date: 3/6/2012
Time: 2:01:06 PM
User: N/A
Computer: FAXSERVER
Description:
MySql.Data.MySqlClient.MySqlException (0x80004005): Too many connections
at MySql.Data.MySqlClient.MySqlStream.ReadPacket()
at MySql.Data.MySqlClient.NativeDriver.Open()
at MySql.Data.MySqlClient.Driver.Open()
at MySql.Data.MySqlClient.Driver.Create(MySqlConnectionStringBuilder settings)
at MySql.Data.MySqlClient.MySqlPool.GetPooledConnection()
at MySql.Data.MySqlClient.MySqlPool.TryToGetDriver()
at MySql.Data.MySqlClient.MySqlPool.GetConnection()
at MySql.Data.MySqlClient.MySqlConnection.Open()
at ProcessFaxes.Service1.Tick(Object source, ElapsedEventArgs e) in C:\Documents and Settings\bruser\My Documents\Visual Studio 2010\Projects\ProcessFaxes\ProcessFaxes\Service1.cs:line 56
I think you should refactor a bit. I explained a little in a comment above, but here's how I would change it (I added some comments for you too):
public static void Tick(object source, ElapsedEventArgs e)
{
// Prevent another Tick from happening if this takes longer than 10 minutes
(source as Timer).Enabled = false;
// It would be better practice to put this in a settings or config file
// so you can change it without having to recompile your application
string connString = "Server=localhost;Port=3306;Database=communications;Uid=root;password=pass;";
// I won't change them here, but since these classes implement IDisposable,
// you should be using a using statement around them:
// using (var conn = new MySqlConnection(connString))
// {
// // use conn
// }
MySqlConnection conn = new MySqlConnection(connString);
MySqlCommand command = conn.CreateCommand();
MySqlCommand updateCommand = conn.CreateCommand();
command.CommandText = "SELECT * FROM outbox WHERE `faxstat` = 'Y' AND `fax` <> '' AND `faxpro` = 'PENDING'";
try
{
conn.Open();
MySqlDataReader reader = command.ExecuteReader();
if (reader.HasRows)
{
while (reader.Read())
{
SendFax(reader["id"].ToString(), reader["filepath"].ToString(), #"C:\FAXDOC\" + reader["filepath"].ToString(), reader["account"].ToString(), reader["fax"].ToString(), reader["fax_orig"].ToString());
string id = reader["id"].ToString();
// I would use a prepared statement with either this query
// or a stored procedure with parameters instead of manually
// building this string (more good practice than worrying about
// SQL injection as it's an internal app
updateCommand.CommandText = "UPDATE outbox SET `faxpro` = 'DONE' WHERE `id` = '" + id + "'";
updateCommand.ExecuteNonQuery();
}
}
}
catch (Exception ex)
{
LogException(ex.ToString());
throw;
}
finally
{
// If you're not going to use using-statements, you might want to explicitly
// call dispose on your disposable objects:
// command.Dispose();
// updateCommand.Dispose();
conn.Close();
// conn.Dispose();
}
// Enable the timer again
(source as Timer).Enabled = true;
}
As to why you're only receiving one row when you're expecting many, I suspect your SQL is at fault.
You should not be using a timer.
A timer fires at regular intervals, and does not care if the previous event has completed or not.
Look at using a Background Worker to send your faxes, having it loop over the queue, and then pause when the queue is empty.
The issue lies in your connection object. You have defined a couple of connection objects. You only need one.
Here are the two in question:
MySqlConnection connupdate = new MySqlConnection(connString);
MySqlConnection conn = new MySqlConnection(connString);
Eliminate the connection to one of them.
Here is one way to fix your code:
string connString = "Server=localhost;Port=3306;Database=communications;Uid=root;password=pass;";
using(MySqlConnection conn = new MySQLConnection(connString))
{
using(MySQlCommand command = conn.CreateCommand())
{
command.CommandText = "SELECT ...";
conn.Open();
using(MySqlDataReader reader = command.ExecuteReader())
{
//process rows...
}
}
}