Is my SqlCommand variable overworked? Did it get tired? - c#

In my testing project, I have a static class called FixtureSetup which I use to setup my integration testing data for validation.
I use the same SqlCommand and SqlParameter variable (not the object itself) within that class, repeatedly, using the same variable references over and over, assigning new SqlCommand and SqlParameter objects each time. My connection itself is created once and passed into the methods performing the setup, so each setup uses it's own distinct connection reference, and while the same conn is used multiple times, it's always in a linear sequence.
In one such method, I ran into a very odd situation, where my SqlCommand variable simply appears to have gotten tired.
cmd = new SqlCommand("INSERT INTO Subscription (User_ID, Name, Active) VALUES (#User_ID, #Name, #Active)", conn);
parameter = new SqlParameter("#User_ID", TestUserID); cmd.Parameters.Add(parameter);
parameter = new SqlParameter("#Name", "TestSubscription"); cmd.Parameters.Add(parameter);
parameter = new SqlParameter("#Active", true); cmd.Parameters.Add(parameter);
cmd.ExecuteNonQuery();
cmd = new SqlCommand("SELECT Subscription_ID FROM [Subscription] WHERE Name = 'TestSubscription'", conn);
parameter = new SqlParameter("#User_ID", TestUserID);
cmd.Parameters.Add(parameter);
using (dr = cmd.ExecuteReader())
{
while (dr.Read())
{
TestSubscriptionID = dr.GetInt32(dr.GetOrdinal("Subscription_ID"));
}
}
cmd = new SqlCommand("INSERT INTO SubscriptionCompany (Subscription_ID, Company_ID) VALUES (#Subscription_ID, #Company_ID)", conn);
parameter = new SqlParameter("#Subscription_ID", TestSubscriptionID); cmd.Parameters.Add(parameter);
parameter = new SqlParameter("#Company_ID", KnownCompanyId); cmd.Parameters.Add(parameter);
cmd.ExecuteNonQuery();
In the above, at the last line shown, doing the same thing I've done quite literally in dozens of other places (insert data, read the ID column and capture it), I get the following:
SetUp : System.InvalidOperationException : ExecuteNonQuery requires an
open and available Connection. The connection's current state is
closed. at
System.Data.SqlClient.SqlConnection.GetOpenConnection(String method)
BUT - replace cmd with new variable myCmd, and everything works swimmingly!
SqlCommand myCmd;
myCmd = new SqlCommand("INSERT INTO Subscription (User_ID, Name, Active) VALUES (#User_ID, #Name, #Active)", conn);
parameter = new SqlParameter("#User_ID", TestUserID); myCmd.Parameters.Add(parameter);
parameter = new SqlParameter("#Name", "TestSubscription"); myCmd.Parameters.Add(parameter);
parameter = new SqlParameter("#Active", true); myCmd.Parameters.Add(parameter);
myCmd.ExecuteNonQuery();
myCmd = new SqlCommand("SELECT Subscription_ID FROM [Subscription] WHERE Name = 'TestSubscription'", conn);
parameter = new SqlParameter("#User_ID", TestUserID);
myCmd.Parameters.Add(parameter);
using (dr = myCmd.ExecuteReader())
{
while (dr.Read())
{
TestSubscriptionID = dr.GetInt32(dr.GetOrdinal("Subscription_ID"));
}
}
myCmd = new SqlCommand("INSERT INTO SubscriptionCompany (Subscription_ID, Company_ID) VALUES (#Subscription_ID, #Company_ID)", conn);
parameter = new SqlParameter("#Subscription_ID", TestSubscriptionID); myCmd.Parameters.Add(parameter);
parameter = new SqlParameter("#Company_ID", KnownCompanyId); myCmd.Parameters.Add(parameter);
myCmd.ExecuteNonQuery();
What the heck is going on here? Did my command var just get tired???
What clued me to the "fix" was I noticed in my tracing that in my "read the id" block, my cmd.Parameters block had only ONE parameter in it, the 2nd one added, and when I forced the first cmd.Parameters.Add line to execute again, the number of parameters in the list dropped to 0. That's what prompted me to try a method level SqlCommand...cause I had the crazy idea that my cmd was tired... Imagine my shock when I apparently turned out to be right!
Edit: I'm not recycling any objects here - just the variable reference itself (static SqlCommand at the class level). My apologies for the earlier confusion in my wording of the question.

use one command per query and call dispose (or better yet, wrap in a using statement). you don't want to be "reusing" ado.net components.

