.NET MySqlCommand.Transaction - what is its purpose? - c#

When using the ADO.Net driver for MySQL in .NET, is it necessary to assign a transaction to the MySqlCommand object (for example, using oCmd.Transaction = oTran) or is it enough that the MySqlConnection object has an open transaction? In the code below I begin a transaction on the connection, run two queries with separate MySqlCommand objects without assigning a transaction and then roll back the transaction. In this example, neither UPDATE is committed to the database.
MySqlConnection oConn = new MySqlConnection("Server=spet-il-cent-02;Port=3306;Database=test;Uid=test;Pwd=test123;CharSet=utf8;");
oConn.Open();
MySqlTransaction oTran = oConn.BeginTransaction();
MySqlCommand oCmd = oConn.CreateCommand();
oCmd.CommandText = "UPDATE testing SET testBalance = testBalance + 10 WHERE testID = 1";
oCmd.ExecuteNonQuery();
oCmd = oConn.CreateCommand();
oCmd.CommandText = "UPDATE testing SET testBalance = testBalance - 10 WHERE testID = 2";
oCmd.ExecuteNonQuery();
oTran.Rollback();
oConn.Close();
When checking the Transaction property of oCmd at runtime, I see that it is null.
Obviously, if I call oTran.Commit() then both UPDATE statements are committed. So what is the purpose of the Transaction property of the MySqlCommand object? Is it to allow more than one concurrent transaction on a single connection (where different commands would be bound to different transactions and could be rolled back or committed irrespective of each other)?

For a single statement, you can pretend the property doesn't exist, along with the Commit() and Rollback() methods. If the property is not there, individual statements are auto-committed. In the code from the question, it would be possible to run a query in the brief span between when testID=1 is updated and testID=2 is updated, and the Rollback() method won't accomplish anything.
To take advantage of the MySqlTransaction object, you need to do this:
using (var Conn = new MySqlConnection("Server=spet-il-cent-02;Port=3306;Database=test;Uid=test;Pwd=test123;CharSet=utf8;"))
{
Conn.Open();
MySqlTransation Tran = Conn.BeginTransaction();
using (var Cmd = new MySqlCommand("UPDATE testing SET testBalance = testBalance + 10 WHERE testID = 1", Conn))
{
Cmd.Transaction = Tran;
Cmd.ExecuteNonQuery();
}
using (var Cmd = new MySqlCommand("UPDATE testing SET testBalance = testBalance + 10 WHERE testID = 2", Conn))
{
Cmd.Transaction = Tran;
Cmd.ExecuteNonQuery();
}
Tran.Rollback();
}
Or even better:
using (var Conn = new MySqlConnection("Server=spet-il-cent-02;Port=3306;Database=test;Uid=test;Pwd=test123;CharSet=utf8;"))
using (var Cmd = new MySqlCommand("UPDATE testing SET testBalance = testBalance + 10 WHERE testID = #testID", Conn))
{
Conn.Open();
Cmd.Transaction = Conn.BeginTransaction();
Cmd.Parameteres.Add("#testID", MySqlDbType.Int32).Value = 1;
Cmd.ExecuteNonQuery();
Cmd.Parameters["testID"].Value = 2; //I can't remember at the moment if you need the "#" here or not
Cmd.ExecuteNonQuery();
Cmd.Transaction.Rollback();
}
You need transactions mainly when you want to guarantee multiple operations are atomic. This works when you assign the transaction to each instance of the command object individually, each instance of the command uses the same connection, and the connection is held open for the duration of the transaction.
Additionally, you can put multiple statements into a single command object:
string sql =
"BEGIN;" +
"UPDATE testing SET testBalance = testBalance + 10 WHERE testID = 1;" +
"UPDATE testing SET testBalance = testBalance - 10 WHERE testID = 2;" +
"COMMIT;";
using (var Conn = new MySqlConnection("Server=spet-il-cent-02;Port=3306;Database=test;Uid=test;Pwd=test123;CharSet=utf8;"))
using (var Cmd = new MySqlCommand(sql, Conn))
{
Conn.Open();
Cmd.ExecuteNonQuery();
}
This is the preferred method, but sometimes the nature of your code will prevent it, and you'll have to use the MySqlTransaction object instead.

