I've tried two different thoughts in resolving this issue without success. I've look at the following links here, here and here but I end up with
Object reference not set to an instance of an object.
or
Procedure or function InsertHero has too many arguments specified.
When I execute the stored procedure in SSMS, my IDs also appear off in that they are incrementing from 1110 instead of the original number set at 100 (this might be a different issue).
It seems like I'm 99% of the way there and I'm missing a small piece. What am I missing?
C#:
using (SqlConnection connection = new SqlConnection(ConnectionString))
{
connection.Open();
using (SqlCommand command = new SqlCommand("InsertHero", connection))
{
command.CommandType = CommandType.StoredProcedure;
command.Parameters.AddWithValue("#Name", hero.Name);
command.Parameters.AddWithValue("#Universe", hero.Universe);
// command.Parameters.Add("#Created_Id", SqlDbType.Int).Direction = ParameterDirection.Output;
int id = (int)command.ExecuteScalar();
}
}
Stored procedure:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[InsertHero]
#Name nvarchar(50),
#Universe nvarchar(15),
#Created_Id int OUTPUT
AS
INSERT INTO Hero
VALUES (#Name, #Universe);
SET #Created_Id = SCOPE_IDENTITY()
GO
Stored procedure (different way):
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[InsertHero]
#Name nvarchar(50),
#Universe nvarchar(15) OUTPUT
AS
INSERT INTO Hero
OUTPUT inserted.Id
VALUES (#Name, #Universe);
GO
EDIT:
Leaving this here in case someone stumbles upon it. Turned out to be a problem with the connection string so be sure to double and maybe triple check
There is no need for the #CreatedID parameter in the first example. Just change your stored procedure to
ALTER procedure [dbo].[InsertHero] #Name nvarchar(50), #Universe nvarchar(15)
as
Insert into Hero values (#Name, #Universe);
SELECT SCOPE_IDENTITY()
When you call ExecuteScalar the result is expected as the return from a SELECT statement not from an output parameter. If you want to use an output paramenter then you need to add it to the SqlCommand parameters collection and read it back its value from the same collection after executing the command.
Also do not use AddWithValue, this method while handy has numerous drawbacks documented in Can we Stop using AddWithValue Already and also in How data access code affects Database performances
Instead use Add specifying type and size of the two parameters
using (SqlConnection connection = new SqlConnection(ConnectionString))
{
connection.Open();
using (SqlCommand command = new SqlCommand("InsertHero", connection))
{
command.CommandType = CommandType.StoredProcedure;
command.Parameters.Add("#Name", SqlDbType.NVarChar, 50).Value = hero.Name;
command.Parameters.Add("#Universe", SqlDbType.NVarChar, 15).Value = hero.Universe;
int id = (int)command.ExecuteScalar();
}
}
The second method should work as well if you change back the #Universe parameter to the same definition of the first example (No output clause).
ALTER procedure [dbo].[InsertHero] #Name nvarchar(50), #Universe nvarchar(15)
Related
I'm trying to insert data into a database using a SQL procedure and an MVC controller. I have the data obtained using a HTML form which is then retrieved by the Create method and added to the parameters of the SQL procedure.
public ActionResult Create([Bind(Include = "UserID,FirstName,Surname,Password,Salt,Phone_Number,Email,IsAdmin")] SaltUsersTable saltUsersTable, FormCollection fc)
{
if (ModelState.IsValid)
{
SqlConnection con = new SqlConnection(#"Connection String");
SqlCommand cmd = new SqlCommand();
cmd.CommandText = "dbo.AddSaltedUser";
cmd.CommandType = CommandType.StoredProcedure;
cmd.Connection = con;
cmd.Parameters.Add("#Password", SqlDbType.NVarChar, 50).Value = Request.Form["Password"];
cmd.Parameters.Add("#FirstName", SqlDbType.NVarChar, 50).Value = Request.Form["FirstName"];
cmd.Parameters.Add("#Surname", SqlDbType.NVarChar, 50).Value = Request.Form["Surname"];
cmd.Parameters.Add("#Email", SqlDbType.NVarChar, 100).Value = Request.Form["Email"];
cmd.Parameters.Add("#PhoneNumber", SqlDbType.NVarChar, 12).Value = Request.Form["PhoneNumber"];
cmd.Parameters.Add("#response", SqlDbType.NVarChar, 250).Direction = ParameterDirection.Output;
if (Request.Form["FirstName"] == "Admin")
{
cmd.Parameters.Add("#IsAdmin", SqlDbType.Bit).Value = 1;
}
else
{
cmd.Parameters.Add("#IsAdmin", SqlDbType.Bit).Value = 0;
}
//Execute the command just established
con.Open();
Int32 rowsAffected = cmd.ExecuteNonQuery();
con.Close();
The code shown above is the Create controller (the connection string I've replaced to be generic)
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[AddSaltedUser]
#FirstName NVARCHAR(50),
#Surname NVARCHAR(50),
#Password NVARCHAR(50),
#PhoneNumber NVARCHAR(12),
#Email NVARCHAR(50),
#IsAdmin BIT,
#response NVARCHAR(250) OUTPUT
AS
BEGIN
SET NOCOUNT ON
DECLARE #salt UNIQUEIDENTIFIER=NEWID()
BEGIN TRY
INSERT INTO dbo.SaltUsersTable (FirstName, Surname , Password, PhoneNumber, Email, IsAdmin)
VALUES(#FirstName, #Surname, HASHBYTES('SHA_512',#Password+CAST(#salt AS NVARCHAR(36))), #PhoneNumber, #Email, #IsAdmin)
SET #response='Success'
END TRY
BEGIN CATCH
SET #response='ERROR'
END CATCH
END
This code is the SQL procedure I wrote which should insert the parameters passed from the MVC controller apart from the salted hash which is done within the procedure.
The issue with this code is that it won't actually insert anything into the database. When I run through the code and have a breakpoint at rowsAffected it shows that it is 0 and I have no idea why. Where am I going wrong in this code?
I should say however that the parameters do exist and the form values are collected properly within the controller as I have gone through it with a debugger.
Could the problem here be the #IsAdmin Parameter?
You're using the datatype SqlDbType.Bit (and the parameter is BIT also) which as far as I'm aware only accepts 0/1/NULL and you're setting the value to true/false. I get that sometimes true/false 0/1 are interchangeable but I'm not sure how C# would pass this and if it's a non issue but it's one thing to check.
Relevant question: DbType equivalent to SqlDbType.Bit
The problem is that you have a SET NOCOUNT ON statement in your stored procedure. From the docs:
Stops the message that shows the count of the number of rows affected by a Transact-SQL statement or stored procedure from being returned as part of the result set.
It does exactly what you told it to do. Comment out that line in the SP and it will return the number of rows affected: 1 in case of a successful insert, 0 if it fails.
As for your rows not inserting...
In your insert you are using HASHBYTES with an invalid algorithm. This results in a null value for all inputs. Try replacing 'SHA_512' in your SP with 'SHA2_512' and it will give you better results.
While this may be responsible for the rows not being inserted, you can't find out with the errors all disabled. TRY...CATCH is generally a bad idea when you're at this point in the code because it suppresses all those handy error messages that might otherwise tell you what went wrong.
I created a test table and uses your code (with the correct hashing algorithm name) and it worked fine for me. I assume that there is a constraint on your table - a unique index on the password field for instance - that is preventing the records from being added.
As a point of interest, you do know that random salt values make this completely unusable right? Even in example code it's better to have your salt represented by an obviously non-production value like 'stand-in the real salt value' or something.
SQL Server is 2014. Visual Studio 2017.
I am trying to pass 3 parameters to SQL Server, but getting this error:
System.Data.SqlClient.SqlException: 'Must declare the scalar variable "#Title1Item".'
I've included both the C# and SQL Server side code. Can anyone tell me what I am doing wrong?
public DataTable GetInventoryByAssetDescription (string Desc, string DispositionText, bool Title1Item) {
DataTable myRecords = new DataTable();
using (SqlConnection cn = new SqlConnection (ConnectionString)) {
// Specify which stored procedure to use and add a parameter.
SqlCommand cmd = new SqlCommand ("GetInventoryByAssetDescription", cn);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue ("#SearchString", Desc);
cmd.Parameters.AddWithValue ("#DispositionText", DispositionText);
cmd.Parameters.AddWithValue ("#Title1Item", Title1Item);
SqlDataAdapter da = new SqlDataAdapter (cmd);
cn.Open();
da.Fill (myRecords);
}
ALTER PROCEDURE [dbo].[GetInventoryByAssetDescription] (
#SearchString varchar(30),
#DispositionText varchar(200),
#Title1Item bit
)
I expect the 3 parameters to be passed through to the stored procedure, but am getting this error on the da.fill (myRecords) line:
System.Data.SqlClient.SqlException: 'Must declare the scalar variable "#Title1Item".'
EDIT: So sorry, guys. Rusty old programmer here. Stored procedure code:
USE [Inventory]
GO
/****** Object: StoredProcedure [dbo].[GetInventoryByAssetDescription] Script Date: 6/4/2019 8:30:15 AM ******/
SET ANSI_NULLS OFF
GO
SET QUOTED_IDENTIFIER OFF
GO
ALTER PROCEDURE [dbo].[GetInventoryByAssetDescription]
(
#SearchString varchar(30),
#DispositionText varchar(200),
#Title1Item bit
)
AS
begin
SET NOCOUNT ON
declare #sql nvarchar (2000)
select #SearchString=UPPER(#SearchString)
set #sql = ' select in_eq_ID,
in_eq_TagNumber as TagNumber,
Title1Item,
in_eq_AssetDescription as Description,
in_eq_ExtendedDescription as ExtendedDescription,
in_eq_SerialNumber as SerialNumber,
in_eq_ValuationAmount as TotalValue,
in_eq_CustodianName as Name,
in_eq_ComplexBuilding as ShortLocation,
in_eq_SubLocationCode as ShortRoomNumber,
in_ca_Categories.in_ca_CategoryName as CategoryName,
in_eq_DispositionDate as DispositionDate,
DATEADD (dd, 0, DATEDIFF (dd, 0, in_eq_Equipment.in_eq_AcquisitionDate)) as AcquisitionDate
from in_eq_Equipment
LEFT JOIN in_ca_Categories ON in_eq_Equipment.in_eq_CategoryID_fk = in_ca_Categories.in_ca_CategoryID
where #Title1Item = Title1Item
AND upper (in_eq_AssetDescription) LIKE upper ('''+ #SearchString + ''')'
set #sql=#sql+' ' + ISNULL(#DispositionText,' ') + ' order by in_eq_AssetDescription'
execute (#sql)
return
end
When you use the datatype BIT in SQLServer as SP parameter, from C# you have to send the values 1 or 0 not true or false so you have to convert bool to int (1 for true and 0 for false)
For example
cmd.Parameters.AddWithValue ("#Title1Item", Title1Item ? 1 : 0);
or
cmd.Parameters.AddWithValue ("#Title1Item", (int)Title1Item);
Right; the problem is your SP, not the C# code. It cannot work in the way written. Parameters and local variables are not "ambient" - they won't be defined inside an EXEC or EXECUTE block, because they are not scoped in there. In some cases there is sp_executesql that allows you to pass through parameters/values by declaring them and adding them as additional parameters, but in your case this would not be a good idea, because fundamentally you are doing something incredibly dangerous, i.e. concatenating inputs to create SQL. Your stored procedure right now is a SQL injection hole and could be used to take over your server with minimal effort. If you're OK with having your data stolen and your database destroyed, you could inline/concatenate #Title1Item just like you did with the others, but: this is a terrible idea and it will hurt you.
I have a form with inputs 'name' and 'phone'.
When they have values, everything works: a record is inserted into the database using a stored procedure, spFoo:
String sp = "spFoo 'test name','test phone'";
OdbcCommand command = new OdbcCommand(sp, connection);
command.CommandType = System.Data.CommandType.StoredProcedure;
connection.Open();
Response.Write(command.ExecuteNonQuery().ToString());
Works on application
Works on Mgmt Studio
But when they don't have values I get -1 as a response in the application:
String sp = "spFoo '',''";
Does not work in application (-1)
Works on Mgmt Studio
I want the user to be able to create a record without any input.
Why does this work in management studio, but not on the application?
Update: I added defaults to the params in the stored procedure, it didn't work; I gave empty strings "NULL" as values in the code, still no luck. Is this a server setting or something that won't allow empty variables?
You'll need to remove the SET NOCOUNT ON; from your stored procedure.
From the documentation:
Stops the message that shows the count of the number of rows affected by a Transact-SQL statement or stored procedure from being returned as part of the result set.
As others have pointed out you should parameterise your query too but the -1 is caused by NOCOUNT being ON.
Edit
I realise it's not what you're asking but to use a parameterised query with ODBC you need to use ?'s as ordinal place holders as per the documentation here. For example:
using (OdbcConnection connection = new OdbcConnection(connectionString))
{
string sp = "{call spFoo (?, ?)}";
using (OdbcCommand command = new OdbcCommand(sp, connection))
{
command.CommandType = System.Data.CommandType.StoredProcedure;
connection.Open();
//the order here is important, the names are not!
command.Parameters.Add("#name", OdbcType.VarChar).Value = "test name";
command.Parameters.Add("#phone", OdbcType.VarChar).Value = "test phone";
Console.WriteLine(command.ExecuteNonQuery().ToString());
}
}
When you are calling a stored procedure from code, you should use the Parameters property on the command. Try this:
String sp = "spFoo";
command.Parameters.Add("#name", "test name");
command.Parameters.Add("#phone", "test phone");
As JimmyV said, you should use the command.Parameters.Add method to setup your parameters, passing in null whenever a parameter value is not specified. To address your comment about the error 'procedure or function 'spFoo' expects parameter '#name', which was not supplied', you'll also need to modify your stored procedure to use default values when a param is not supplied (e.g. null):
CREATE PROCEDURE MyStoredProcedure
#foo int = null
AS
BEGIN
...
END
Sorry for not adding this a comment on the above post. Not enough reputation!
You shouldn't be calling a stored procedure the way that you currently are. You should be using parameters. Your code is susceptible to SQL injection.
Never string concat user inputted values.
What you should have, is a stored procedure setup similarly:
CREATE PROCEDURE spFoo
#name varchar(50) = 'Jim', -- default
#phone varchar(50) = null -- optional
AS
BEGIN
SET NOCOUNT ON;
-- INSERT STATEMENT
END
GO
And then supply the parameters in the code:
string name = this.nameTextBox.Text;
string phone = this.phoneTextBox.Text;
if (string.IsNullOrWhiteSpace(name))
name = null;
if (string.IsNullOrWhiteSpace(phone))
phone = null;
SqlConnection connection = new SqlConnection(#"<connection string>");
using (SqlCommand command = connection.CreateCommand())
{
command.CommandType = CommandType.StoredProcedure;
// leave this as the stored procedure name only
command.CommandText = "spFoo";
// if name is null, then Jim gets passed (see stored procedure definition)
// if phone is null, then null gets passed (see stored procedure definition)
command.Parameters.AddWithValue("#name", name);
command.Parameters.AddWithValue("#phone", phone);
try
{
connection.Open();
int result = command.ExecuteNonQuery();
Console.WriteLine(result);
}
finally
{
if (connection.State != ConnectionState.Closed)
connection.Close();
}
}
I'm not sure why you used the Odbc namespace objects since it sounds like you are using MS-SQL. You should be using objects from the System.Data.SqlClient namespace.
The answer to your actual question would most likely involve executing a script (not a stored procedure) similar to:
DECLARE #RC int
DECLARE #name varchar(50)
DECLARE #phone varchar(50)
-- TODO: Set parameter values here.
EXECUTE #RC = spFoo
#name,
#phone
GO
Which is not recommended.
Stored procedure:
ALTER PROCEDURE VendorsRowcount
#RowCount int OUTPUT
AS
SET NOCOUNT ON
SELECT *
FROM dbo.Vendors
SET #RowCount = ##ROWCOUNT
RETURN #RowCount
C#:
using (var conn = new SqlConnection("Data Source=.\\SQLEXPRESS;Initial Catalog=Pricer;Persist Security Info=True;User ID=xxx;Password=xxx"))
using (var command = new SqlCommand("VendorsRowcount", conn)
{
CommandType = CommandType.StoredProcedure
})
{
conn.Open();
command.ExecuteNonQuery();
conn.Close();
}
I am getting the error:
Additional information: Procedure or function 'VendorsRowcount' expects parameter '#RowCount', which was not supplied.
I am just learning C# after setting out to learn VB and realizing that there are a lot more resources on the internet for C#.
This is probably a stupid question, but I have searched and maybe the terms I use are not the correct ones, because I can not find an answer.
To the best of my knowledge, I don't need to send a parameter because #RowCount is output.
Why do I get this error?
If you declare a parameter in the stored procedure, it has no relevance the fact that is declared as OUTPUT. You need to pass it from your C# code. The alternative is to declare the parameter as optional as shown in another answer. However you have now a problem. How do you read back in your C# code that parameter's value?
First option, pass the parameter at the stored procedure and read it back
conn.Open();
SqlParameter prm = command.Parameters.Add(new SqlParameter("#RowCount", SqlDbType.Int));
prm.Direction = ParameterDirection.Output;
command.ExecuteNonQuery();
Console.WriteLine(prm.Value.ToString());
conn.Close();
Second option, set the parameter as optional, call the SqlCommandBuilder.DeriveParameters method to fill the Parameter collection on the C# side and read it back. (Please read the remarks section in the link provided about the efficiency of this solution)
-- in the stored procedure
#RowCount int = 0 OUTPUT
conn.Open();
SqlCommandBuilder.DeriveParameters(command);
command.ExecuteNonQuery();
Console.WriteLine(command.Parameters["#RowCount"].Value.ToString());
conn.Close();
However I am puzzled by the fact that you run a potentially costly SELECT * command but you don't seems to be interested in the records returned.
In this context, a StoredProcedure seems to be excessive and adds a maintenance issue while you could get the row count simply writing:
conn.Open();
command.CommandText = "SELECT COUNT(*) FROM Vendors";
int rowCount = Convert.ToInt32(command.ExecuteScalar());
Console.WriteLine(rowCount.ToString());
conn.Close();
You need to pass in that parameter. Here's a good example of how to do this:
Get output parameter value in ADO.NET
If the parameter should be optional, you must provide a default value in your Stored Procedure.
For example:
#RowCount int OUTPUT = 0
Why if I have this stored procedure created with an output parameter, I'm getting the following error:
sp_DTS_InsertLSRBatch expects parameter #ErrorMsg which was not
supplied
Stored procedure code:
set ANSI_NULLS ON
set QUOTED_IDENTIFIER ON
go
ALTER PROCEDURE [dbo].[sp_DTS_InsertLSRBatch]
#LSRNbr varchar(10),
#BatchNbr varchar(10),
#ErrorMsg varchar(20) output
AS
BEGIN
SET NOCOUNT ON;
if not exists(select *
from tblDTS_LSRBatch (nolock)
where LSRNbr=#LSRNbr and BatchNbr=#BatchNbr)
begin
-- check if BatchNbr exists under another LSR
-- if not add (LSR, BatchNbr) else error
if not exists(select *
from tblDTS_LSRBatch (nolock)
where BatchNbr=#BatchNbr)
insert into tblDTS_LSRBatch (LSRNbr,BatchNbr) values (#LSRNbr, #BatchNbr)
else
set #ErrorMsg = 'Batch dif LSR'
end
END
C# code:
SqlConnection conn = new SqlConnection(ConnStr);
try
{
conn.Open();
for (int i = 0; i <= lbxBatch.Items.Count - 1; i++)
{
SqlCommand cmd = new SqlCommand("sp_DTS_InsertLSRBatch", conn);
cmd.Parameters.Add(new SqlParameter("#LSRNbr", txtLSR.Text));
cmd.Parameters.Add(new SqlParameter("#BatchNbr", lbxBatch.Items[i].ToString()));
//Output parameter "ErrorMsg"
SqlParameter pErrorMsg = new SqlParameter("#ErrorMsg", SqlDbType.VarChar, 20);
pErrorMsg.Direction = ParameterDirection.Output;
cmd.CommandType = CommandType.StoredProcedure;
cmd.ExecuteNonQuery(); <--- ERROR
In your code you haven't added the pErrorMsg parameter. Add this line:
cmd.Parameters.Add(pErrorMsg);
Moreover, in your Stored Procedure, you must set #ErrorMsg sql output variable to an appropriate value like an empty string or double double-quotes ("") in the if condition parts of your SP code,as a good coding practice.
You're creating the pErrorMsg parameter, but where are you adding it to your command?