Stored procedure calling, a weird situation i'm facing - c#

Using Visual Studio 2008, C#
For the past 2 days I'm facing a problem so I thought I would ask here.
I have this stored procedure in my database called StoredProcedure1:
ALTER PROCEDURE dbo.StoredProcedure1
#ID int,
#Username varchar(10),
#Pass varchar(10),
#Status varchar(10)
AS
INSERT INTO tPasswords(ID, fUsername, fPassword, fStatus)
Values (#ID, #Username, #Pass, #Status)
RETURN
I am simply adding a row into my table.
When I execute the procedure from the server explorer inside visual studio everything runs smoothly, and when I see the data into my database I see the new row I just added.
The problem is when I'm trying to run the procedure inside my program.
So for test inside an empty form I created a button and :
using System.Data.Sql;
using System.Data.SqlClient;
private void button1_Click(object sender, EventArgs e)
{
string a = "Andreas";
string b = "Andreas2";
string c = "Andreas3";
int _id = 10;
SqlConnection connection = new SqlConnection(
Properties.Settings.Default.dPasswordsConnectionString);
SqlCommand cmd = new SqlCommand(
"StoredProcedure1", connection);
connection.Open();
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandTimeout = 600;
cmd.Parameters.Add("#ID", _id);
cmd.Parameters.Add("#Username", a);
cmd.Parameters.Add("#Pass", b);
cmd.Parameters.Add("#Status", c);
int i = cmd.ExecuteNonQuery();
connection.Close();
}
When I press this button I don't have any errors or anything, when I press it twice it causes an error about the same primary key, so the procedure is working.
But when I go to my database I don't see the new data been inserted.
That's my problem actually, I can't write to my database from within my program.

you are trying to set the primary key in your stored procedure, i am guessing.
you should let SQL set it for you.
I am guessing #ID is your primary key... remove that code, and the code from your proc where you are inserting the value into the row.
you should make sure your column definition is set to auto-generate these for you.

Related

Return newly created stored procedure C#

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)

Why does rowsAffected return 0 when I have passed parameters for it to use?

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.

Unable to open connection using a "using var parameterized queries"

I have this scenario:
I have a table called "Entradas" and one called "Saidas". Both have
the column "data" and "hora". With that in mind, since the "data" and
"hora column of "entradas" mean that a car joined at that date and
time, and the values from the columns "data" and "hora" of "saidas"
are mean to be inserted as i click a button, inserting the current
date and time. I'm requesting a sql syntax that could insert the
values "data" and "hora" into the table "saidas" and update a value of
"entradas" called "sai" which is equal to 0 and i want it to change to
1 on button press.
And i have this procedure:
CREATE DEFINER=`root`#`localhost` PROCEDURE `entradas_sai`(
IN ID_VEICULO VARCHAR(45), OUT retcode INT)
BEGIN
DECLARE _rollback BOOL DEFAULT 0;
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SET _rollback = 1;
START TRANSACTION;
INSERT INTO SAIDAS(data, hora) VALUES(date(now()) ,time(now()));
UPDATE ENTRADAS SET SAI=1;
IF '_rollback' THEN
SET retcode = 0;
ROLLBACK;
ELSE
SET retcode = 1;
COMMIT;
END IF;
END
And this is my actual code to run it on button click:
private void cmdSaida_Click(object sender, EventArgs e)
{
using (var cn = new MySqlConnection("server=localhost;user id=root;password=12345;persistsecurityinfo=True;database=portaria;allowuservariables=True"))
{
cn.Open();
MySqlCommand cmd = new MySqlCommand();
cmd.CommandText = "entradas_sai";
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("#ID_veiculo", Grid1.SelectedCells[1].Selected);
cmd.Parameters["#id_veiculo"].Direction = ParameterDirection.Input;
cmd.Parameters.AddWithValue("#retcode", MySqlDbType.Int32);
cmd.Parameters["#retcode"].Direction = ParameterDirection.Output;
cmd.ExecuteNonQuery();
var res = cmd.Parameters["#retcode"].Value;
cn.Close();
}
}
And my problem at the moment is that when i click the button, it doesn't execute my procedure neither my commands to open or close the connection. Any sugestions?
EDIT: After some research i found that my actual procedure sets all the values from 0 to 1 in column "sai" how can i do it so it only change the selected grid row value, based on "ID_Entrada"
You have a MySqlCommand object, but it's not associated with any connection. There has to be an active connection in order to execute a command.
You can associate it on the constructor:
MySqlCommand cmd = new MySqlCommand("entradas_sai", cn);
or after the constructor:
cmd.Connection = cn;

Quote default values in create table SQL Server command

I want to write a C# method that creates a table and adds a column. The default values for the column is given as argument to the method. What is the safest way to quote the default value in the SQL command?
The code would be something like this:
var defaultValueThatOriginatesFromAnEvilSource = "'; DROP TABLE #Entities; SELECT '"; // the argument
database.RawExecute(string.Format("create table #Entities (somecolumn int default {0})", defaultValueThatOriginatesFromAnEvilSource));
Would a simple replace of the single quotes in the argument take care of all types of injection? Or is there a special method in C# or SQL I should use for this?
One thing to note, is that I create a temp table, and I need that to be accessible to the the next SQL statement (using the same connection).
As what shA.t said you can do this by sqlcommand in C# though it's quite hardwork if you'll be having multiple columns
but here's how to do it using stored procedure in SQL and sqlcommand in C#
in SQL
CREATE PROCEDURE sp_CreateTable
#TableName VARCHAR(50),
#ColumnName VARCHAR(50)
AS
BEGIN
DECLARE #SQLCreate VARCHAR(MAX)
SET #SQLCreate ='CREATE TABLE ' + #TableName
+ '('+#ColumnName+')'
EXEC (#SQLCreate)
END
GO
and in C#
add this using directives
using System.Data;
using System.Data.Client;
SqlConnection con = new SqlConnection("your connection string here");
con.Open();
SqlCommand cmd = new SqlCommand("sp_CreateTable");
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add("#TableName", SqlDbType.VarChar).Value = "someName";
cmd.Parameters.Add("#ColumnName", SqlDbType.VarChar).Value = "someColumnName";
cmd.ExecuteNonQuery();
con.Close();

MS-SQL Stored Procedure doesn't work with empty params

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.

Categories