Related

How to actually execute a command?

I'm playing around making a POC and I've created the following call.
public string DoStuff()
{
try
{
using (SqlDataAdapter adapter = new SqlDataAdapter())
{
SqlConnection connection = new SqlConnection("Server...");
string command = "insert into Records values (...)";
adapter.InsertCommand = new SqlCommand(command, connection);
}
}
catch (Exception exception)
{
return exception.Message + " " + exception.InnerException;
}
return "WeeHee!";
}
The text I'm seeing returned is the happy one, so I conclude there's no exceptions. Hence, I conclude that the call to the DB is performed as supposed to. However, there's no new lines in the DB being created.
I'm using the same connection string as I have in my config file and the command in pasted in from SQL Manager, where it works.
So my suspicion was that although I create an insert command, I never actually execute it but according to MSDN that's how it's supposed to work.
What stupid thing do I miss here?
You are missing connection.Open(); and adapter.InsertCommand.ExecuteNonQuery();
using (SqlDataAdapter adapter = new SqlDataAdapter())
{
SqlConnection connection = new SqlConnection("Server...");
connection.Open();
string command = "insert into Records values (...)";
adapter.InsertCommand = new SqlCommand(command, connection);
adapter.InsertCommand.ExecuteNonQuery();
}
You should use ExecuteNonQuery instead. Using an SqlDataAdapter for an INSERT query does not make sense.
Also you should Open your connection just before you execute it.
You can:
using(SqlConnection connection = new SqlConnection("Server..."))
{
SqlCommand command = connection.CreateCommand();
command.CommandText = "insert into Records values (...)";
connection.Open();
int craeted = command.ExecuteNonQuery();
}
The example you linked to returned a SQLAdapter for later use.
You don't need one at all:
using (SqlConnection connection = new SqlConnection("Server..."))
{
string command = "insert into Records values (...)";
connection.Open();
var command = new SqlCommand(command, connection);
command.ExecuteNonQuery();
}
Note that there are other execution methods, depending on expected return values and whether you want asynchronous operation: https://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlcommand(v=vs.110).aspx

how to solve error of " ExecuteNonQuery: Connection property has not been initialized?"

con = new SqlConnection (cs);
SqlCommand UpdateCommand = new SqlCommand("Update Stock Set ConfigID = #ConfigID , Quantity = #Quantity ,TotalPrice =#TotalPrice, StockDate =#StockDate ,Where StockID='" +txtStockID.Text+"'");
UpdateCommand.Parameters.Add("#ConfigID",SqlDbType.Int).Value= txtConfigID.Text;
UpdateCommand.Parameters.Add("#Quantity", SqlDbType.Int).Value = txtQty.Text;
UpdateCommand.Parameters.Add("#TotalPrice", SqlDbType.Int).Value = txtTotalPrice.Text;
UpdateCommand.Parameters.Add("#StockDate", SqlDbType.NVarChar, 50).Value = dtpStockDate.Value;
con.Open();
UpdateCommand.ExecuteNonQuery();
con.Close();
I find this error-message understandable, isn't it? However, you have to assign the connection to the SqlCommand, either by using the constructor or the property Connection:
string updateSQL = #"UPDATE Stock Set ConfigID = #ConfigID,
Quantity = #Quantity,
TotalPrice = #TotalPrice,
StockDate = #StockDate
WHERE StockID = #StockID";
SqlCommand UpdateCommand = new SqlCommand(updateSQL, con);
or
SqlCommand UpdateCommand = new SqlCommand(updateSQL);
UpdateCommand.Connection = con;
Note that i've added a parameter for the StockID in the Where and removed the last comma before the Where.
Note also that you should close connections when you're finished, therefore you can use the using-statement which ensures that it gets disposed/closed even on error:
using(var con = new SqlConnection (cs))
{
// ...
}
how to solve error of “ ExecuteNonQuery: Connection property has not been initialized?”
By initializing the Connection property
UpdateCommand.Connection = con;
UpdateCommand.ExecuteNonQuery();
Also you should wrap your connection object with a using statement to make sure it is properly disposed.
You can also use the connection to create the command:
var command = connection.CreateCommand();
This ensures you have a command initialised with the correct connection. You can set the query string using the property command.CommandText:
command.CommandText = #"SELECT foo FROM foo";

