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

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();
}
}

Related

.NET MySqlCommand.Transaction - what is its purpose?

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.

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

Having some trouble using scope identity

I have two tables, one containing names, and one containing rates and other data that is lined to each name. After I insert a new name into table A, I want to get the newly auto generated PK to now use to insert into my other table B with rates.
How can I do this? I read about scope_identity online but I'm not sure how to use it.
This is what I have so far:
SqlConnection con = new SqlConnection(pubvar.x);
SqlCommand command = con.CreateCommand();
command.CommandText ="Insert into A values('" +Name + "')";
SqlCommand command2 = con.CreateCommand();
command2.CommandText = "Insert into B values(....)";
SELECT SCOPE_IDENTITY();
con.Open();
command.ExecuteNonQuery();
con.Close();
Considering the case you've described, I don't see any need to return the identity from the database. You can simply issue both statements in one command:
using (var cnx = new SqlConnection(pubvar.x))
using (var cmd = new SqlCommand
{
Connection = cnx,
CommandText = #"
insert into A (Name) values (#name)
insert into B (A_ID, Rate) values (scope_identity(), #rate)
",
Parameters =
{
new SqlParameter("#name", name),
new SqlParameter("#rate", .5m) //sample rate
}
})
{
cnx.Open();
cmd.ExecuteNonQuery();
}

Insert multiple object to MySQL

Is there an other way to insert multiple objects to an MySQL database than the way shown here. This works but takes time to execute.
using (MySql.Data.MySqlClient.MySqlConnection conn = new MySql.Data.MySqlClient.MySqlConnection(connStr))
{
//Goes thrue the List<object>
foreach(List<object> sub in listSubject)
{
MySql.Data.MySqlClient.MySqlCommand cmd = new MySql.Data.MySqlClient.MySqlCommand();
cmd.Connection = conn;
cmd.CommandText = "CALL stp_InsertSubject(#param_SubjectId, #param_ProjectId, #param_Used);";
cmd.Parameters.AddWithValue("#param_SubjectId",Convert.ToInt32(sub[0]) );
cmd.Parameters.AddWithValue("#param_ProjectId", Convert.ToInt32(sub[1]));
cmd.Parameters.AddWithValue("#param_Used", Convert.ToBoolean(sub[2]) );
conn.Open();
cmd.ExecuteNonQuery();
conn.Close();
}
My Stored procedure:
CREATE DEFINER=`mortenstarck`#`%` PROCEDURE `stp_InsertSubject`(param_SubjectId int(45), param_ProjectId int(45), param_Used tinyint(1))
BEGIN
INSERT INTO Subject_has_Projects(Subject_Id, Projects_Id, Used) VALUES (param_SubjectId, param_ProjectId, param_Used);
END
Few things to improve:
Open the connection just one time outside the loop (no need to close
with using)
Create the command, assign connection just one time before the loop
Create the parameters all before the loop with dummy values
Assign only the value inside the loop and call the ExecuteScalar()
using(MySql.Data.MySqlClient.MySqlConnection conn = new MySql.Data.MySqlClient.MySqlConnection(connStr))
{
conn.Open();
MySql.Data.MySqlClient.MySqlCommand cmd = new MySql.Data.MySqlClient.MySqlCommand();
cmd.Connection = conn;
cmd.CommandText = "CALL stp_InsertSubject(#param_SubjectId, #param_ProjectId, #param_Used);";
cmd.Parameters.AddWithValue("#param_SubjectId",0 );
cmd.Parameters.AddWithValue("#param_ProjectId", 0);
cmd.Parameters.AddWithValue("#param_Used", false );
foreach(List<object> sub in listSubject)
{
cmd.Parameters["#param_SubjectId"].Value = Convert.ToInt32(sub[0]) ;
cmd.Parameters["#param_ProjectId"].Value = Convert.ToInt32(sub[1]);
cmd.Parameters["#param_Used"].Value = Convert.ToBoolean(sub[2]);
Id = (Int64)cmd.ExecuteScalar();
}
}
You can try. Open connection outside foreach loop. This will save time in opening and closing connection every time in loop. This will improve performance.
Int64 Id = 0;
using (MySql.Data.MySqlClient.MySqlConnection conn = new MySql.Data.MySqlClient.MySqlConnection(connStr))
{
//Goes through the List<object>
conn.Open();
foreach(List<object> sub in listSubject)
{
MySql.Data.MySqlClient.MySqlCommand cmd = new MySql.Data.MySqlClient.MySqlCommand();
cmd.Connection = conn;
cmd.CommandText = "CALL stp_InsertSubject(#param_SubjectId, #param_ProjectId, #param_Used);";
cmd.Parameters.AddWithValue("#param_SubjectId",Convert.ToInt32(sub[0]) );
cmd.Parameters.AddWithValue("#param_ProjectId", Convert.ToInt32(sub[1]));
cmd.Parameters.AddWithValue("#param_Used", Convert.ToBoolean(sub[2]) );
Id = (Int64)cmd.ExecuteScalar();
}
conn.Close();
}
How bad is opening and closing a SQL connection for several times? What is the exact effect?
Have you thought about surrounding these calls with a single transaction?

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