The following code executing a bunch of SQL statements works well...
// SQL Server 2008 R2
SqlConnection connection = null;
var runBatch = false;
try
{
connection = new SqlConnection(connectionString);
connection.Open();
var command = connection.CreateCommand();
// 1st batch
command.CommandText = "BEGIN TRANSACTION";
command.ExecuteNonQuery;
// 2nd batch
command.CommandText = "UPDATE MyTable SET MyColumn = 'foo' WHERE Name = 'bar'";
command.ExecuteNonQuery;
// 3rd batch
command.CommandText = "COMMIT TRANSACTION";
command.ExecuteNonQuery;
}
finally
{
if (connection != null)
{
connection.Close();
}
}
...unless I use a parameter:
// [...]
command.Parameters.AddWithValue("name", "bar");
// 1st batch
command.CommandText = "BEGIN TRANSACTION";
command.ExecuteNonQuery; // <= throws Exception
Exception message:
Transaction count after EXECUTE indicates a mismatching number of BEGIN and COMMIT statements. Previous count = 1, current count = 0
Note: This would work, if I combined all the statements in a single command. But in my app, the original SQL script is a bunch of batches separated by GO which is automatically split into multiple commands (so I have no control over how or what transactions are used):
/* Original SQL */
BEGIN TRANSACTION
GO
UPDATE MyTable
SET MyColumn = 'foo'
WHERE NAME = #name
GO
COMMIT TRANSACTION
GO
I have read about this exception, but nothing really seems to apply to this special scenario.
I have not figured out yet what exact difference introducing a parameter makes here and why it breaks the code. Anyone got a solution for this?
Try this:
// SQL Server 2008 R2
SqlConnection connection = null;
var runBatch = false;
try
{
connection = new SqlConnection(connectionString);
connection.Open();
var command = connection.CreateCommand();
// 1st batch
command.CommandText = "BEGIN TRANSACTION";
command.ExecuteNonQuery();
// 2nd batch
command.CommandText = "UPDATE MyTable SET MyColumn = 'foo' WHERE Name = #name";
command.Parameters.AddWithValue("name", "bar");
command.ExecuteNonQuery();
// 3rd batch
command.CommandText = "COMMIT TRANSACTION";
command.Parameters.Clear();
command.ExecuteNonQuery();
}
finally
{
if (connection != null)
{
connection.Close();
}
}
Related
I have the following Microsoft Access database structure:
I am trying to create a C# function in the class book: Return(), that is used to return the book from the client that borrowed it. This is done by setting a return date on the borrow table (is being set to the time the function got called).
Here is the function:
public bool Return()
{
if (IsFree())
return false;
const string SQL = "UPDATE Borrow SET Return_date = #Date WHERE Book_ID = #Id AND Return_date IS NULL";
conn.Open();
using (OleDbCommand cmd = conn.CreateCommand())
{
cmd.CommandText = SQL;
cmd.Parameters.AddRange(new OleDbParameter[]
{
new OleDbParameter("#Id", Id),
new OleDbParameter("#Date", DateTime.Now)
});
conn.Close();
}
return true;
}
When I run it I get no error, but nothing changes in the database, and trying to get the numbers of rows affected returns 0.
Other functions that involve the database work fine so it is not the connection string.
When I try to run the query in Microsoft Access it works fine.
what have I done wrong?
Change the order of the following two lines from:
new OleDbParameter("#Id", Id),
new OleDbParameter("#Date", DateTime.Now)
To:
new OleDbParameter("#Date", DateTime.Now),
new OleDbParameter("#Id", Id)
OLE DB does not recognise named parameters, but merely populates the parameters in the order in which they appear in the SQL statement.
Try with the following code, and make sure that the Id has some value:
public bool Return()
{
if (IsFree())
return false;
const string SQL = "UPDATE Borrow SET Return_date = #Date WHERE Book_ID = #Id AND Return_date IS NULL";
using (OleDbConnection connection = new OleDbConnection(
connectionString))
{
using(OleDbCommand command = new OleDbCommand(SQL)) {
command.Connection = connection;
command.Parameters.AddRange(new OleDbParameter[]
{
new OleDbParameter("#Id", Id),
new OleDbParameter("#Date", DateTime.Now)
});
command.Connection.Open();
command.ExecuteNonQuery();
}
}
return true;
}
You should not close the connection yourself and it is better to use a new connection each time
I tried to do begin transaction on SQL Server, but it returns an error that I can't figure out what the real problem is. So here is some of my code I tried.
This is the error:
Code:
SqlConnection connection = new SqlConnection("Data Source=LOCALHOST\\SQLEXPRESS;Initial Catalog=tempdb;Integrated Security=SSPI;User ID = xxxx; Password=xxx;");
DateTime dt = dateTimePicker1.Value.Date;
dt = dt.AddDays(60);
string selectQuery = "BEGIN TRANSACTION UPDATE tester SET
test_ad=#dateTimePicker1, test_ud=#dt, test_pd=#dt WHERE
test_name=#textBox1;INSERT INTO records(testr_status, testr_name, testr_ad,
testr_ud, testr_pd, apte_name)VALUES(#testr_status, testr_name = #comboBox1,
testr_ad = #dateTimePicker1, testr_ud = #dt, testr_pd = #dt COMMIT";
connection.Open();
SqlCommand command = new SqlCommand(selectQuery, connection);
command.Parameters.AddWithValue("#dateTimePicker1",this.dateTimePicker1.Value.Date);
command.Parameters.AddWithValue("#textBox1", this.textBox1.Text);
command.Parameters.AddWithValue("#comboBox1",this.comboBox1.SelectedItem);
command.Parameters.AddWithValue("#testr_status",SqlDbType.VarChar);
command.Parameters.AddWithValue("#dt", dt);
int iResult = command.ExecuteNonQuery();
if (iResult > 0)
MessageBox.Show("Successfully saved ", "Error",MessageBoxButtons.OK, MessageBoxIcon.Information);
else
MessageBox.Show("Record not saved ", "Error",MessageBoxButtons.OK, MessageBoxIcon.Error);
command.ExecuteNonQuery();
connection.Dispose();
command.Dispose();
Try cleaning up a bit your query or paste it on SSMS and declare your parameters and you will figure out what is wrong.
In your case your INSERT statement has some errors.
This is not valid syntax VALUES (test_name = #combobox1) instead you only pass the parameter VALUES (#combobox1)
There are more columns in the INSERT statement than values specified in the VALUES clause, you are not providing a value for apte_name. In the c# code you will need to add that parameter too.
You are missing the closing parenthesis for the VALUES clause
You should end up with something like this (not tested)
string selectQuery =
#"BEGIN TRANSACTION
UPDATE tester SET
test_ad = #dateTimePicker1,
test_ud = #dt,
test_pd = #dt
WHERE test_name = #textBox1;
INSERT INTO records
(
testr_status,
testr_name,
testr_ad,
testr_ud,
testr_pd,
apte_name
)
VALUES
(
#testr_status,
#comboBox1,
#dateTimePicker1,
#dt,
#dt,
#apte_name
);
COMMIT";
The actual problem is, that is one big, invalid SQL statement. Use the semi-colon to separate statements, like so:
"BEGIN TRANSACTION;
INSERT ...;
UPDATE ...;
ETC ...;
COMMIT;"
That said, don't embed transaction statements in a query string. Do what Oliver suggests in another answer.
You can use SqlTransaction
using (SqlConnection conn = new SqlConnection("Connection String"))
{
conn.Open();
SqlTransaction trans;
trans = conn.BeginTransaction();
string selectQuery = "your sql query";
SqlCommand command = new SqlCommand(selectQuery, connection);
int iResult = command.ExecuteNonQuery();
if (iResult > 0)
{
trans.Commit();
}else{
trans.Rollback();
}
conn.Close();
}
Use a formatted string for your select query by using # and the syntax in the value block in not accurate.
string selectQuery = #"
BEGIN TRANSACTION
UPDATE tester SET test_ad = #dateTimePicker1, test_ud = #dt, test_pd = #dt WHERE test_name = #textBox1;
INSERT INTO records(testr_status, testr_name, testr_ad, testr_ud, testr_pd, apte_name) VALUES(#testr_status, #comboBox1, #dateTimePicker1, #dt, #dt);
COMMIT";
I'm using the ExecuteNonQuery function and stored procedure to insert a new record in an MSSQL database.
During testing the insert of the new record is successful. But my second call to ExecuteScalar and get the newly inserted record's ID fails. The reason for the failure according to the debugger is:
ExecuteScalar requires an open and available Connection. The
connection's current state is closed.
Looking at this error it explains that the connection has been closed after the initial call to ExecuteNonQuery. Meaning that my code to get the ID won't have a valid connection to query with.
Question:
How can you retrieve ##Identity following an ExecuteNonQuery?
This is the piece of code that performs the insert in the DAL:
Database db = GetConnectionString();
string sqlCommand = "CREATE_RECORD";
string idQuery= "Select ##Identity";
int recID = 0;
using (DbCommand dbCommand = db.GetStoredProcCommand(sqlCommand))
{
db.AddInParameter(dbCommand, "#Application", DbType.String, escalation.Application);
db.AddInParameter(dbCommand, "#UpdatedTime", DbType.DateTime, escalation.UpdatedTime);
db.ExecuteNonQuery(dbCommand);
dbCommand.CommandText = idQuery;
recID = (int)dbCommand.ExecuteScalar();
return recID ;
}
DISCLAIMER: This is a bad idea - the correct solution is server-side (server in this case is SQL Server).
You may be able to do this if you use SCOPE_IDENTITY() (which you should anyway - ##IDENTITY is not guaranteed to be your insert's identity) and execute your command as CommandType.Text instead of CommandType.StoredProcedure
WARNING: Serious security implications here, most notably SQL Injection Attack possibility:
Database db = GetConnectionString();
string sqlCommand = $"CREATE_RECORD '{escalation.Application}', '{escalation.UpdatedTime}'";
string idQuery= "Select SCOPE_IDENTITY()"
int recID = 0;
using (DbCommand dbCommand = db.GetStoredProcCommand(sqlCommand))
{
dbCommand.CommandType = commandType.Text;
db.ExecuteNonQuery(dbCommand);
dbCommand.CommandText = idQuery;
recID = (int)dbCommand.ExecuteScalar();
return recID;
}
Of course, if you go this route, you might as well combine both commands into a single query:
Database db = GetConnectionString();
string sqlCommand = $"CREATE_RECORD '{escalation.Application}', '{escalation.UpdatedTime}'; Select SCOPE_IDENTITY()";
int recID = 0;
using (DbCommand dbCommand = db.GetStoredProcCommand(sqlCommand))
{
dbCommand.CommandType = commandType.Text;
//TODO: Open connection using your db object
recID = (int)dbCommand.ExecuteScalar();
//TODO: Close connection using your db object
return recID;
}
Again, I stress that the correct solution is to fix this in SQL, not in C#. Use at your own risk!
You should create and open connection for each query and dispose it after query. Don't worry, there are connection pool in ADO and connection will not be physically established and closed each time. It's only a hint for ADO.NET.
int recID = 0;
string connStr = ProcThatGivesYouConnectionString();
using (SqlConnection con = new SqlConnection(connStr))
{
con.Open();
SqlCommand command = new SqlCommand("CREATE_RECORD", con);
command.CommandType = CommandType.StoredProcedure;
command.Parameters.AddWithValue("#Application", escalation.Application);
command.Parameters.AddWithValue("#UpdatedTime", escalation.UpdatedTime);
command.ExecuteNonQuery();
}
using (SqlConnection con2 = new SqlConnection(connStr))
{
con2.Open();
SqlCommand command = new SqlCommand("Select ##Identity", con2);
recID = (int)command.ExecuteScalar();
}
Also you can execute both queries in one command if you want:
int recID = 0;
string connStr = ProcThatGivesYouConnectionString();
using (SqlConnection con = new SqlConnection(connStr))
{
con.Open();
SqlCommand command = new SqlCommand("
EXEC CREATE_RECORD #Application = #Application, #UpdatedTime = #UpdatedTime
SELECT ##Identity", con);
command.Parameters.AddWithValue("#Application", escalation.Application);
command.Parameters.AddWithValue("#UpdatedTime", escalation.UpdatedTime);
recID = (int)command.ExecuteScalar();
}
object r = command.ExecuteScalar();
Convert.ToInt32(r.ToString());
To prevent the ExecuteScalar gets Specified cast is not valid error , use above
I currently have a problem with multiple SqlDataReader and commands in a single method. This method should delete a customer + its related address, networks, ipaddresses...
When I execute the code, I get an error
There is already an open DataReader associated with this Command which must be closed first
So I googled a little bit and read, that the
using(SqlDataReader....)
and adding MultipleActiveResultSets=True to the connection string should help.
I'm using SQL Server 2014, I heard that there is a problem with SQL Server 2005, so that shouldnt be a problem..
But it still doesn't work...
The exception is thrown at
var addressId = (int)command.ExecuteScalar();
Connection string:
Data Source=.\\DATABASE;Initial Catalog=customer;Persist Security Info=True;MultipleActiveResultSets=True;User ID=sa;Password=xxxxxxxx!
Code:
public static Boolean ExecuteDeleteCutomer(string customerId) {
using (SqlConnection connection = new SqlConnection(new DatabaseConnection().ConnectionString)) {
connection.Open();
SqlCommand command = connection.CreateCommand();
SqlTransaction transaction;
SqlDataReader locationReader;
SqlDataReader networkReader;
SqlDataReader ipaddressReader;
// Start a local transaction to delete a customer and related table entries.
transaction = connection.BeginTransaction("StartTransaction DeleteCustomer");
// Must assign both transaction object and connection
// to Command object for a pending local transaction
command.Connection = connection;
command.Transaction = transaction;
try {
//First get the locations of selected customer
command.Parameters.AddWithValue("#customerId", customerId);
command.CommandText =
"SELECT l_id from location where c_id = #customerId";
locationReader = command.ExecuteReader();
using (locationReader) { //save location ids in a reader
while (locationReader.Read()) {
var locationID = locationReader.NextResult();
command.Parameters.AddWithValue("#locationId", locationID);
command.CommandText =
"SELECT a_id from address where l_location = #locationId";
var addressId = (int)command.ExecuteScalar(); // get address ID to delete later
command.Parameters.AddWithValue("#addressId", addressId);
command.CommandText = "SELECT n_id from network where n_location = #locationId";
using (networkReader = command.ExecuteReader()) { // save networks in a reader;
while (networkReader.Read()) {
var networkId = networkReader.NextResult();
command.Parameters.AddWithValue("#networkId", networkId);
command.CommandText = "SELECT ip_id from ipaddress where n_id = #networkId";
using (ipaddressReader = command.ExecuteReader()) { // get ipaddressId ID to delete later
while (ipaddressReader.Read()) {
var ipaddressId = ipaddressReader.NextResult();
command.Parameters.AddWithValue("#ipId", ipaddressId);
command.CommandText = "Delete from ipaddress where ip_id = #ipId; ";
command.ExecuteScalar();
}
}
command.CommandText = "Delete from network where n_id = #networkId; ";
command.ExecuteScalar();
}
}
command.CommandText = "Delete from location where l_id = #locationID; ";
command.ExecuteScalar();
}
}
command.CommandText = "Delete from customer where c_id = #customerId; ";
command.ExecuteScalar();
// Attempt to commit the transaction.
transaction.Commit();
return true;
}
catch (Exception ex) {
Console.WriteLine("Commit Exception Type: {0}", ex.GetType());
Console.WriteLine(" Message: {0}", ex.Message);
// Attempt to roll back the transaction.
try {
transaction.Rollback();
}
catch (Exception ex2) {
// This catch block will handle any errors that may have occurred
// on the server that would cause the rollback to fail, such as
// a closed connection.
Console.WriteLine("Rollback Exception Type: {0}", ex2.GetType());
Console.WriteLine(" Message: {0}", ex2.Message);
}
return false;
}
}
}
Thanks!
As the error message reads, you already have an open DataReader associated with that Command object.
You need to create a new SqlCommand object for each (nested) command that you want to execute.
SqlCommand locationCommand = connection.CreateCommand();
SqlCommand networkCommand = connection.CreateCommand();
SqlCommand ipAddrCommand = connection.CreateCommand();
Assign your CommandText as needed to each of those command objects, then you can call ExecuteReader on each one and process as needed.
Use a different command instance for the outer and inner commands; so essentially:
var innerCommand = ...
innerCommand.CommandText = "Delete from ipaddress where ip_id = #ipId; ";
var ipId = innerCommand.Parameters.Add(...);
while (ipaddressReader.Read()) {
ipId.Value = ...
innerCommand.ExecuteNonQuery();
}
(and similar for each command)
Basically, each command instance should only be executing once at a time, regardless of whether they share a connection or not.
I am trying to execute a stored procedure in my Sql Server Database from an Asp.Net MVC project. I have set it up in a way that I can use it for testing purposes, which is why the variable "procedureTest" is a constant value (I know that "2044" is an existing record in my database). I will change this once I accomplish a successful run. Also, I know that my stored procedure works because I have executed it in Sql Server Management Studio successfully. The procedure has the task of adding ID from one table to another, but I have yet to see it appear in this table. I am not receiving an error in my catch block so I am kind of lost at the moment. I could definitely use your help.
try
{
using (var connection = new SqlConnection(connectionString))
{
connection.Open();
int procedureTest = 2044;
var command = new SqlCommand("SELECT ID FROM Images WHERE ID = #id", connection);
var paramDate = new SqlParameter("#id", procedureTest);
command.Parameters.Add(paramDate);
var reader = command.ExecuteReader();
while (reader.Read())
{
var storedProcCommand = new SqlCommand("EXEC addToNotificationTable #ID", connection);
var paramId = new SqlParameter("#ID", reader.GetInt32(0));
storedProcCommand.Parameters.Add(paramId);
command.ExecuteNonQuery();
}
}
}
catch (Exception e)
{
string exceptionCause = String.Format("An error occurred: '{0}'", e);
System.IO.File.WriteAllText(#"C:\Users\Nathan\Documents\Visual Studio 2013\Projects\MVCImageUpload\uploads\exception.txt", exceptionCause);
}
Stored Procedure:
CREATE PROCEDURE addToNotificationTable #ID int
AS
Insert NotificationTable (ID)
SELECT ID
FROM Images
Where ID = #ID
Change you code like this
while (reader.Read())
{
var storedProcCommand = new SqlCommand("EXEC addToNotificationTable #ID", connection);
var paramId = new SqlParameter("#ID", reader.GetInt32(0));
storedProcCommand.Parameters.Add(paramId);
storedProcCommand.ExecuteNonQuery();
}
First of all, you missed to specify the command type. Also using EXEC in SqlCommand is not a proper way.
Please try with the below code
while (reader.Read())
{
using(SqlCommand storedProcCommand = new SqlCommand("addToNotificationTable", connection)) //Specify only the SP name
{
storedProcCommand.CommandType = CommandType.StoredProcedure; //Indicates that Command to be executed is a stored procedure no a query
var paramId = new SqlParameter("#ID", reader.GetInt32(0));
storedProcCommand.Parameters.Add(paramId);
storedProcCommand.ExecuteNonQuery()
}
}
Since you are calling the sp inside a while loop, wrap the code in using() { } to automatically dispose the command object after each iteration