using the same instance of SQLCommand more than one time in the same code for more than one query?

I have question about using why i can not use the same instance of SQLCommand more than one time in the same code?
I tried the code down here and it runs good for the gridview but when i changed the query by using cmd.CommandText() method it keeps saying:
There is already an open DataReader associated with this Command which must be closed first.
This is the code:
string cs = ConfigurationManager.ConnectionStrings["MyDB"].ConnectionString;
SqlConnection con = new SqlConnection(cs);
try
{
SqlCommand cmd = new SqlCommand();
cmd.Connection = con;
con.Open();
cmd.CommandText = "Select top 10 FirstName, LastName, Address, City, State from Customers";
GridView1.DataSource = cmd.ExecuteReader();
GridView1.DataBind();
cmd.CommandText = "SELECT TOP 10 COUNT(CreditLimit) FROM Customers";
int total = (int)cmd.ExecuteScalar();
TotalCreditLble.Text = "The total Credit :" + total.ToString();
}
catch(Exception exp)
{
Response.Write(exp.Message);
}
finally
{
con.Close();
}
The problem is that you are using the SqlCommand object to generate a DataReader via the command.ExecuteReader() command. While that is open, you can't re-use the command.
This should work:
using (var reader = cmd.ExecuteReader())
{
GridView1.DataSource = reader;
GridView1.DataBind();
}
//now the DataReader is closed/disposed and can re-use command
cmd.CommandText = "SELECT TOP 10 COUNT(CreditLimit) FROM Customers";
int total = (int)cmd.ExecuteScalar();
TotalCreditLble.Text = "The total Credit :" + total.ToString();
There is already an open DataReader associated with this Command which must be closed first.
This is the very reason you don't share a command. Somewhere in your code you did this:
cmd.ExecuteReader();
but you didn't leverage the using statement around the command because you wanted to share it. You can't do that. See, ExecuteReader leaves a connection to the server open while you read one row at a time; however that command is locked now because it's stateful at this point. The proper approach, always, is this:
using (SqlConnection c = new SqlConnection(cString))
{
using (SqlCommand cmd = new SqlCommand(sql, c))
{
// inside of here you can use ExecuteReader
using (SqlDataReader rdr = cmd.ExecuteReader())
{
// use the reader
}
}
}
These are unmanaged resources and need to be handled with care. That's why wrapping them with the using is imperative.
Do not share these objects. Build them, open them, use them, and dispose them.
By leveraging the using you will never have to worry about getting these objects closed and disposed.
Your code, written a little differently:
var cs = ConfigurationManager.ConnectionStrings["MyDB"].ConnectionString;
var gridSql = "Select top 10 FirstName, LastName, Address, City, State from Customers";
var cntSql = "SELECT TOP 10 COUNT(CreditLimit) FROM Customers";
using (SqlConnection con = new SqlConnection(cs))
{
con.Open();
try
{
using (SqlCommand cmd = new SqlCommand(gridSql, con))
{
GridView1.DataSource = cmd.ExecuteReader();
GridView1.DataBind();
}
using (SqlCommand cmd = new SqlCommand(cntSql, con))
{
int total = (int)cmd.ExecuteScalar();
TotalCreditLble.Text = "The total Credit :" + total.ToString();
}
}
catch(Exception exp)
{
Response.Write(exp.Message);
}
}
Thank u quys but for the guys who where talking about using block !
why this code work fine which i seen it on example on a video ! It's the same thing using the same instance of SqlCommand and passing diffrent queries by using the method CommanText with the same instance of SqlCommand and it's execute just fine , this is the code :
using (SqlConnection con = new SqlConnection(cs))
{
SqlCommand cmd = new SqlCommand();
cmd.Connection = con;
con.Open();
cmd.CommandText = "Delete from tbleProduct where ProductID= 4";
int TotalRowsAffected = cmd.ExecuteNonQuery();
Response.Write("Total rows affected :" + TotalRowsAffected );
cmd.CommandText = "Insert into tbleProduct values (4, 'Calculator', 100, 230)";
TotalRowsAffected = cmd.ExecuteNonQuery();
Response.Write("Total rows affected :" + TotalRowsAffected );
cmd.CommandText = "ypdate tbleProduct set QtyAvailbe = 234 where ProductID = 2";
TotalRowsAffected = cmd.ExecuteNonQuery();
Response.Write("Total rows affected :" + TotalRowsAffected );
}

