I have created a stored procedure for SQL Server 2014.
There are two parameters: Name which is a user name and Hash which is password md5 hash. I check in the database if the md5 hashes are equal (first hash is from the program and the second one is already stored in the database).
If I just run a query (not a stored procedure) in the database (or in program using commandType.Text) - everything works and the user is being selected, but when I run the exact thing but using stored procedures, the SqlReader in C# has no elements returned, which most likely means that the conditions during those variable comparison were not met.
Maybe I am doing something wrong?
I also have about 10 other stored procedures for reading or/and writing to the database, everything works except this one.
Here is the procedure:
CREATE PROCEDURE GetHash
#Name nvarchar(50),
#Hash nvarchar(200)
AS
BEGIN
SET NOCOUNT ON;
SELECT Orders.orderId, Employee.name, Employee.surname
FROM Orders
LEFT JOIN Employee ON Orders.orderId = Employee.id
WHERE batchName = '#Name' AND productCode = '#Hash'
END
GO
Code part:
public Boolean VerifyPassword(string name, string password)
{
var paramsList = new List<SqlParameter> { new SqlParameter("#Name", name), new SqlParameter("#Hash", GetMd5Hash(password)) };
const string ProcedureName = "GetHash";
var ActiveUser = new DBController().GetFromDatabase(ProcedureName, "Login", "EJL15_DB", paramsList).ToList();
return ActiveUser.Count > 0;
}
And from Database Controller
private void SetCommandProperties(string procedureName, IEnumerable<SqlParameter> paramsList)
{
this.sqlCommand.CommandText = procedureName;
this.sqlCommand.CommandType = CommandType.StoredProcedure;
foreach (var curParam in paramsList)
this.sqlCommand.Parameters.Add(curParam);
this.sqlCommand.CommandTimeout = 15;
}
You don't need to quote the parameters in the stored procedure. Do this instead:
CREATE PROCEDURE GetHash
#Name nvarchar(50),
#Hash nvarchar(200)
AS
BEGIN
SET NOCOUNT ON;
SELECT Orders.orderId,
Employee.name,
Employee.surname
FROM Orders
LEFT JOIN Employee
ON Orders.orderId=Employee.id
WHERE batchName = #Name
AND productCode = #Hash
END
I just wonder, obviously your #Hash parameter passed to the stored
procedure is a user's password. But for some reason your WHERE clause
in the procedure goes like that:
"WHERE batchName='#Name' AND productCode='#Hash'"
Is there a chance your condition is incorrect? I guess it should be something like: Employee.password = #Hash
You should not put '' around your variables. Otherwise your comparison is totally wrong.
Related
I have a Windows application written in C# using embedded firebird sql version 2.5.5.26952, which I am re-working it to update to embedded firebird sql version 4.0.0.2496. I have update the fdb file to the new version,and all the tables and sprocs, are there. When run a cmd.Fill() command for a selected statement rows are returned, if I do a update for a row in the table, I get the expected results back fine. but If I do a insert nothing is returned, and no errors are thrown, but the data is added to the database. If I run the sproc from the FireRobin application, the data is inserted, and a row is returned, so I'm at a loss to know why it is not working from my C# application. below is slimmed down version of the code.
The 2.5 version is using FirebirdSql.Data.FirebirdClient.4.10.0.0
The 4.0 version is using FirebirdSql.Data.FirebirdClient.9.0.2
using (var cmd = new FbDataAdapter("PROC_UPSERTPEOPLE", _connection)
{
DataTable data = new DataTable();
cmd.SelectCommand.Parameters.Add("SURNAME", FbDbType.Text).Value = item.Surname;
cmd.SelectCommand.Parameters.Add("FORENAMENAME", FbDbType.Text).Value = item.Forename);
var transaction = _connection.BeginTransaction();
cmd.SelectCommand.CommandType = CommandType.StoredProcedure;
cmd.SelectCommand.Transaction = transaction;
var result = cmd.Fill(data);
transaction.Commit();
}
On a update result contains 1, and data has the expected result, but on a insert result = 0, and data has no rows in.
Any help would be appreciated.
This is the simple version fo the sproc in question
CREATE OR ALTER PROCEDURE PROC_UPSERTPEOPLE_SLIM
(
RECID INTEGER,
SURNAME VARCHAR(100),
FORENAME VARCHAR(100)
)
RETURNS
(
ID INTEGER,
LSURNAME VARCHAR(100),
LFORENAME VARCHAR(100)
)
AS
DECLARE VARIABLE local_id integer;
DECLARE VARIABLE local_surname varchar(100);
DECLARE VARIABLE local_forename varchar(100);
BEGIN
select
ID,
FORENAME,
SURNAME
FROM
APA_PEOPLE
WHERE
(:RECID IS NOT NULL AND ID = :RECID)
OR (:RECID IS NULL
AND FORENAME = :FORENAME
AND SURNAME = :SURNAME)
INTO
:local_id,
:local_forename,
:local_surname;
IF (:local_id IS NULL) then
begin
UPDATE OR INSERT INTO APA_PEOPLE(FORENAME, SURNAME)
VALUES(:FORENAME, :SURNAME)
MATCHING (FORENAME, SURNAME);
end
else
begin
UPDATE APA_PEOPLE SET FORENAME = :FORENAME,
SURNAME = :SURNAME
WHERE ID = :local_id;
end
FOR
SELECT
ID,
SURNAME,
FORENAME
from
APA_PEOPLE
WHERE
(:RECID IS NOT NULL AND ID = :RECID)
OR (:RECID IS NULL
AND FORENAME = :FORENAME
AND SURNAME = :SURNAME)
INTO
:ID,
:LSURNAME,
:LFORENAME
DO
begin
suspend;
end
END;
Update
To answer my own question, being mainly a TSQL developer, DSQL seems strange, change the sproc to the following, which is simpler
CREATE OR ALTER PROCEDURE PROC_UPSERTPEOPLE_SLIM (
RECID integer,
SURNAME varchar(100),
FORENAME varchar(100)
)
RETURNS (ID integer)SQL SECURITY INVOKER
AS
BEGIN
UPDATE OR INSERT INTO APA_PEOPLE(FORENAME, SURNAME)
VALUES(:FORENAME, :SURNAME)
MATCHING (FORENAME, SURNAME)
RETURNING ID INTO :ID;
END;
but also had to change the way it was called, to use
EXECUTE PROCEDURE PROC_UPSERTPEOPLE_SLIM(#RECID, #SURNAME, #FORENAME)
This does seem counter intuitive, I had assumed a stored procedure was a stored procedure, and there are not two different flavors. Oh well it works now, so move on to getting the rest of the app to work.
I am calling a stored procedure to create and hash a new password. It is being called from a .NET/C# WebAPI controller.
_sqlDataContext.ExecuteCommand("[Users].[SetNewPassword]", new List<SqlParameter>
{
new SqlParameter("#UserId", SqlDbType.UniqueIdentifier) { Value = user.Id },
new SqlParameter("#Password", SqlDbType.NVarChar, 256) { Value = command.Password }
});
userId and command.Password are a GUID and simple string respectively.
The stored procedure being called hashes and stores the password as follows:
ALTER PROC [Users].[SetNewPassword]
#UserId uniqueidentifier,
#Password NVARCHAR(256) -- Unsalted and unhashed
AS
BEGIN TRANSACTION [Transaction1]
BEGIN TRY
DECLARE #PasswordSalt uniqueidentifier = NEWID();
DECLARE #SaltedPassword binary(64);
SET #SaltedPassword = HASHBYTES('SHA2_512', #Password + CAST(#PasswordSalt as NVARCHAR(256)));
-- Insert new password
INSERT INTO Passwords (UserId, Password, PasswordSalt, UpdatedDate)
VALUES (#UserId, #SaltedPassword, #PasswordSalt, GETDATE());
COMMIT TRANSACTION [Transaction1]
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION [Transaction1]
PRINT ERROR_MESSAGE()
END CATCH
When I call this from the WebAPI / C# method, I get a different hash than when I execute the stored procedure directly in SQL Server Management Studio.
Furthermore, if I execute the SetPassword stored procedure from C#, the following stored procedure - ValidatePassword - to validate a password fails.
This is because the hash stored in the database does not match the hash being generated by ValidatePassword
ALTER PROC [Users].[ValidatePassword]
#UserId uniqueidentifier,
#Password NVARCHAR(256)
AS
BEGIN
DECLARE #PasswordHash binary(64) = HASHBYTES('SHA2_512', (SELECT #Password + CAST((SELECT p.PasswordSalt FROM Passwords p WHERE p.UserId = #UserId) AS NVARCHAR(256))))
SELECT
CASE
WHEN EXISTS (SELECT UserId
FROM Passwords
WHERE UserId = #UserId
AND Password = #PasswordHash)
THEN 1
ELSE 0
END
SELECT #passwordHash;
END
Question / problem:
Why is the hash generated by the stored procedure when called from C# different to the one generated from within SQL Server Management Studio? Is there is an issue with unicode/binary conversion or something like that?
Please note I appreciate that 'rolling your own' authorisation is not always a good idea, but this is addressing a temporary issue with a legacy app.
I'm sure this question has already been answered in this forum. I went through most of them and tried all the possible fixes but it did not work in my case. I'm not sure where I'm going wrong.
My stored procedure works fine in SQL Server Management Studio. It is returning the output whenever I run my stored procedure. But the output parameter returns NULL value when I call it from my ADO. NET code. I'm pretty new to this. Any help to resolve this issue is very much appreciated.
Stored Procedure :
CREATE PROCEDURE [dbo].[sp_ZebraGetEmpName]
#EmpId int,
#EmpName varchar(40) out
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
SELECT EmployeeName
FROM ZebraPrinter.dbo.EmployeeInfo
WHERE EmpID = #EmpId
RETURN (#EmpName)
END
GO
ADO .NET Code :
public string GetEmpName(int EmpId)
{
string connStr = ConfigurationManager.ConnectionStrings["ZebraPrinterConnectionString"].ConnectionString;
SqlConnection con = new SqlConnection(connStr);
SqlCommand cmd = new SqlCommand("sp_ZebraGetEmpName", con);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("#EmpId",EmpId);
cmd.Parameters.Add("#EmpName", SqlDbType.VarChar, 40);
cmd.Parameters["#EmpName"].Direction = ParameterDirection.Output;
con.Open();
cmd.ExecuteScalar();
con.Close();
string EmpName = cmd.Parameters["#EmpName"].Value.ToString();
return (EmpName);
}
You didn't assign any values to #EmpName variable in procedure so it will be NULL. Change the select query to assign value to #EmpName
Also you don't need procedure to do this.
SELECT #EmpName=EmployeeName
FROM ZebraPrinter.dbo.EmployeeInfo
WHERE EmpID = #EmpId
You getting the null value in the output parameter because you are not assigning the value to the output parameter.
When you are using a output parameter then you have to set it's value in the stored procedure to use it's value later.
Use:
SET #EmpName = (SELECT TOP 1 EmployeeName
FROM ZebraPrinter.dbo.EmployeeInfo
WHERE EmpID = #EmpId)
and your stored procedure should be like this:
CREATE PROCEDURE [dbo].[sp_ZebraGetEmpName]
#EmpId int,
#EmpName varchar(40) out
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
SET #EmpName = (SELECT TOP 1 EmployeeName
FROM ZebraPrinter.dbo.EmployeeInfo
WHERE EmpID = #EmpId)
END
GO
Returning something, setting an output parameter value, and selecting something are three different things.
You can set #EmpName like NoDisplayName's answer and leave your C# code as-is, or you can just run the select and pull the value when you execute the query:
CREATE PROCEDURE [dbo].[sp_ZebraGetEmpName]
#EmpId int
AS
BEGIN
SELECT EmployeeName
FROM ZebraPrinter.dbo.EmployeeInfo
WHERE EmpID = #EmpId
END
string empName = (string)cmd.ExecuteScalar();
What you're seeing in Management Studio when you run this is not the return value, but rather the query result. The return value of your query would be null.
Some stored procedures do some checks and when those fails, return a specific value. If nothing fails, some return a SELECT statement, some return NOTHING (like the sample below).
Note: Stored Procedures canNOT be changed at this point and the sample below is just a sample to show the RETURN and the SELECT differences.
/* THIS IS A SAMPLE PROC */
CREATE PROCEDURE [dbo].[Register]
(
#Email varchar(200)
)
AS
BEGIN
SET NOCOUNT ON ;
-- Check if user table has an Active record with this email
IF EXISTS(SELECT * FROM Users WHERE Email = #Email AND Activated = 0)
RETURN(1)
-- Check if user table has an Active record with this email
IF EXISTS(SELECT * FROM Users WHERE Email = #Email AND Activated = 1)
RETURN(2)
-- Create New User record
INSERT INTO Users (Email, xxx)
VALUES (#Email, xxxx)
END
Using EF5 and importing the stored procedures into the EDMX, how can I get the value 1 or 2 in case of problems or nothing in case the stored procedures went thru successfully?
USe out parameter in store procedure.
var result = dbContext.Database.SqlQuery<string>("QUERY TEXT OR PROUCEDURE TEXT").FirstOrDefualt();
Check if the result not null than it must be 1 or 2.
you can also call the stored procedure as following:
var firstName = "test";
var id = 12;
var sql = #"Update [User] SET FirstName = {0} WHERE Id = {1}";
ctx.Database.ExecuteSqlCommand(sql, firstName, id);
But ExecuteSqlCommand does not return a result!
By default the SPROC will return 0 if no other value is given and no error is thrown. I don't know if that works in all of your scenarios (maybe you are explicitly returning zero in one of them) but in this one, you will still get the 0 response when you aren't explicitly returning anything.
I have a table with autoincremented primary key. In my code I am trying to receive the new autoincremented value when I execute each 'insert' query. Is there a way to do it programatically?
Thanks.
UPD:
Assume I have a table:
TABLE User ( userID INT NOT NULL AUTO_INCREMENT, name VARCHAR( 25 ) NOT NULL , email VARCHAR( 50 ) NOT NULL , UNIQUE ( userID ) );
And I when I insert new values (name and email) to this table I want automatically receive newly generated userID. Ideally I am looking for any ways to do that with a single transaction and without stored procedures.
Have your sql/stored proc return scope_identity() or if you are using Linq2SQL or EF the entity used for insertion gets the new id.
In the stored proc it is:
ALTER proc [dbo].[SaveBuild](
#ID int = 0 output,
#Name varchar(150)=null,
#StageID int,
#Status char(1)=null
)
as
SET NOCOUNT ON
Insert into Builds
(name, StageID, status)
values (#Name, #StageID, #Status)
select #ID = scope_identity()
RETURN #ID
In the C# code you have:
public int SaveBuild(ref int id, ref string Name)
{
SqlCommand cmd = GetNewCmd("dbo.SaveBuild");
cmd.Parameters.Add("#ID", SqlDbType.Int).Value = id;
cmd.Parameters["#ID"].Direction = ParameterDirection.InputOutput;
cmd.Parameters.Add("#Name", SqlDbType.VarChar).Value = Name;
cmd.Parameters.Add("#StageID", SqlDbType.Int).Value = 0;
ExecuteNonQuery(cmd);
id = (int)cmd.Parameters["#ID"].Value;
return id;
}
Dependent upon your situation, you might be better off using table-valued parameters to pass your inserts to a stored procedure, then use OUTPUT INSERTED to return a table-valued parameter from your stored procedure.
It will drastically reduce the number of hits required if you're processing multiple items.
Are you limited to building SQL on the client and sending it to the server? Cause if you can use a stored procedure, this is easy to do. In the stored proc, do the insert and then, either
Select Scope_Identity() as the last statement in the stored proc., or
Use a output parameter to the stored proc, (say named #NewPKValue) and make the last statement:
Set #NewPKValue = Scope_Identity()
Otherwise, you need to send a batch of commands to the server that include two statements, the insert, and Select Scope_Identity() and execute the batch as though it was a select statement
You could use the SQL statement SELECT scope_identity().