Big Edit:
From: http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqldatareader.close.aspx
You must explicitly call the Close method when you are through using
the SqlDataReader to use the associated SqlConnection for any other
purpose.
The Close method fills in the values for output parameters, return
values and RecordsAffected, increasing the time that it takes to close
a SqlDataReader that was used to process a large or complex query.
When the return values and the number of records affected by a query
are not significant, the time that it takes to close the SqlDataReader
can be reduced by calling the Cancel method of the associated
SqlCommand object before calling the Close method.
so try:
using (dr = cmd.ExecuteReader())
{
while (dr.Read())
{
TestSubscriptionID = dr.GetInt32(dr.GetOrdinal("Subscription_ID"));
}
dr.Close();
}

Check that you haven't set the DataReader to CommandBehavior.CloseConnection since you mentioned that you're re-using the connection for your test initialization.
Also, the DataReader does take resources, so utilize Dispose

Do you really need to do make a new connection object after each try?
myCmd = new SqlCommand(...)
//your code
myCmd = new SqlCommand(...)
//etc
You can just say:
myCmd.CommandText = "INSERT INTO SubscriptionCompany (Subscription_ID, Company_ID) VALUES (#Subscription_ID, #Company_ID)";
so that you may re-use your command object. Additionally you can reset your parameters as well after each call. Just call myCmd.Parameters.Clear().
Also, make sure you wrap your SqlCommand in a using statement so they will be properly cleaned up.
using (SqlCommand cmd = new SqlCommand())
{
cmd.CommandText = "some proc"
cmd.Connection = conn;
//set params, get data, etc
cmd.CommandText = "another proc"
cmd.Parameters.Clear();
//set params, get date, etc.
}

Related

How do I use ExecuteScalar with a stored Procedure?