Trying to make two SQL INSERT statements atomic in C# code

Below is the code I'm using in an SSIS script task. I am trying to make both inserts atomic as they deal with similar customers.
The first .executeNonQuery() works fine, locking the SQL table as it should.
The second .executNonQuery() throws an error:
ExecuteNonQuery 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.
Code:
ConnectionManager cm;
SqlTransaction sqlTrans;
SqlConnection sqlConn;
SqlCommand sqlComm;
cm = Dts.Connections["connectionManager"];
try
{
//Set 'global' variables
SqlParameter agentID = new SqlParameter("#agentID", 1000018); //retrievedMessage.Substring(2, 10));//Primary key
SqlParameter lastChangeOperator = new SqlParameter("#lastChangeOperator", "LVO");
SqlParameter lastChangeDate = new SqlParameter("#lastChangeDate", DateTime.Now);
SqlParameter controlId = new SqlParameter("#controlID", 1); //Hard-coded value for testing - CHANGE LATER
//Set variables for Agent table
SqlParameter entityType = new SqlParameter("#entityType", "P");//retrievedMessage.Substring(162, 1));
SqlParameter fName = new SqlParameter("#fName", "test");//retrievedMessage.Substring(12, 25));
SqlParameter lName = new SqlParameter("#lName", "test");//retrievedMessage.Substring(37, 35));
SqlParameter suffix = new SqlParameter("#suffix", "test");//retrievedMessage.Substring(72, 10));
SqlParameter corporateName = new SqlParameter("#corporateName", "Initech");//retrievedMessage.Substring(82, 80));
//Insert record into Agent table
sqlConn = (SqlConnection)cm.AcquireConnection(Dts.Transaction);
sqlComm = new SqlCommand
(
"SET IDENTITY_INSERT Agent ON " +
"INSERT INTO Agent (UniqueAgentId, EntityType, FirstName, LastName, NameSuffix, CorporateName, LastChangeOperator, LastChangeDate, ControlId) " +
"VALUES (#agentID, #entityType, #fName, #lName, #suffix, #corporateName, #lastChangeOperator, #lastChangeDate, #controlID)" +
"SET IDENTITY_INSERT Agent OFF",
sqlConn//, sqlTrans
);
sqlTrans = sqlConn.BeginTransaction("SqlAgentTableUpdates");
sqlComm.Parameters.Add(agentID);
sqlComm.Parameters.Add(lastChangeOperator);
sqlComm.Parameters.Add(lastChangeDate);
sqlComm.Parameters.Add(controlId);
sqlComm.Parameters.Add(entityType);
sqlComm.Parameters.Add(fName);
sqlComm.Parameters.Add(lName);
sqlComm.Parameters.Add(suffix);
sqlComm.Parameters.Add(corporateName);
sqlComm.Transaction = sqlTrans;
sqlComm.ExecuteNonQuery();
//Set variables for AgentIdentification table
SqlParameter taxIdType = new SqlParameter("taxIdType", "S");//Hard-coded value for testing - CHANGE LATER
SqlParameter agentTaxId = new SqlParameter("#agentTaxId", "999999999");//Hard-coded value for testing - CHANGE LATER
//Insert record into AgentIdentification table
sqlConn = (SqlConnection)cm.AcquireConnection(Dts.Transaction);
sqlComm = new SqlCommand
(
"INSERT INTO AgentIdentification (UniqueAgentId, TaxIdType, AgentTaxId, LastChangeOperator, LastChangeDate, ControlId) " +
"VALUES (#agentID, #taxIdType, #agentTaxId, #lastChangeOperator, #lastChangeDate, #controlId)",
sqlConn//, sqlTrans
);
sqlComm.Parameters.Add(taxIdType);
sqlComm.Parameters.Add(agentTaxId);
sqlComm.Transaction = sqlTrans;
sqlComm.ExecuteNonQuery();
}
catch (Exception)
{
sqlTrans.Rollback();
cm.ReleaseConnection(sqlConn);
}
finally
{
sqlTrans.Commit();
cm.ReleaseConnection(sqlConn);
}
EDIT
I was able to make this transaction work by eliminating the second connection. However, both queries use a couple of the same variables (SqlParameters). I was forced to duplicate these in order for this to run without errors. Is there a way for them to share the variables so I do not have to waste space re-creating them?
I think the problem might be with the connection, or when you set the command to a new command for the second insert, you can use two different commands with the same connection or reuse one command just changing the CommandText property.
Hope this helps... Using SqlTransaction
A transaction cannot span multiple connections... does cm.AcquireConnection return a new connection each time? If so, try using the same connection for both commands.
use transactionscope
using(TransactionScope ts = new TransactionScope())
{
using(SqlConnection conn = new SqlConnection(myconnstring)
{
conn.Open();
//call first executenonquery
//call second executenonquery
ts.Complete();
conn.Close();
}
}

ASP.net why are these queries not executing?

In my code neither of these queries appear to be running. The debug label is printing as "end" so it is executing something inside that code block, just appears it doesn't like the queries?
// Check input is all valid
if (Page.IsValid)
{
debug.Text = "begin";
using (SqlConnection cn = new SqlConnection(
ConfigurationManager.ConnectionStrings["LocalSqlServer"].ToString()))
{
// Verify that username is unique
using (SqlCommand cmd = new SqlCommand(
"UPDATE tblSiteSettings SET isActive = 0", cn))
{
cn.Open();
cn.Close();
}
using (SqlCommand cmd = new SqlCommand(
"INSERT INTO tblSiteSettings (allowProductRatings, allowComments, " +
"siteName, settingDate, isActive) VALUES (#allowRatings, " +
"#allowcomments, #siteName, getDate(), 1)", cn))
{
cmd.Parameters.Add("#allowRatings", SqlDbType.Bit).Value = 1;
cmd.Parameters.Add("#allowcomments", SqlDbType.Bit).Value = 1;
cmd.Parameters.Add("#siteName", SqlDbType.VarChar, 128).Value = "lol";
cn.Open();
cn.Close();
}
debug.Text = "end";
}
}
A few questions:
Why are they not executing?
In classic ASP for inserts, updates and deletes I would use con.Execute(query) as supposed to using a recordset, am I running my update statement correctly here?
Is my design of the queries good, or should I be executing them in a different manner?
The reason it's not doing anything is because you're not actually executing the queries. What you need to do is:
// Verify that username is unique
using (SqlCommand cmd = new SqlCommand("UPDATE tblSiteSettings SET isActive = 0", cn))
{
cn.Open();
cmd.ExecuteNonQuery();
cn.Close();
}
using (SqlCommand cmd = new SqlCommand("INSERT INTO tblSiteSettings (allowProductRatings, allowComments, siteName, settingDate, isActive) VALUES (#allowRatings, #allowcomments, #siteName, getDate(), 1)", cn))
{
cmd.Parameters.Add("#allowRatings", SqlDbType.Bit).Value = 1;
cmd.Parameters.Add("#allowcomments", SqlDbType.Bit).Value = 1;
cmd.Parameters.Add("#siteName", SqlDbType.VarChar, 128).Value = "lol";
cn.Open();
cmd.ExecuteNonQuery();
cn.Close();
}
It's the line cmd.ExecuteNoneQuery(); that you're missing. There are various different Execute methods exposed by the SqlCommand class, the most commonly used are:
ExecuteNonQuery: Executes a query and returns no result from the query (it does return the rows affected as its return value however)
ExecuteScalar: Executes a query and returns the value in the first column of the first row
ExecuteReader: Executes a query and returns the data to a SqlDataReader
Your are missing
cmd.ExecuteScalar();
You may also reuse you SqlConnection, you can open the connection right after the using (SqlConnection cn = new Sql... statement. You don't have to close the connection when the SqlConnection is in a using block, accordning to the documentation the connection is closed when you are leaving the using block.

Categories

Resources