I have a method inside a main one. I need the child method to be able to roll back if the parent method fails. The two data connections use different servers . Before I added the transaction scopes, they worked well. But when I tie them together, the child method aborts.
Edit: Error message: Network access for distributed transaction Manager(MSDTC) has been disabled. Please enable DTC for network access in the security configuration for MSDTC using Component Service Administrative tool.
public static void LoopStudent()
{
try
{
using(TransactionScope scope = new TransactionScope())
{
String connString = ConfigurationManager.AppSettings["DBConnection"];
using(SqlConnection webConn = new SqlConnection(connString))
{
webConn.Open();
String sql = "select * from students";
using(SqlCommand webComm = new SqlCommand(sql, webConn))
{
using(SqlDataReader webReader = webComm.ExecuteReader())
{
if (webReader.HasRows)
{
while (webReader.Read())
{
int i = GetNextId();
}
}
else
Console.WriteLine("wrong");
}
}
}
scope.Complete();
}
}
catch (Exception ex)
{
Console.WriteLine("Error " + ex.Message);
}
} //End LoopThroughCart
public static int GetNextId(String str)
{
int nextId = 0;
String connString = ConfigurationManager.AppSettings["SecondDBConnection"];
try
{
using(TransactionScope scope = new TransactionScope())
{
using(SqlConnection webConn = new SqlConnection(connString))
{
webConn.Open();
using(SqlCommand webComm = new SqlCommand("GetNextId", webConn))
{
//do things
}
}
scope.Complete();
}
}
catch (TransactionAbortedException ex)
{
Console.WriteLine("TransactionAbortedException Message: {0}", ex.Message);
}
catch (ApplicationException ex)
{
Console.WriteLine("ApplicationException Message: {0}", ex.Message);
}
return nextId;
} //End GetNextId
If you do not use RequireNew in you inner method, the inner method will be automatically rolled back if the parent fails to commit the transaction.
What error are you getting?
Related
I send SQL command to ODBC to be able to insert into another application's database.
Looping in selected forms and its rows, I create command.
"OnTransactionCommand" simply returns an sql string of related row to be sent to ODBC for table 'STOCKTRN'
But the problem is that my integration function sometimes returns the error below:
ERROR [HY000] [TOD][ODBC][GENESIS]VISION: Cannot insert into 'STOCKTRN',No locks available
I am not sure but it is probably about the number of execution. With similar data when I test it with 15-20 records it works nicely. But Working with +50 records it returns error.
Is there any kind of limit or I am missing another part ?
My function:
private void MTransaction(MyGridView gvList)
{
try
{
var m_Integration = ctx.Integration.Take(1).FirstOrDefault();
if (m_Integration != null)
{
DbProviderFactory mFactory= DbProviderFactories.GetFactory(m_Integration.ServerType);
using (DbConnection dbcon = mFactory.CreateConnection())
{
dbcon.ConnectionString = m_Integration.ConnString;
for (int i = 0; i < gvList.RowCount; i++)
{
if (Convert.ToBoolean(gvList.GetRowCellValue(i, "Select")))
{
dbcon.Open();
int m_TransactionId = Convert.ToInt32(gvList.GetRowCellValue(i, "Id"));
MTransaction m_Transaction = ctx.Transaction.Where(c => c.Id == m_TransactionId).FirstOrDefault();
using (DbTransaction dbtr = dbcon.BeginTransaction())
{
try
{
using (DbCommand dbcmd = dbcon.CreateCommand())
{
dbcmd.Transaction = dbtr;
if (m_Transaction != null)
{
int TransactionRowCounter = 1;
foreach (var item in m_Transaction.TransactionRow)
{
dbcmd.CommandText = OnTransactionCommand(m_Transaction, item, TransactionRowCounter);
dbcmd.ExecuteNonQuery();
TransactionRowCounter++;
}
}
dbtr.Commit();
}
}
catch (Exception err)
{
dbtr.Rollback();
dbcon.Close();
XtraMessageBox.Show(err.Message);
continue;
}
}
dbcon.Close();
}
}
}
}
}
catch (Exception err)
{
SuccessMessage = "Error:\n" + err.Message;
}
}
I am throwing a new exception when a database row is not found.
Class that was called:
public ProfileBO retrieveProfileByCode(string profileCode)
{
return retrieveSingleProfile("profile_code", profileCode);
}
private ProfileBO retrieveSingleProfile(string termField, string termValue)
{
ProfileBO profile = new ProfileBO();
//Query string is temporary. Will make this a stored procedure.
string queryString = " SELECT * FROM GamePresenterDB.gp.Profile WHERE " + termField + " = '" + termValue + "'";
using (SqlConnection connection = new SqlConnection(App.getConnectionString()))
{
connection.Open();
SqlCommand command = new SqlCommand(queryString, connection);
SqlDataReader reader = command.ExecuteReader();
if (reader.Read())
{
profile = castDataReadertoProfileBO(reader, profile);
}
else
{
// No record was selected. log it and throw the exception (We'll log it later, for now just write to console.)
Console.WriteLine("No record was selected from the database for method retrieveSingleProfile()");
throw new InvalidOperationException("An exception occured. No data was found while trying to retrienve a single profile.");
}
reader.Close();
}
return profile;
}
However, when I catch the exception in the calling class, 'e' is now null. What am I doing wrong? I believe this works fine in Java, so C# must handle this differently.
Calling class:
private void loadActiveProfile()
{
try
{
ProfileBO profile = profileDAO.retrieveProfileByCode(p.activeProfileCode);
txtActiveProfileName.Text = profile.profile_name;
}
catch (InvalidOperationException e)
{
}
}
Now all the code has been put in the question, you can move the try catch outside of your 'loadActiveProfile' method and place it into 'retrieveSingleProfile'.
private void loadActiveProfile()
{
ProfileBO profile = profileDAO.retrieveProfileByCode(p.activeProfileCode);
txtActiveProfileName.Text = profile.profile_name;
}
removed the try catch^
private ProfileBO retrieveSingleProfile(string termField, string termValue)
{
try {
ProfileBO profile = new ProfileBO();
//Query string is temporary. Will make this a stored procedure.
string queryString = " SELECT * FROM GamePresenterDB.gp.Profile WHERE " + termField + " = '" + termValue + "'";
using (SqlConnection connection = new SqlConnection(App.getConnectionString()))
{
connection.Open();
SqlCommand command = new SqlCommand(queryString, connection);
SqlDataReader reader = command.ExecuteReader();
if (reader.Read())
{
profile = castDataReadertoProfileBO(reader, profile);
}
else
{
// No record was selected. log it and throw the exception (We'll log it later, for now just write to console.)
Console.WriteLine("No record was selected from the database for method retrieveSingleProfile()");
throw new InvalidOperationException("An exception occured. No data was found while trying to retrienve a single profile.");
}
reader.Close();
}
return profile;
}
catch(InvalidOperationException e)
{
}
}
Added try catch in the correct place.
You need to step into the catch block for e to be set to the thrown InvalidOperationException:
catch (System.InvalidOperationException e)
{
int breakPoint = 0; //<- set a breakpoint here.
//Either you reach the breakpoint and have an InvalidOperationException, or you don't reach the breakpoint.
MessageBox.Show(e.Message);
}
Also make sure that the InvalidOperationException you throw is actually a System.InvalidOperationException and not some custom type of yours called "InvalidOperationException".
Like #Clemens said, you need to show all the relevant code.
As a quick test, this works just fine:
class Program
{
static void Main(string[] args)
{
try
{
Console.WriteLine("Throwing error");
ThrowException();
}
catch (InvalidOperationException e)
{
Console.WriteLine(e.Message);
}
Console.ReadKey(true);
}
static void ThrowException()
{
throw new InvalidOperationException("Blah blah blah");
}
}
So this is a little bit code-ceptionlike.
I have a function that is checking the last ID in a table, this function is called within another function. At the end of that function, I have another function that's opening another datareader.
Error:
There is already an open Datareader associated with this connection which must be closed first.
getLastIdfromDB()
public string getLastIdFromDB()
{
int lastIndex;
string lastID ="";
var dbCon = DB_connect.Instance();
if (dbCon.IsConnect())
{
MySqlCommand cmd2 = new MySqlCommand("SELECT ID FROM `competitor`", dbCon.Connection);
try
{
MySqlDataReader reader = cmd2.ExecuteReader();
while (reader.Read())
{
string item = reader2["ID"].ToString();
lastIndex = int.Parse(item);
lastIndex++;
lastID = lastIndex.ToString();
}
}
catch (Exception ex)
{
MessageBox.Show("Error:" + ex.Message);
}
}
return lastID;
}
This function is later-on used in this function:
private void addPlayerBtn_Click(object sender, EventArgs e)
{
ListViewItem lvi = new ListViewItem(getLastIdFromDB());
.........................................^
... HERE
...
... irrelevant code removed
.........................................
var dbCon = DB_connect.Instance();
if (dbCon.IsConnect())
{
MySqlCommand cmd = new MySqlCommand("INSERT INTO `competitor`(`ID`, `Name`, `Age`) VALUES(#idSql,#NameSql,#AgeSql)", dbCon.Connection);
cmd.Parameters.AddWithValue("#idSql", getLastIdFromDB());
cmd.Parameters.AddWithValue("#NameSql", playerName.Text);
cmd.Parameters.AddWithValue("#AgeSql", playerAge.Text);
try
{
cmd.ExecuteNonQuery();
listView1.Items.Clear();
}
catch (Exception ex)
{
MessageBox.Show("Error:" + ex.Message);
dbCon.Connection.Close();
}
finally
{
updateListView();
}
}
}
What would be the best way for me to solve this problem and in the future be sure to close my connections properly?
UPDATE: (per request, included DB_connect)
class DB_connect
{
private DB_connect()
{
}
private string databaseName = "simhopp";
public string DatabaseName
{
get { return databaseName; }
set { databaseName = value; }
}
public string Password { get; set; }
private MySqlConnection connection = null;
public MySqlConnection Connection
{
get { return connection; }
}
private static DB_connect _instance = null;
public static DB_connect Instance()
{
if (_instance == null)
_instance = new DB_connect();
return _instance;
}
public bool IsConnect()
{
bool result = true;
try
{
if (Connection == null)
{
if (String.IsNullOrEmpty(databaseName))
result = false;
string connstring = string.Format("Server=localhost; database={0}; UID=root;", databaseName);
connection = new MySqlConnection(connstring);
connection.Open();
result = true;
}
}
catch (Exception ex)
{
Console.Write("Error: " + ex.Message);
}
return result;
}
public void Close()
{
connection.Close();
}
}
}
You are trying to have multiple open readers on the same connection. This is commonly called "MARS" (multiple active result sets). MySql seems to have no support for it.
You will have to either limit yourself to one open reader at a time, or use more than one connection, so you can have one connection for each reader.
My suggestion would be to throw away that singleton-like thingy and instead use connection pooling and proper using blocks.
As suggested by Pikoh in the comments, using the using clause indeed solved it for me.
Working code-snippet:
getLastIdFromDB
using (MySqlDataReader reader2 = cmd2.ExecuteReader()) {
while (reader2.Read())
{
string item = reader2["ID"].ToString();
lastIndex = int.Parse(item);
lastIndex++;
lastID = lastIndex.ToString();
}
}
Your connection handling here is not good. You need to ditch the DB_connect. No need to maintain a single connection - just open and close the connection each time you need it. Under the covers, ADO.NET will "pool" the connection for you, so that you don't actually have to wait to reconnect.
For any object that implements IDisposable you need to either call .Dispose() on it in a finally block, or wrap it in a using statement. That ensures your resources are properly disposed of. I recommend the using statement, because it helps keep the scope clear.
Your naming conventions should conform to C# standards. Methods that return a boolean should be like IsConnected, not IsConnect. addPlayerBtn_Click should be AddPlayerButton_Click. getLastIdFromDB should be GetlastIdFromDb or getLastIdFromDatabase.
public string GetLastIdFromDatabase()
{
int lastIndex;
string lastID ="";
using (var connection = new MySqlConnection(Configuration.ConnectionString))
using (var command = new MySqlCommand("query", connection))
{
connection.Open();
MySqlDataReader reader = cmd2.ExecuteReader();
while (reader.Read())
{
string item = reader2["ID"].ToString();
lastIndex = int.Parse(item);
lastIndex++;
lastID = lastIndex.ToString();
}
}
return lastID;
}
Note, your query is bad too. I suspect you're using a string data type instead of a number, even though your ID's are number based. You should switch your column to a number data type, then select the max() number. Or use an autoincrementing column or sequence to get the next ID. Reading every single row to determine the next ID and incrementing a counter not good.
I saw so many example while googling regarding Transaction Scope.But i tried to do that,So in my case it throws the exception.Exception shows "Transaction Aborted".. I saw ther's may solution it shows Close the connection.but here i close it in finally block.Can anyone helpuot to solve this issue.
UPDATED
Above connection issue solved.Now ti throws the exception in db.ExecuteNonQuery(cmd); section.Accroding to my knowledge until thrasaction has completed this doen't executed.
MyCode
public bool NewsType(int Id)
{
bool status = false;
DatabaseProviderFactory factory = new DatabaseProviderFactory();
Database db = factory.Create("NewsCon");
con.Open();
using (TransactionScope transactionScope = new TransactionScope())
{
try
{
String query = #"DELETE FROM NewsType WHERE ID =: ID";
cmd = db.GetSqlStringCommand(query);
db.AddInParameter(cmd, "ID", DbType.Int32, Id);
db.ExecuteNonQuery(cmd); //<-- Now it shows from the error here.But i think inside transaction scope this doesn't execute until transaction has completed.
//Second step goes here
status = true;
transactionScope.Complete();
transactionScope.Dispose();
}
catch (Exception ex)
{
throw;
}
finally
{
con.Close();
}
return status;
}
}
I have some tasks (nWorkers = 3):
var taskFactory = new TaskFactory(cancellationTokenSource.Token,
TaskCreationOptions.LongRunning, TaskContinuationOptions.LongRunning,
TaskScheduler.Default);
for (int i = 0; i < nWorkers; i++)
{
var task = taskFactory.StartNew(() => this.WorkerMethod(parserItems,
cancellationTokenSource));
tasks[i] = task;
}
And the following method called by the tasks:
protected override void WorkerMethod(BlockingCollection<ParserItem> parserItems,
CancellationTokenSource cancellationTokenSource)
{
//...log-1...
using (var connection = new OracleConnection(connectionString))
{
OracleTransaction transaction = null;
try
{
cancellationTokenSource.Token.ThrowIfCancellationRequested();
connection.Open();
//...log-2...
transaction = connection.BeginTransaction();
//...log-3...
using (var cmd = connection.CreateCommand())
{
foreach (var parserItem in parserItems.GetConsumingEnumerable(
cancellationTokenSource.Token))
{
cancellationTokenSource.Token.ThrowIfCancellationRequested();
try
{
foreach (var statement in this.ProcessRecord(parserItem))
{
cmd.CommandText = statement;
try
{
cmd.ExecuteNonQuery();
}
catch (OracleException ex)
{
//...log-4...
if (!this.acceptedErrorCodes.Contains(ex.Number))
{
throw;
}
}
}
}
catch (FormatException ex)
{
log.Warn(ex.Message);
}
}
if (!cancellationTokenSource.Token.IsCancellationRequested)
{
transaction.Commit();
}
else
{
throw new Exception("DBComponent has been canceled");
}
}
}
catch (Exception ex)
{
//...log-5...
cancellationTokenSource.Cancel();
if (transaction != null)
{
try
{
transaction.Rollback();
//...log-6...
}
catch (Exception rollbackException)
{
//...log-7...
}
}
throw;
}
finally
{
if (transaction != null)
{
transaction.Dispose();
}
connection.Close();
//...log-8...
}
}
//...log-9...
}
There is a producer of ParserItem objects and these are the consumers. Normally it works fine, there are sometimes that there is an Oracle connection timeout, but in these cases I can see the exception message and everything works as designed.
But sometimes the process get stuck. When it gets stuck, in the log file I can see log-1 message and after that (more or less 15 seconds later) I see log-8 message, but what is driving me nuts is why i cannot see neither the exception message log-5 nor the log-9 message.
Since the cancellationTokenSource.Cancel() method is never called, the producer of items for the bounded collection is stuck until a timeout two hours later.
It is compiled for NET Framework 4 and I'm using Oracle.ManagedDataAccess libraries for the Oracle connection.
Any help would be greatly appreciated.
You should never dispose a transaction or connection when you use using scope. Second, you should rarely rely on exception based programming style. Your code rewritten below:
using (var connection = new OracleConnection(connectionString))
{
using (var transaction = connection.BeginTransaction())
{
connection.Open();
//...log-2...
using (var cmd = connection.CreateCommand())
{
foreach (var parserItem in parserItems.GetConsumingEnumerable(cancellationTokenSource.Token))
{
if (!cancellationTokenSource.IsCancellationRequested)
{
try
{
foreach (var statement in ProcessRecord(parserItem))
{
cmd.CommandText = statement;
try
{
cmd.ExecuteNonQuery();
}
catch (OracleException ex)
{
//...log-4...
if (!acceptedErrorCodes.Contains(ex.ErrorCode))
{
log.Warn(ex.Message);
}
}
}
}
catch (FormatException ex)
{
log.Warn(ex.Message);
}
}
}
if (!cancellationTokenSource.IsCancellationRequested)
{
transaction.Commit();
}
else
{
transaction.Rollback();
throw new Exception("DBComponent has been canceled");
}
}
}
}
//...log-9...
Let me know if this helps.
I can confirm everything you're saying. (program stuck, low CPU usage, oracle connection timeouts, etc.)
One workaround is to use Threads instead of Tasks.
UPDATE: after careful investigation I found out that when you use a high number of Tasks, the ThreadPool worker threads queued by the Oracle driver become slow to start, which ends up causing a (fake) connect timeout.
A couple of solutions for this:
Solution 1: Increase the ThreadPool's minimum number of threads, e.g.:
ThreadPool.SetMinThreads(50, 50); // YMMV
OR
Solution 2: Configure your connection to use pooling and set its minimum size appropriately.
var ocsb = new OracleConnectionStringBuilder();
ocsb.DataSource = ocsb.DataSource;
ocsb.UserID = "myuser";
ocsb.Password = "secret";
ocsb.Pooling = true;
ocsb.MinPoolSize = 20; // YMMV
IMPORTANT: before calling any routine that creates a high number of tasks, open a single connection using that will "warm-up" the pool:
using(var oc = new OracleConnection(ocsb.ToString()))
{
oc.Open();
oc.Close();
}
Note: Oracle indexes the connection pools by the connect string (with the password removed), so if you want to open additional connections you must use always the same exact connect string.