I've got an ASP.NET MVC site with an admin page where I need to merge two records. I'm passing in two values, #old and #new. After adding those to my SqlCommand object, I call the ExecuteNonQuery(). This is generating an exception with the message saying
stored procedure 'FixDuplicate' expects parameter '#old' which was
not supplied
As you can see below, I'm definitely adding the parameter. All 3 versions that I've tried are there.
What's wrong with this code?
Version 1: (please ignore the syntax of this one, I already removed this code, but I did try it and confirmed that the parameters existed when it reached the ExecuteNonQuery() call)
var sql = "FixDuplicate";
comm.Parameters.Add(new System.Data.SqlClient.SqlParameter("#old", model.Duplicate));
comm.Parameters.Add(new System.Data.SqlClient.SqlParameter("#new", model.Primary));
_dataAccessService.ExecuteSql(conn, comm);
Version 2:
comm.Parameters.AddWithValue("#old", model.Duplicate);
comm.Parameters.AddWithValue("#new", model.Primary);
Version 3:
comm.Parameters.Add("#old", System.Data.SqlDbType.Int);
comm.Parameters[0].Value = model.Duplicate;
comm.Parameters.Add("#new", System.Data.SqlDbType.Int);
comm.Parameters[1].Value = model.Primary;
Lastly, here's the code in the _dataAccessService.ExecuteSql(conn, comm) call:
public void ExecuteSql(SqlConnection connection, SqlCommand command, bool closeConnection = true)
{
if (connection == null)
throw new ArgumentNullException("connection");
if (command == null)
throw new ArgumentNullException("command");
try
{
if (connection.State != ConnectionState.Open)
connection.Open();
try
{
command.ExecuteNonQuery();
}
catch
{
throw;
}
finally
{
if (closeConnection)
command.Dispose();
}
}
catch (Exception ex)
{
throw new Exception("Sorry, an error occurred ExecuteSql: " + ex.Message, ex);
}
finally
{
if (closeConnection)
connection.Dispose();
}
}
Note: I have confirmed that the values for both '#old' and '#new' are set when it gets to the ExecuteNonQuery() line.
Edit: Here's the full code, as it exists:
// Here we need to execute the "FixDuplicate" stored procedure
var sql = "FixDuplicate";
var conn = _dataAccessService.GetConnection("");
var comm = _dataAccessService.GetCommand(conn, sql, System.Data.CommandType.StoredProcedure);
//comm.Parameters.AddWithValue("#old", model.DuplicateWrestler);
//comm.Parameters.AddWithValue("#new", model.PrimaryWrestler);
comm.Parameters.Add("#old", System.Data.SqlDbType.Int);
comm.Parameters[0].Value = model.DuplicateWrestler;
comm.Parameters.Add("#new", System.Data.SqlDbType.Int);
comm.Parameters[1].Value = model.PrimaryWrestler;
_dataAccessService.ExecuteSql(conn, comm);
Probably your model.Duplicate is null. In this case value of parameter will not be set.
If you need to pass null to SqlParameter.Value, use DBNull.Value.
See SqlParameter.Value for reference.
I believe you need to specify a command type:
command.CommandType = CommandType.StoredProcedure;
I suggest you to check with Profiler if old parameter is set or not.
And also try to run this procedure from SQL Server Management Studio to be sure it's working fine.
Related
I have a method that calls a Stored Procedure in MySQL database:
public DataTable ExecuteSP_ReturnDataTable(DynamicGridAction pAction)
{
DataTable ret = new DataTable();
MySqlCommand wCmd = new MySqlCommand();
try
{
wCmd = new MySqlCommand(pAction.SPName);
wCmd.CommandType = CommandType.StoredProcedure;
wCmd.Parameters.Add(new MySqlParameter(pAction.SPParameterName, MySqlDbType.LongText)).Value = pAction.SPParameterViewColumn;
MySqlParameter outMessage = new MySqlParameter("pmessage", MySqlDbType.LongText);
outMessage.Direction = ParameterDirection.Output;
wCmd.Parameters.Add(outMessage);
if (base.mSqlTransaction == null)
wCmd.Connection = base.GetOpenedConection();
else
{
wCmd.Connection = (MySqlConnection)base.mSqlTransaction.Connection;
wCmd.Transaction = (MySqlTransaction)base.mSqlTransaction;
}
ret.Load(wCmd.ExecuteReader());
return ret;
}
catch (MySqlException exp)
{
throw new FunctionalException(exp.Number, exp);
}
catch (Exception ex)
{
throw ex;
}
finally
{
if (base.mSqlTransaction == null)
base.CloseConection();
}
}
The StoredProcedure name and Parameter name are dinamics.
When I execute that method at the first time, it works. After that, I changed the name of the storedprocedure parameter in mysql (newParamName), and I change the "pAction.SPParameterName" value with the same (newParamName).
I execute that method after that and, surprisingly, the wCmd.ExecuteReader() method throws me:
System.ArgumentException: 'Parameter 'oldParamName' not found in the collection.'
I debug the code step by step and the name of the parameter is correct (the new one), but i get that exception every time I call that method.
But, if I restart the application (.net6 webapi), it works.
I think that there is some kind of "cache" for the StoredProcedure definition in my aplication.
The problem is that I need to change the parameter's name (I took it from other table in database), but the method continue internally calling the old one until I restart the aplication.
If you're using MySql.Data, set Procedure Cache Size = 0; in your connection string.
From Connection Options Reference:
ProcedureCacheSize, Procedure Cache Size, procedure cache, procedurecache
Default: 25
Sets the size of the stored procedure cache. By default, Connector/NET stores the metadata (input/output data types) about the last 25 stored procedures used. To disable the stored procedure cache, set the value to zero (0).
I am trying to save this to my datebase but I keep getting this error
System.InvalidOperationException
here is my code.
protected void btnSend_Click(object sender, EventArgs e)
{
con.Open();
cmd = new SqlCommand(#"INSERT INTO orders2
(orderName,orderFile,orderType,orderPrice,orderQuantity,orderShipped)
VALUES
('"+DropDownList1.SelectedValue+"','"+lblFile.Text+"','"+lblPrice.Text+"','"+txtQuantity.Text+"','"+DateTime.Now+"')",con);
cmd.ExecuteNonQuery();
con.Close();
lblFinished.Text = "Order has been submitted for process.";
}
WhoAmI is probably right, however your code could be greatly improved to avoid other problems and to also allow you not to allow unhandled exceptions.
I have put extra comments directly in the code:
try
{
// SqlConnection is disposable, so it is recommended to dispose it (using calls Dispose() for you)
using (var con = new SqlConnection(connStr))
{
con.Open();
// this is missing from your code and might the errors actual cause
// SqlCommand is also disposable
using (var cmd = con.CreateCommand())
{
// is is strongly recommended to construct parameterized commands
// to avoid SQL injection (check this - https://technet.microsoft.com/en-us/library/ms161953(v=sql.105).aspx)
cmd.Text = #"
INSERT INTO orders2
(orderName,orderFile,orderType,orderPrice,orderQuantity,orderShipped)
VALUES (#orderName, #orderFile, #orderType, #orderPrice, #orderQuantity, #orderShipped)";
// the parameters - SqlCommand infers parameter type for you
cmd.AddWithValue("#orderName", DropDownList1.SelectedValue);
cmd.AddWithValue("#orderFile", lblFile.Text);
cmd.AddWithValue("#orderType", theMissingParametersForOrderType);
// some conversion might be needed here, as I expect the price to be some number
// with a fixed number of decimals
// e.g. Convert.ToDecimal(lblPrice.Text)
cmd.AddWithValue("#orderPrice", lblPrice.Text);
// same convertion issue as for price
cmd.AddWithValue("#orderQuantity", txtQuantity.Text);
cmd.AddWithValue("#orderShipped", DateTime.Now);
}
}
}
// there are several Exceptions that can be raised and treated separately
// but this at least you can do
catch (Exception exc)
{
// log the error somewhere
// put a breakpoint just below to inspect the full error details
}
// this is executed even if an exception has occurred
finally
{
if (con != null && con.State != ConnectionState.Closed)
con.Close();
}
As a side note, this code belongs to a data layer, no presentation layer. Consider including it within another assembly.
You are inserting 6 values(orderName,orderFile,orderType,orderPrice,orderQuantity,orderShipped) here, but supplied only 5 values. DropDownList1.SelectedValue, lblFile.Text, lblPrice.Text, txtQuantity.Text, DateTime.Now.
These are my functions to Update student record and Insert Student record in SQL batabase.
public void UpdateStudent(ref student stu, string rollno) //Update the values to corresponding roll number 'rollno'
{
try
{
connection.Open(); //I have already defined the connection and command
command = new SqlCommand("update student set FirstName='"+stu.Firstname+"',LastName='"+stu.Lastname+"',YearOfAdmission="+stu.Yearofadmission+",Branch='"+stu.Branch+"' where RollNo='"+rollno+"'", connection); //Yearofadmission is int
command.ExecuteNonQuery();
}
catch (Exception)
{
throw;
}
finally
{
if (connection != null)
connection.Close();
}
}
public void insertstudent(ref student s)
{
try
{
connection.Open();
command = new SqlCommand("insert into student values('"+s.Rollno+"','"+ s.Firstname+"','"+s.Lastname+"',"+s.Yearofadmission+",'"+s.Branch+"','"+s.Password+"')", connection);
command.ExecuteNonQuery();
}
catch (Exception)
{
throw;
}
finally
{
if (connection != null)
connection.Close();
}
}
My second function 'insertstudent' to insert value into SQL table is working correctly and inserting the values properly into database table. But the first function 'Update student' is not updating the values in the databse table. Its is not providing any error either.
So where i am wrong?
Thanks in advance!
Check to make sure that rollno passed to the update function is correct. If the command is not throwing error, most likely it's executing correctly and ending up updating nothing because no record hits the supplied rollno.
Put a break point at beginning of update function and check the supplied value of rollno.
Also, roll no. in your insert statement is a subset of 's' whereas in update, its provided separately, you may need to check if that's OK.
Correct way using parameters. You also need to Dispose of your connection and command objects.
using (connection = new SqlConnection("connectionstring"))
{
using (command = connection.CreateCommand())
{
command.CommandText = "update student set FirstName= #FirstName ,LastName= #LastName, YearOfAdmission= #YearOfAdmission, Branch=#Branch WHERE RollNo= #RollNo";
command.Parameters.AddWithValue("#FirstName", stu.FirstName);
command.Parameters.AddWithValue("#LastName", stu.LastName);
command.Parameters.AddWithValue("#YearOfAdmission", stu.YearOfAdmission);
command.Parameters.AddWithValue("#Branch", stu.Branch);
command.Parameters.AddWithValue("#RollNo", stu.RollNo);
connection.Open();
command.ExecuteNonQuery();
connection.Close();
}
}
I am coding a Sql-Server-ce application in C#.
Recently I have been converting my code to use using statements, as they are much cleaner. In my code I have a GetLastInsertedID function which is very simple - it returns the last inserted ID. The working version is as follows:
public static int GetLastInsertedID()
{
int key = 0;
try
{
SqlCeCommand cmd = new SqlCeCommand("SELECT CONVERT(int, ##IDENTITY)", DbConnection.ceConnection);
key = (int)cmd.ExecuteScalar();
}
catch (Exception ex)
{
MessageBox.Show("Could not get last inserted ID. " + ex.Message);
key = 0;
}
return key;
}
Below is the code that does NOT work once I wrap it in using statements:
public static int GetLastInsertedID()
{
int key = 0;
try
{
using (SqlCeConnection conn = new SqlCeConnection(DbConnection.compact))
{
conn.Open();
using (SqlCeCommand cmd = new SqlCeCommand("SELECT CONVERT(int, ##IDENTITY)", conn))
key = (int)cmd.ExecuteScalar();
}
}
catch (Exception ex)
{
MessageBox.Show("Could not get last inserted ID. " + ex.Message);
key = 0;
}
return key;
}
The error that I'm getting is specified cast is not valid. Although this error is usually self-explanatory, I cannot see why I would be getting it inside the second block of code, but not the first. This error occurs on the line key = (int)cmd.ExecuteScalar();.
What am I doing wrong with the second block of code?
From the ##IDENTITY documentation:
##IDENTITY and SCOPE_IDENTITY will return the last identity value generated in any table in the current session.
I think your change now starts a new session for each using statement. Therefore ##IDENTITY is null.
First of all, ##Identity will return any last generated ID from anywhere in SQL Server. Most probably you need to use SCOPE_IDENTITY() instead.
This shows your actual problem and design issue - you need to keep Connection and Command separate. Connection embeds transaction and though SCOPE_IDENTITY() will work until connection is closed; Command can be created, used and disposed.
So you need method which accept connection and use it to obtain identity - something like this (didn't check it but think idea should be clear):
public static int GetLastInsertedID(DbConnection connection)
{
try
{
string query = "SELECT CONVERT(int, SCOPE_IDENTITY())";
using (SqlCeCommand cmd = new SqlCeCommand(query, conn)) {
return (int)cmd.ExecuteScalar();
}
}
catch (Exception ex)
{
MessageBox.Show("Could not get last inserted ID. " + ex.Message);
return 0;
}
}
For working with connection you can create helper method like this:
public static SqlCeConnection OpenDefaultConnection()
{
SqlCeConnection conn = new SqlCeConnection(DbConnection.compact);
conn.Open();
return conn;
}
And use it like this:
...
using (SqlCeConnection conn = OpenDefaultConnection()) {
//... do smth
int id = GetLastInsertedID(conn);
//... do smth
}
...
in my opinion, the reason that it doesn't work is not related to the using statement.
If you use a static class to do the operation of connecting database, like DBHelper. The problem will be caused by that you close the connection of database before you execute the select ##identity and when you execute select ##identity, you open it again. This executing sequence will cause that the return result of select ##identity is NULL. That is, you can not use DBHelper.xxx() twice for getting the automated ID, because every time you call DBHelper.xxx(), the process of the opening database and the closing database will be done.
I have a solution but it maybe not the best one. Instead of using select ##identity, you can use select count(*) from xxx to get the same result.
Hope that it can help you
I have a very simple Update statement that will update mail server settings and network credentials info... Query works fine when I run it in Access but C# keeps giving me the error stating that my SQL Syntax is wrong ... I have a dataaccess layer (dal class) and Update instance method pasted belows ... But the problem must be sth else cuz I have updated lots of stuff this way but this time it just won't do .. any clues will be greatly appreciated. Thx in advance.
Update instance method in DAL class .. (this is supposed to be a Data Access Layer :) I'm just a management graduate :P
public int UpdateRow(string Query, bool isSP, params OleDbParameter[] args)
{
int affectedRows = -1;
using (con = new OleDbConnection(connStr))
{
using (cmd = con.CreateCommand())
{
cmd.CommandText = Query;
if (isSP)
{
cmd.CommandType = CommandType.StoredProcedure;
}
if (args != null)
{
foreach (OleDbParameter prm in args)
{
cmd.Parameters.Add(prm);
}
}
try
{
con.Open();
affectedRows = cmd.ExecuteNonQuery();
}
catch(OleDbException ex)
{
throw ex;
}
catch (Exception ex)
{
throw ex;
}
}
}
return affectedRows;
}
And the ASP.NEt codebehind that will do the updating =
protected void Update_Click(object sender, EventArgs e) {
DAL dal = new DAL();
string upt = string.Format("UPDATE [MailConfig] SET Server='{0}', Username='{1}', Password='{2}', AddressFrom='{3}', DisplayName='{4}'",server.Text,username.Text,password.Text,replyto.Text,displayname.Text);
dal.UpdateRow(upt,false,null);
LoadData();
}
peace!
Trying wrapping your field names in [ ]. I have had problems in the past with certain field names such as a username and password and count, etc, being recognized as reserved words and screwing up the sql giving me an error.
First off - don't use string.Format here. Use parameters, and add parameters to the command. Right now, you are wide open to SQL injection attacks. Think "Bobby Tables".
Re "stating that my SQL Syntax is wrong" - can you please quote the exact error?
First of all, you have no where clause in your Update, so it will update all rows, and violate key constraints causing an error, if you have any.
Second, running that kind of code makes you very vunerable to SQL Injection, if someone enters a username that has a sql command embedded in it, you could lose all your data.
You should use parameterized queries. You specify your parameters in the sql command with #paramname instead of using {4}, and then with the command object do accessCommand.parameters.AddWithValue("#paramname", value)
You are using a CommandType of StoredProcedure, but your query is not a stored procedure name, its a sql query without a where clause.
UPDATE [MailConfig]
SET Server='{0}',
Username='{1}',
Password='{2}',
AddressFrom='{3}',
DisplayName='{4}'"
So you need to remove the command type line, or change it to a correct command type CommandType.Text, and add a Where clause specifying what rows are to be affected.
I don't think Access even has Stored Procedures, so there's no using to use that command type with it.
An example of a command that does use stored procedures would be something like:
string sqlCommString = "QCApp.dbo.ColumnSeek";
SqlCommand metaDataComm = new SqlCommand(sqlCommString, sqlConn);
metaDataComm.CommandType = CommandType.StoredProcedure;
The command string for that type is just the name of the stored proc.