Using:
Visual Studio 2010
SQL Server 2012
I have a stored procedure, which inserts a new record in the DB and returns the new record ID as an output parameter. I ran the sp manually and it works.
If I run it from my C# application, and I read the output parameter afterwards, C# reads a *, instead of the number.
I changed the sp to write the output parameter to a table before outputting it. I always get the correct record ID in this table.
The C# code that reads the output parameter is used (without any changes) in several other applications and works. It is used for other sp's in this application and it works. I've added the code nevertheless:
public string SpOutputParameter(string sSpName, SpParameter[] oParam, bool
bKeepConnectionOpen = false)
{
// Set return value to -1
int iReturnValue = -1;
// SP Command
SqlCommand Cmd = new SqlCommand(sSpName, this.Db); // Command (exec sp)
Cmd.CommandType = CommandType.StoredProcedure; // Type of command
try // Try to get results
{
// Add the parameters
this.AddParameters(oParam, Cmd);
this.AddReturnValue(Cmd);
// Get the results
this.OpenDatabase();
Cmd.ExecuteNonQuery();
if (!bKeepConnectionOpen)
this.Db.Close();
// Get the return value
iReturnValue = GetReturnValue(Cmd);
// If the sp fails, throw an exception (to be caught)
if (iReturnValue != 0)
throw new Exception("The database returned a return value of " + Convert.ToString(iReturnValue != 0));
// Get the output parameter to return
foreach (SqlParameter parameter in Cmd.Parameters)
{
if (parameter.Direction == ParameterDirection.Output ||
parameter.Direction == ParameterDirection.InputOutput)
return Convert.ToString(parameter.Value);
}
}
catch (Exception Ex)
{
// Edit the message, rethrow exception
throw new Exception(
"Failed to run sp '" + sSpName + "'",
Ex);
}
finally // Dispose of used objects
{
// Dispose the command
Cmd.Dispose();
Cmd = null;
}
// If the code gets here, there was no output parameter.
// return null...
return null;
}
When I debug, I see the value of the parameter as a * on the parameter.Value property. ('return Convert.ToString(parameter.Value);' line)
At the moment my app doesn't work, I need to get the value of the output parameter. Can someone please help me figure out why I get a * (in C#) instead of the actual output parameter value?
Thank you!
Based on your explanation that your stored procedure is inserting a new record and returning that value, and the fact that your return type is string, I am going to guess that your output parameter is a char or varchar, and you do something like this:
SET #VarCharParameter = SCOPE_IDENTITY();
In which case, if your char/varchar is not large enough to store the int, it will become *, e.g.
SELECT CONVERT(CHAR(2), 10000);
The solution to this is to use the correct types. If you are looking to return an integer, then use an INT parameter.
It seems to be some types converting issue. Try just casting like:
return (string)parameter.Value;
Since stored procedure being used is not shown here, kindly make sure that OUTPUT keyword is used in StoredProcedure with the parameters required to be sent back for C#
e.g. > #outputParameter Varchar(100) OUTPUT
Also while adding SQL parameters to cmd object in your C# code, check that the direction is set to output
e.g.SqlParameter OutputParam = new SqlParameter("#OutputParam", SqlDbType.VarChar);
OutputParam.Direction = ParameterDirection.Output;
cmd.Parameters.Add(OutputParam);
At Last, try to close database connection (this.Db.Close()) after you have got everything you need from the cmd object.
Related
I was creating a query for the Table Adapter that looked like this
SELECT COUNT(*) FROM Trip WHERE ShipCode = #ShipCode
AND Date < #TodayPlusWeek
The thing is this method will expect (string, string) as parameter where I need it to accept int for the (ShipCode) and Date for the (Date)
Any ideas how to do that?
Very much appreciated!
The first part will be to set up a pair of variables of the proper type to hold the correct values for the Sql Parameters:
int iShipCode;
DateTime dtTodayPlusWeek;
Once you have that, we are going to run a TryParse method on your current string variables to convert into our new variables. As I did not know the variable names within your method, I generic'd them to (strShipCode, strTodayPlusWeek). If the TryParse method fails, you can set up default values for the query; I used 0 and Today + 1 week.
if (!int.TryParse(strShipCode, out iShipCode)) {
iShipCode = 0;
}
if (!DateTime.TryParse(strTodayPlusWeek, out dtTodayPlusWeek)) {
dtTodayPlusWeek = DateTime.Now.AddDays(7);
}
Now that we have correct types for the data, we can add that to our existing query as parameters.
StringBuilder sbCmd = new StringBuilder();
sbCmd.Append("SELECT COUNT(*) ");
sbCmd.Append("FROM Trip ");
sbCmd.Append("WHERE (ShipCode = #ShipCode) ");
sbCmd.Append("AND Date < #TodayPlusWeek");
string txtCmd = sbCmd.ToString();
int iQryReturn;
using (SqlCommand cmd = new SqlCommand(txtCmd, conn)) {
cmd.CommandType = CommandType .Text;
cmd.Parameters.AddWithValue("#ShipCode", iShipCode;);
cmd.Parameters.AddWithValue("#TodayPlusWeek", dtTodayPlusWeek);
try {
conn.Open();
iQryReturn = (int)cmd.ExecuteScalar();
}
catch(Exception ex) {
iQryReturn = -1;
// Your Exception handling here
}
finally {
conn.Close();
// Your cleanup code if any
}
}
Not knowing what type of DB setup you have in use, I have written this in ADO for Sql Server. Ad there will only be value returned; the count(*), I have added an integer variable for a return. If there is an error, that value will be set to -1.
It is up to you on implementation and syntactical changes that may be needed for your particular application.
I will start by saying that I know this exact question has been asked before but it was incorrectly marked as a duplicate question and never answered.
The first key point about this question is that the InvalidCastExcpetion is thrown, before attempting to use the return from the stored procedure. So all of the suggested solutions, which are to deal with that are not applicable. It fails on the line that executes the SQL command.
The second key point is that the exact same code works successfully when connecting as root but not when connecting as a different user. Therefore, it is not a problem with the code that can be solved with any of the previously given solutions.
Does anyone have any ideas why this would work for the root user but not for another and why the message in the exception has nothing to do with privileges? By the way, the user in question has execute privileges and can call this procedure from within MySQL Workbench.
I do not think the code is particularly relevant, given that it works for root but I am including it anyway.
My C# code is as follows:
using (MySqlConnection conn = new MySqlConnection(SqlConnectionString)) {
using (MySqlCommand cmd = conn.CreateCommand()) {
try {
conn.Open();
cmd.CommandType = System.Data.CommandType.StoredProcedure;
cmd.CommandText = #"open_application";
cmd.Parameters.AddWithValue("?p_opr_id", organisationID);
cmd.Parameters.AddWithValue("?p_app_id", applicationID);
MySqlParameter returnValue = new MySqlParameter();
returnValue.Direction = System.Data.ParameterDirection.ReturnValue;
returnValue.MySqlDbType = MySqlDbType.Int32;
cmd.Parameters.Add(returnValue);
cmd.ExecuteNonQuery();
//Deal with the return value
} catch (Exception ex) {
message = "Failed to record application opening: " + ex.Message;
}
}
}
The InvalidCastException is thrown from cmd.ExecuteNonQuery();
The MySQL stored function is as follows:
DELIMITER $$
DROP FUNCTION IF EXISTS `open_application` $$
CREATE FUNCTION `open_application`(p_opr_id int, p_app_id int) RETURNS tinyint
BEGIN
DECLARE v_opened int;
DECLARE cur CURSOR FOR SELECT opened FROM operator_application WHERE opr_id = p_opr_id AND app_id = p_app_id;
OPEN cur;
FETCH cur INTO v_opened;
CLOSE cur;
IF v_opened = 1 THEN
RETURN 0;
END IF;
UPDATE operator_application SET opened = 1 WHERE opr_id = p_opr_id AND app_id = p_app_id;
RETURN 1;
END $$
DELIMITER ;
** UPDATE **
I have rewritten the stored function as a stored procedure. Instead of using RETURN, I am using SELECT and then invoking the stored procedure using MySQLCommand.ExecuteScalar, instead of ExecuteNonQuery. It is now working. I still have no idea why it wasn't working as a function.
I think that the error you get does not represent the real problem.
There are two possible ways that InvalidCastException is raised while executing ADO command.
Input parameter type missmatch. If any of input parameter values (either organisationID or applicationID) cannot be converted to desired sql type the exception is raised
Result/Output parameter type mismatch. If value returned by sql function (or assigned to output parameter) cannot be converted to result type (Int32 in you case) the exception is raised
I suspect that when connecting as non-root user, access is denied to some table/view/cursor and your sql function raises error and no RETURN statement is executed. This way you will get NULL value in output parameter and the exception.
This is a blind guess but maybe there is problem with cursor rights (see here).
Using SQL Server 2008, WinForms C# .NET 4.5, Visual Studio 2012.
I have a query that currently updates a table with some information from a GridView.
Below is the code that calls the stored procedure:
public void UpdateMain(string part, int? pareto)
{
try
{
using (SqlConnection AutoConn = new SqlConnection(conn32))
{
AutoConn.Open();
using (SqlCommand InfoCommand = new SqlCommand())
{
using (SqlDataAdapter infoAdapter = new SqlDataAdapter(InfoCommand))
{
InfoCommand.Connection = AutoConn;
InfoCommand.CommandType = CommandType.StoredProcedure;
InfoCommand.CommandText = "dbo.updateMain";
InfoCommand.Parameters.AddWithValue("#part", part);
InfoCommand.Parameters.AddWithValue("#pareto", pareto);
InfoCommand.CommandTimeout = 180;
InfoCommand.ExecuteNonQuery();
}
}
}
}
catch (Exception e)
{
//MessageBox.Show("Error in connection :: " + e);
}
}
And here's the SQL:
ALTER PROCEDURE [dbo].[updateMain]
#part varchar(255),
#Pareto int
as
UPDATE dbo.ParetoMain
SET NewPareto = #Pareto
WHERE Part = #part
Nothing fancy as you can see. The problem I have is the Newpareto doesn't have to have a value, so I need it to allow nulls. I made sure the table allows nulls. And in my C# code I made sure to use nullable int, but when I run the code I get the error:
Exception:Thrown: "Procedure or function 'updateMain' expects parameter '#Pareto', which was not supplied." (System.Data.SqlClient.SqlException)
A System.Data.SqlClient.SqlException was thrown: "Procedure or function 'updateMain' expects parameter '#Pareto', which was not supplied."
So how do I stop this error and get the null into the table?
The problem is that the parameter is expected, but not added if the nullable value is null. You need to address this by either:
Manually setting it do DBNull.Value as in: InfoCommand.Parameters.AddWithValue("#Pareto", (object)pareto ?? DbNull.Value);
Or by making the parameter optional as in: #Pareto int = null
Your stored procedure could look like this if you want to make the parameter optional:
ALTER PROCEDURE [dbo].[updateMain]
#part varchar(255),
#Pareto int = null
as
UPDATE dbo.ParetoMain SET NewPareto =#Pareto WHERE Part = #part
EDIT
I take it from the accepted answer that you need to cast to object due to type mismatch problems. I'm fixing my answer for the sake of completeness.
use
InfoCommand.Parameters.AddWithValue("#Pareto", (Object)pareto ?? DBNull.Value);
Try this:
if (pareto != null)
{
InfoCommand.Parameters.AddWithValue("#pareto", pareto);
}
else
{
InfoCommand.Parameters.AddWithValue("#pareto", DBNull);
}
I am trying to run a stored procedure and for some reason it keeps telling me "Specified cast is not valid". the "hidSelectedExpenseIDs" is a hidden field that gets populated with a javascript array of id's.
Example: the "hidSelectedExpenseIDs.Value" would look like "123,124,125,126". Hence why I have the .Split(',') in there.
Here is my code:
public void hasExhistingExpenseInvoice()
{
string[] Expenses = hidSelectedExpenseIDs.Value.Split(',');
//check if there is an existing invoice. Then report back to the user so the
//user knows if he/she has to check overwrite option.
bool invoiceExists = false;
foreach (var expense in Expenses)
{
var connection = new SqlConnection(ConfigurationManager.ConnectionStrings["OSCIDConnectionString"].ToString());
var command = new SqlCommand("p_CaseFiles_Expenses_InvoiceExhists", connection);
command.Parameters.Add(new SqlParameter("#ExpenseID", SqlDbType.Int));
command.Parameters["#ExpenseID"].Value = Convert.ToInt32(expense);
command.CommandType = CommandType.StoredProcedure;
try
{
connection.Open();
invoiceExists = (bool)command.ExecuteScalar();
if (invoiceExists)
{
//previous invoice exhists
Warning1.Visible = true;
Warning1.Text = "There is an exhisting Invoice.";
}
}
catch (SqlException sql)
{
lblStatus.Text = "Couldn't connect to the Database - Error";
lblStatus.ForeColor = System.Drawing.Color.Red;
}
catch (Exception ex)//catches exception here
{
lblStatus.Text = "An error occured";
lblStatus.ForeColor = System.Drawing.Color.Red;
}
finally
{
if (connection.State == ConnectionState.Open)
connection.Close();
}
}
}
this is my stored procedure:
ALTER PROCEDURE dbo.[InvoiceExhists]
#ExpenseID int
AS
BEGIN
SELECT InvNumber FROM dbo.Expenses from ExpID = #ExpenseID
END
The logic is faulty.
Your Query returns a number, and you are trying to cast it directly to a Boolean, this can't be done in C#.
Some languages will interpret any non-zero as true, it is not the case for C# and it will throw an exception.
You will need to compare the returned value.
In this case, you should just check if there is a value, because NULL will be returned if the invoice does not exist.
This would look like this :
invoiceExists = command.ExecuteScalar() != null ;
Also I recommend reading this thread and consider using UDF instead of scalar Stored Procedures.
change your stored procedure .This fits your requirement
ALTER PROCEDURE [dbo].[InvoiceExhists]
#ExpenseID int
AS
BEGIN
if exists(select * Expenses where ExpID = #ExpenseID)
select 1
else
select 0
END
The exception is likely caused by invoiceExists = (bool)command.ExecuteScalar(); considering its the only casting that happens within the try statement. You need to look at the return result of ExecuteScalar() to solve your problem.
Looking on some source code I've inherited, there's a snippet of code that calls a SQL stored procedure making an Update.
The stored procedure returns -1 in case something goes wrong:
IF ##Error <> 0
BEGIN
ROLLBACK TRAN
SELECT -1
RETURN
END
COMMIT TRAN
SELECT 0
The C# code is something like this:
System.Data.SqlClient.SqlDataReader myReader;
try{
SqlDbConnection.Open();
SqlDbCommand.Connection = SqlDbConnection;
SqlDbCommand.CommandType = System.Data.CommandType.StoredProcedure;
SqlDbCommand.CommandText = "StoredProcedured_UpdateFoo";
SqlDbCommand.Parameters.Clear();
SqlDbCommand.Parameters.Add("#FooData", SqlDbType.DateTime); SqlDbCommand.Parameters["#FooData"].Value = System.DateTime.Now.ToString("yyyy-MM-dd");
myReader = SqlDbCommand.ExecuteReader();
if (myReader.Read())
{
if (int.Parse(myReader.GetValue(0).ToString()) == -1) throw new ErrorDataTxRx("Error FOO ");
}
} finally {
if (SqlDbConnection.State != ConnectionState.Closed){
SqlDbConnection.Close();
}
if (myReader != null){
if (!myReader.IsClosed) myReader.Close();
}
}
I also see part of the same code, checking the same thing using System.Data.DataSet() with Fill method.
Is there a more elegant way to check if the returned value is -1?
Is it OK to use an ExecuteReader in this case?
Have you tried using ExecuteScalar? That's designed for queries which return a single value:
Executes the query, and returns the
first column of the first row in the
result set returned by the query.
Additional columns or rows are
ignored
As Jon correctly noted, you're actually not using the return value of the SP, but actually getting the first value of the first row, which is what ExecuteScalar would do in a simpler way.
However, in order to get the return value from the SP (e.g. from something like RETURN #i; in the SP SQL code), you need to add a new Parameter and set its direction to ReturnValue, something like this:
SqlParameter returnValueParam = new SqlParameter();
returnValueParam.DbType = DbType.Int32;
returnValueParam.IsNullable = false;
returnValueParam.Direction = ParameterDirection.ReturnValue;
SqlDbCommand.Parameters.Add(returnValueParam);
// execute SP here...
int returnValue = (int)returnValueParam.Value;
Use ExecuteNonQuery instead of the reader and you will get the number of rows affected.
Your question is not clear enough. You may need to do it with a ReturnValue like Lucero has written.