I'm trying to get a count of column records in a Sql database and show the result in a MessageBox.
This is my code:
public DataTable CheckIfNameExist(string name)
{
con = Connect();
cmd = new SqlCommand();
cmd.Connection = con;
cmd.CommandText = "spCheckIfNameExist";
cmd.Parameters.AddWithValue("#Name", SqlDbType.NVarChar).Value = name;
MessageBox.Show(name);
Int32 totalNames = (Int32) cmd.ExecuteScalar();
string tNames = totalNames.ToString();
MessageBox.Show(tNames);
}
And this is my sp:
#Name nvarchar(50) = null
As
Begin
SELECT COUNT(*) from OrdersSent where CustomerName LIKE #Name + '%'
End
Problem:
It always returns 0.
There are a couple of errors in your code:
You should write it as:
cmd.CommandText = "spCheckIfNameExist";
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add("#Name", SqlDbType.NVarChar).Value = name;
First you need to tell the ADO engine that you are calling a stored procedure and not a simple command text, but you also need to use Add instead of AddWithValue to be precise on the type of the parameter passed to the SP. Your code creates a parameter int becase the second parameter of the AddWithValue is the Value of the parameter not the type.
You have a few problems in the c# code - the most important is probably this:
cmd.Parameters.AddWithValue("#Name", SqlDbType.NVarChar).Value = name;
Don't use AddWithValue. Use Add.
Also, you didn't specify the command type - the default is Text.
And you are using fields for SqlConnection and SqlCommand - which is also the wrong thing to do. You should create and dispose both of them inside each method you are using them.
A better version of your code would be this:
using(var con = new SqlConnection(ConnectionString))
{
using(var cmd = new SqlCommand("spCheckIfNameExist", con))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add("#Name", SqlDbType.NVarChar).Value = name;
con.Open();
var tNames = cmd.ExecuteScalar().ToString();
}
}
Another thing that puzzles me is why a method called CheckIfNameExist returns a DataTable. I would expect it to simply return a bool.
If you really only want to check if the name exists, you can do this better on both the SQL level and the c# level.
A better SQL would be something like this:
SELECT CAST(CASE WHEN EXISTS(
SELECT 1
FROM OrdersSent
WHERE CustomerName LIKE #Name + '%'
) THEN 1 ELSE 0 END AS bit)
And on the c# level, bit translates directly to bool, so the code can simple be this:
public bool CheckIfNameExist(string name)
{
using(var con = new SqlConnection(ConnectionString))
{
using(var cmd = new SqlCommand("spCheckIfNameExist", con))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add("#Name", SqlDbType.NVarChar).Value = name;
con.Open();
return (bool)cmd.ExecuteScalar();
}
}
}
And another note - you should avoid using the sp prefix for stored procedures.
Microsoft have reserved this prefix for built in system procedures.
For more information, read Aaron Bertrand's Is the sp_ prefix still a no-no?, where you'll see that the short answer to this question is "Yes".
The sp_ prefix does not mean what you think it does: most people think sp stands for "stored procedure" when in fact it means "special." Stored procedures (as well as tables and views) stored in master with an sp_ prefix are accessible from any database without a proper reference (assuming a local version does not exist). If the procedure is marked as a system object (using sp_MS_marksystemobject (an undocumented and unsupported system procedure that sets is_ms_shipped to 1), then the procedure in master will execute in the context of the calling database.
You need to specify the type of your command like this:
cmd.CommandText = "spCheckIfNameExist";
cmd.CommandType = CommandType.StoredProcedure;
See also:
What is the benefit of using CommandType.StoredProcedure versus using CommandType.Text?
Although specify the type directly and use the Value property is more better than AddWithValue:
cmd.Parameters.Add("#Name", SqlDbType.NVarChar).Value = name;
The following article could be also interesting:
https://blogs.msmvps.com/jcoehoorn/blog/2014/05/12/can-we-stop-using-addwithvalue-already/

How to delete from Access database from C#

I am trying to add code that will delete from 2 tables in access db file. Sometimes one of them will work and the other wont then when I try it another way it will do the opposite. So in the end only 1 of the 2 works.
Here is my code I hope someone can spot something I did wrong.
try
{
Conn.Open();
OleDbCommand command = new OleDbCommand();
command.Connection = Conn;
command.CommandText = "DELETE FROM TBLNAME WHERE name =#name";
command.Parameters.AddWithValue("#name", lvlist.SelectedItems[0].Text);
command.ExecuteNonQuery();
command.CommandText = "DELETE from TBLNAME WHERE cb_listName =#listname";
command.Parameters.AddWithValue("#listname", lvlist.SelectedItems[0].Text);
command.ExecuteNonQuery();
Conn.Close();
}
catch (Exception ex)
{
MessageBox.Show("Error " + ex);
}
You should use different Command instances, one for each command you want to execute. If you do not do that then you need to clear the parameters. This is because parameters in OleDb queries are positional and not named. This means that when you add the 2nd parameter in the 2nd query the first parameter is used because it is first in the list.
using(var connection = new OleDbConnection("connection string here"))
{
connection.Open();
using(var command = new OleDbCommand("DELETE FROM TBLNAME WHERE name = #name", connection))
{
cmd.Parameters.Add(new OleDbParameter("#name", OleDbType.VarChar, 50)).Value = lvlist.SelectedItems[0].Text;
command.ExecuteNonQuery();
}
using(var command = new OleDbCommand("DELETE from TBLNAME WHERE cb_listName = #listname", connection))
{
cmd.Parameters.Add(new OleDbParameter("#listname", OleDbType.VarChar, 50)).Value = lvlist.SelectedItems[0].Text;
command.ExecuteNonQuery();
}
}
Also you should:
Use using blocks to ensure connections are closed after use. Do not try to create class scoped, or even worse global, connection instances.
You should also specify the db type for your parameters and do not use AddwithValue.
When possible also specify the length for your db types, in the above this is possible if you have a varchar type. note I toke a guess at your schema length for these columns
Finally, just a note on general best practices, do not add catch blocks that do nothing useful with the exception. At least log the type, message, and the stack trace and then repeat this recursively for each inner exception found in property InnerException. This useful information can help you figure out exactly why an exception occurred.
Use two different OleDbCommand objects.

Error: Object reference not set to an instance of an object./The connection was not closed. The connection's current state is open

For the longest time, my code has been running, but lately I encounter this error,Object reference not set to an instance of an object. I don't know if it is related to the creation and usage of a new database.
Here is my code:
con2.Open();
SqlCommand cmd = new SqlCommand();
cmd.Connection = con;
cmd.CommandText = "SELECT QtyInHand FROM Inventory WHERE ProductID=#ProductID";
cmd.Parameters.Add("#ProductID", SqlDbType.Int).Value = productID;
int existingQty = (int)cmd.ExecuteScalar();
cmd.Parameters.Clear();
cmd.CommandText = "UPDATE Inventory SET QtyInHand=#QtyInHand WHERE ProductID=#ProductID";
cmd.Parameters.Add("#QtyInHand", SqlDbType.Int).Value = existingQty - int.Parse(quantity);
cmd.Parameters.Add("#ProductID", SqlDbType.Int).Value = productID;
cmd.ExecuteNonQuery();
con2.Close();
Error on this part: int existingQty = (int)cmd.ExecuteScalar();
When I tried using my other SqlConnection: con
con.Open();
SqlCommand cmd = new SqlCommand();
cmd.Connection = con;
cmd.CommandText = "SELECT QtyInHand FROM Inventory WHERE ProductID=#ProductID";
cmd.Parameters.Add("#ProductID", SqlDbType.Int).Value = productID;
int existingQty = (int)cmd.ExecuteScalar();
cmd.Parameters.Clear();
cmd.CommandText = "UPDATE Inventory SET QtyInHand=#QtyInHand WHERE ProductID=#ProductID";
cmd.Parameters.Add("#QtyInHand", SqlDbType.Int).Value = existingQty - int.Parse(quantity);
cmd.Parameters.Add("#ProductID", SqlDbType.Int).Value = productID;
cmd.ExecuteNonQuery();
con.Close();
I encounter another error, The connection was not closed. The connection's current state is open. Error on con.Open(); part. How should I solve this problem?
For the first error, your executeScalar() call is returning a null value. Either refine your query - you can check your query by running it directly in your database - or change your logic to deal with null values.
For the second error, if calling Open() is throwing that error, it's because the connection object was in use before and was not closed properly. It's usually considered bad practice to reuse connections like that, so consider creating a new connection instance when you go opening one.
Edit: I tried to imply something in the second paragraph there, but now I feel I must make it explicit: don't forget to deal with the connection you left open there, as it may be a major performance hog for your application. Specially since it's an ASP.NET one. Dispose of connections as soon as you don't need them. When you call Dispose() for a connection, it gets closed - with the added bonus of other finer memory management procedures as well. Read about the using statement and its usage with connections as soon as you have some time.

Connecting to a Server

Trying to improve my C# to SQL skills... Currently I am using this bit of code to pull data from our application server. I have two different DBA's telling me two other ways to write this, just trying to figure out if this should be improved on or changed. If so, I would really appreciate some kind of examples.
FYI: This code...
db.con(user.Authority)
...Is essentially a 'new sqlconnection' code.
DataTable dtInfo = new DataTable("SomeInfo");
using (SqlConnection con = db.con(user.Authority))
{
string command = "SOME SQL STATEMENT;";
using (SqlCommand cmd = new SqlCommand(command,con))
{
cmd.CommandType = CommandType.Text;
cmd.Parameters.AddWithValue("#Param", sqlDbType).Value = Param;
con.Open();
cmd.ExecuteNonQuery();
**********
*** OR ***
**********
cmd.CommandType = CommandType.Text;
cmd.Parameters.AddWithValue("#Param", sqlDbType).Value = Param;
using (SqlDataAdapter da = new SqlDataAdapter(cmd))
{
da.Fill(dtInfo );
}
}
}
So, if I'm understanding the provided information, this is my best route?
using (SqlConnection con = db.con(user.Authority))
{
string command = "SELECT [TBL_EMPLOYEE].[ACTIVE_DIRECTORY] FROM [TBL_EMPLOYEE];";
using (SqlCommand cmd = new SqlCommand(command, con))
{
con.Open();
using (SqlDataReader reader = cmd.ExecuteReader())
{
while (reader.Read())
{
MessageBox.Show(reader["ACTIVE_DIRECTORY"].ToString());
}
}
}
And one last thing... This should prevent the need for
cmd.Dispose();
etc...
The code would depend on the specific query. If the query retrieves rows of data (as a SELECT does), then you would go the da.Fill() route. If it's a query that just makes a change to the database (such as INSERT, UPDATE, or DELETE), then you would use ExecuteNonQuery().
I would not use the SqlDataAdapter version. The version that uses the SqlCommand object and the SqlDataReader will perform better, and allows more insight into the actual data being returned.
// Assumes the following sql:
// SELECT foo, bar FROM baz
// error checking left out for simplicity
var list = new List<SomeClass>();
using(var reader = cmd.ExecuteReader()) {
while(reader.Read()) {
list.Add(new SomeClass {
// NOTE: you can see the columns that the c# is referencing
// and compare them to the sql statement being executed
Foo = (string)reader["foo"],
Bar = (string)reader["bar"]
});
}
}
Later as your level of experiance increases you will be able to use other features of the SqlCommand and SqlDataReader classes in order to ensure that the code executes as quickly as possible. If you start using the SqlDataAdapter route, you will eventually have to relearn how to do the exact same things you have already been doing because the SqlCommand and SqlDataReader have operations that do not exist elsewhere in .NET.
ExecuteNonQuery returns the number of rows effected.
A DataTable is not an efficient way to retrieve that number.
int rowsRet = cmd.ExecuteNonQuery();
SqlCommand.ExecuteNonQuery Method

How to repeat the Insert command in C# to SQL Server database 3 times consecutively

I'm new here and im really having trouble with this part of the code. I need to repeat the Insert multiple times consecutively based on the given quantity.
int ctr = int.Parse(txtquantity3.ToString());
int x=0;
SqlCommand cmd = new SqlCommand("INSERT INTO tbl_books (Book_num,Call_Num, Book_Title, Book_Author, Book_Publisher, Book_Quantity, Book_Pages, genre , Book_available,book_status) VALUES (#Bn, #Cn, #Title, #Author, #Publisher, #Quantity, #Pages, #Genre, #Available, #Status)");
cmd.CommandType = CommandType.Text;
cmd.Connection = con;
MessageBox.Show("Save successful!");
while (x <= ctr)
{
x += 1;
cmd.Parameters.AddWithValue("#Bn", txtbooknum3.Text);
cmd.Parameters.AddWithValue("#Cn", txtcallnum3.Text);
cmd.Parameters.AddWithValue("#Title", txttitle3.Text);
cmd.Parameters.AddWithValue("#Author", txtauthor3.Text);
cmd.Parameters.AddWithValue("#Publisher", txtpublisher3.Text);
cmd.Parameters.AddWithValue("#Quantity", txtquantity3.Text);
cmd.Parameters.AddWithValue("#Pages", txtpages3.Text);
cmd.Parameters.AddWithValue("#Genre", txtgenre3.Text);
cmd.Parameters.AddWithValue("#Available", txtquantity3.Text);
cmd.Parameters.AddWithValue("#Status", "Available");
con.Open();
cmd.ExecuteNonQuery();
}
You can't use AddWithValue in that way. The SqlCommand instance is always the same so calling AddWithValue a second time with the same parameter name produces an exception. You need to add, at every loop a call to
while (x <= ctr)
{
x+=1;
cmd.Parameters.Clear();
....
}
However your code could be changed to create the parameter collection before entering the loop and inside the loop changing only the parameter value.
A full example on how to rewrite your code introducing a better Handling of the connection is the following....
// Using statement to close and dispose on exit
using(SqlConnection con = new SqlConnection(.....))
using(SqlCommand cmd = new SqlCommand(#"INSERT INTO tbl_books
(Book_num,Call_Num, Book_Title, Book_Author,
Book_Publisher, Book_Quantity, Book_Pages, genre ,
Book_available,book_status)
VALUES (#Bn, #Cn, #Title, #Author, #Publisher, #Quantity,
#Pages, #Genre, #Available, #Status)", con))
{
// Open the connection just one time here
con.Open();
// Create all the parameters required SPECIFYING the correct datatype
cmd.Parameters.Add("#Bn", SqlDbType.NVarChar);
...... declare other parameters....
// Start your loop
while (x <= ctr)
{
x+=1;
cmd.Parameters["#bn"].Value = txtbooknum3.Text;
.... set the value for other parameters at each loop ....
cmd.ExecuteNonQuery();
}
}
Finally, if all you need is to insert a specific number of record all with the same values then just call the Adds setting the values before entering the loop and execute the ExecuteNonQuery the required number of times without changing anything in the parameter collection
using(SqlConnection con = new SqlConnection(.....))
using(SqlCommand cmd = new SqlCommand(#"INSERT INTO tbl_books
(Book_num,Call_Num, Book_Title, Book_Author,
Book_Publisher, Book_Quantity, Book_Pages, genre ,
Book_available,book_status)
VALUES (#Bn, #Cn, #Title, #Author, #Publisher, #Quantity,
#Pages, #Genre, #Available, #Status)", con))
{
con.Open();
// Create all the parameters required SPECIFYING the correct datatype
cmd.Parameters.Add("#Bn", SqlDbType.NVarChar).Value = txtbooknum3.Text;
...... declare other parameters and set the value before the loop....
// Start your loop
while (x <= ctr)
{
x+=1;
// Just execute the query three times
cmd.ExecuteNonQuery();
}
}
You don't have to open a connection each time you want to execute a command. Open it before your while loop, and close it after.
I agree with all of the other suggestions about proper user of ADO.
That said, what is txtquantity3? From the way you use it it looks like a TextBox control. If so, then your first line should be
int ctr = int.Parse(txtquantity3.text);

Categories