Stored Procedure expects parameter: Parameter is shown in profiler ...? - sql

I am calling a stored procedure from a DbContext using SqlQuery(). When I run the query I get the error Procedure or function 'p_Insert_Phones' expects parameter '#Number', which was not supplied. What am I missing here? I see the #Number parameter.
declare #p5 nvarchar(255)
set #p5=NULL
exec sp_executesql N'p_Insert_Phones',
N'#Number int,
#PhoneTypeId int,
#ReturnId nvarchar(255) output',
#Number=0,
#PhoneTypeId=0,
#ReturnId=#p5 output
select #p5
EDIT
Procedure definition
CREATE PROCEDURE [dbo].[p_Insert_Phones]
(
#Number int,
#PhoneTypeId int,
#ReturnId uniqueidentifier out
)
AS
DECLARE #id TABLE(
ReturnColId uniqueidentifier
)
BEGIN TRAN
INSERT INTO Phones ([Number],[PhoneTypeId])
OUTPUT inserted.Id
INTO #id
VALUES (#Number,#PhoneTypeId)
COMMIT TRAN
SET #ReturnId = (SELECT ReturnColId FROM #id)

Please try the following :
this.context.Database.SqlQuery<string>("p_Insert_Phones #Number = {0}, #PhoneTypeId = {1}", 0, 0);
Where this.context is the context you want to use ofcourse. The generic type parameter for the SqlQuery method is the return type of the query result, in your case is a nvarchar which results in a string type.
(Untested, but it's a mockup based on code i used somewhere within one of my projects).

You cannot call a stored procedure that is found in the database. The parameters that you provide will not be found by the stored procedure. I resolved the issue by including the stored procedure as the sql in the DbContext call.
var sqlInsert = "DECLARE #id TABLE(ReturnColId uniqueidentifier) " +
"BEGIN TRAN INSERT INTO Phones " +
"([Number],PhoneTypeId]) " +
"OUTPUT inserted.Id " +
"INTO #id " +
"VALUES (#Number,#PhoneTypeId) " +
"COMMIT TRAN " +
"SET #ReturnId = (SELECT ReturnColId FROM #id)"
var sqlTransaction = context.Database.SqlQuery(type, insertProcedure, parameters.ToArray());
foreach (var record in sqlTransaction) { } // executes the lazy loading in order to
// populate the out parameter.
Guid newId = new Guid(parameters[parameters.Count - 1].Value.ToString());

Related

How do I retrieve scope identity with ExecuteNonQuery?

My project is using .NET Core 3.1 and I have my stored procedures executing in my repository class. I want to insert and return the scope identity(the id of the record that just inserted UserNumber) so I can use it for another stored proc within this same method. The problem I have here is that parameters[1].Value value is returning zero.
Here is an abbreviation of my stored proc:
ALTER PROCEDURE [dbo].[InsertUser]
#iUserNumber int OUTPUT,
As
INSERT dbo.tblUser (
CreatedBy
)
VALUES (#LoginUserId)
IF ##ERROR <> 0 GOTO ERRHANDLER
SET #UserNumber = SCOPE_IDENTITY() /** this is the primary Key **/
RETURN(#UserNumber)
Here is a sample of my repository
public int InsertUsers(int LoginUserId, int UserNumber)
{
SqlParameter[] parameters = new List<SqlParameter>()
{
_dbContext.CreateSqlParameter(StoredProcedureConstants.LoginUserId,SqlDbType.Int,LoginUserId.ToSafeInt()),
_dbContext.CreateSqlParameter(StoredProcedureConstants.UserNumber,SqlDbType.Int,UserNumber.ToSafeInt())
}.ToArray();
var intResult = _dbContext.ExecuteNonQuery(StoredProcedureConstants.InsertUsers, parameters);
var result2 = parameters[1].Value; //This comes back as zero
How do I assign the scope identity to result2?
Should be something like:
CREATE OR ALTER PROCEDURE [dbo].[InsertUser]
#LoginUserId int,
#iUserNumber int OUTPUT
As
INSERT dbo.tblUser (CreatedBy)
VALUES (#LoginUserId)
SET #iUserNumber = SCOPE_IDENTITY() /** this is the primary Key **/
and
SqlParameter[] parameters = new List<SqlParameter>()
{
_dbContext.CreateSqlParameter("#LoginuserId",SqlDbType.Int,LoginUserId),
_dbContext.CreateSqlParameter("#iUserNumber",SqlDbType.Int)
}.ToArray();
parameters[1].Direction = ParameterDirection.Output;
_dbContext.ExecuteNonQuery(StoredProcedureConstants.InsertUsers, parameters);
var result2 = parameters[1].Value;

SQL stored procedure call failed with index out of range

I am trying to execute a stored procedure in SQL Server from my java code. This stored procedure is used in some other language and it was working fine from long back. Now I need to integrate it in my java app. I have 15 columns in my table. When I tried this in my java code, its throwing
com.microsoft.sqlserver.jdbc.SQLServerException: The index 11 is out of range
I also see "Error: 0, SQLState: S1093"
My stored procedure
ALTER PROCEDURE [dbo].[user_input_sp]
#Username VARCHAR(10)=NULL,
#UserKey VARCHAR(50)=NULL,
#ReqTime DATETIME=null,
#ResTime DATETIME=null,
#Req TEXT=null,
#Res TEXT=null,
#condition TEXT=null,
#ID INT=null,
#Address VARCHAR(8000) = NULL,
#Task VARCHAR(50) = NULL,
#Direction INT=0,
#Seq INT=0,
#RR BIT=0,
#Correction VARCHAR(8) = NULL,
#PendingTrans VARCHAR(50) = NULL,
#ForwardID VARCHAR(50) = NULL,
#Command VARCHAR(50) = NULL
AS
DECLARE #TSqno int
#IF #Direction=0
BEGIN
EXECUTE Basp_NewKey 'web_log', #TSqno OUTPUT
Insert into cbscne dbo WebServiceLog( Order, US_Name, US_Key , US_ReqD, US_ResD, US_Req , US_Res, cond, ID ,Address, Task, Command)
values (#TSqno, #Username, #UserKey, #ReqTime, #ResTime, #Req ,#Res, #condition, #ID ,#Address, #Task ,#Command)
END
.......
My java code
public vold addWebServiceLogRequest(UserInput cd){
sessionFactory = sqlServerEMFactory.unwrap(SessionFactory.class);
Session sessionForSave = sessionFactory.openSession();
sessionForSave.beginTransaction();
sessionForSave.doReturningWork(connection-> {
try(CallableStatement cstmt = connection.prepareCall("{call user_input_sp(?,?,?,?,?,?,?,?,?)}")) {
cstmt.setInt("indicator", 0);
cstmt.setString("Username",cd.getInterfaceName());
cstmt.setInt("User_code",cd.getCaseId());
cstmt.setTimestamp("RetrieveDate",cd.getRequestDate());
cstmt.setTimestamp("ReturnDate",cd.getResponseDate());
cstmt.setString("Req",cd.getRequestxml());
cstmt.setString("Res",cd.getResponsexml());
cstmt.setString("Task",cd.getTask());
cstmt.setString("flow",null);
cstmt.execute();
cstmt.close();
connection.close();
return null;
}
});
}

SQL Server 2008 stored procedure return value

Here is my stored procedure
ALTER PROCEDURE Delete
#ID nvarchar(64),
#value int = 0 output
AS
BEGIN
IF(EXISTS(SELECT * FROM A where Ap = #ID))
BEGIN
set #value = 1
END
ELSE IF(EXISTS(SELECT * FROM B where Bp = #ID))
BEGIN
set #value = 2
END
ELSE
BEGIN
select *
from Table_D
END
END
RETURN #value
Problem is that when I execute it, this does not return any value
There are multiple ways of returning status information from a stored procedure to an application. Each has its pros and cons; no single technique can definitely be said to be the right one in all circumstances. Even so, I'll start off with:
TL;DR: recommendation
Use RAISERROR if your stored procedure runs into trouble and cannot return the data it normally returns. Use OUTPUT parameters for information the client isn't free to ignore, but which isn't logically part of your result. Use the return value if you have an informational status code that the client is free to ignore. Use additional result sets only if you know what you're doing.
RAISERROR
If your stored procedure encounters an error and can't return any data, you can use RAISERROR to terminate execution and cause an exception to be raised on the client side.
CREATE PROCEDURE [Delete]
#ID nvarchar(64)
AS BEGIN
IF(EXISTS(SELECT * FROM A where Ap = #ID))
BEGIN
RAISERROR('Wrong. Try again.', 11, 1);
RETURN;
END
ELSE IF(EXISTS(SELECT * FROM B where Bp = #ID))
BEGIN
RAISERROR('Wrong in a different way. Try again.', 11, 2);
RETURN;
END
ELSE
BEGIN
select *
from Table_D
END
END
The second parameter (severity) must be set to at least 11 to make the error propagate as an exception, otherwise it's just an informational message. Those can be captured too, but that's out of the scope of this answer. The third parameter (state) can be whatever you like and could be used to pass the code of the error, if you need to localize it, for example. User-generated message always have SQL error code 50000, so that can't be used to distinguish different errors, and parsing the message is brittle.
The C# code to process the result:
try {
using (var reader = command.ExecuteReader()) {
while (reader.Read()) {
...
}
}
} catch (SqlException e) {
Console.WriteLine(
"Database error executing [Delete] (code {0}): {1}", e.State, e.Message
);
}
This is a natural fit for errors because the code to actually process the data stays what it is, and you can handle the exception at the right location (rather than propagating a status code everywhere). But this method is not appropriate if the stored procedure is expected to return a status that is informational and not an error, as you would be catching exceptions all the time even though nothing's wrong.
Output parameter
A stored procedure can set parameter values as well as receive them, by declaring them OUTPUT:
CREATE PROCEDURE [Delete]
#ID nvarchar(64),
#StatusCode INT OUTPUT
AS BEGIN
IF(EXISTS(SELECT * FROM A where Ap = #ID))
BEGIN
SET #StatusCode = 1;
END
ELSE IF(EXISTS(SELECT * FROM B where Bp = #ID))
BEGIN
SET #StatusCode = 2;
END
ELSE
BEGIN
SET #StatusCode = 0;
select *
from Table_D
END
END
From C#, this is captured in a parameter marked as an output parameter:
SqlParameter statusCodeParameter = command.Parameters.Add(
new SqlParameter {
ParameterName = "#StatusCode",
SqlDbType = SqlDbType.Int,
Direction = ParameterDirection.Output
}
);
using (var reader = command.ExecuteReader()) {
int statusCode = (int) statusCodeParameter.Value;
if (statusCode != 0) {
// show alert
return;
}
while (reader.Read()) {
...
}
}
The benefits here are that the client cannot forget to declare the parameter (it must be supplied), you're not restricted to a single INT, and you can use the value of the parameter to decide what you want to do with the resul set. Returning structured data is cumbersome this way (lots of OUTPUT parameters), but you could capture this in a single XML parameter.
Return value
Every stored procedure has a return value, which is a single INT. If you don't explicitly set it using RETURN, it stays at 0.
CREATE PROCEDURE [Delete]
#ID nvarchar(64)
AS BEGIN
IF(EXISTS(SELECT * FROM A where Ap = #ID))
BEGIN
RETURN 1
END
ELSE IF(EXISTS(SELECT * FROM B where Bp = #ID))
BEGIN
RETURN 2
END
ELSE
BEGIN
select *
from Table_D
END
END
From C#, the return value has to be captured in a single special parameter marked as the return value:
SqlParameter returnValueParameter = command.Parameters.Add(
new SqlParameter { Direction = ParameterDirection.ReturnValue }
);
using (var reader = command.ExecuteReader()) {
// this could be empty
while (reader.Read()) {
...
}
}
int returnValue = (int) returnValueParameter.Value;
It's important to note that the return value will not be available until you've processed all other result sets that the stored procedure generates (if any), so if you're using it for a status code that indicates there are no rows, you must still process the empty result set first before you have the status code. You cannot return anything other than an INT. Frameworks/OR mappers often have no support for the return value. Finally, note that the client is not required to do anything with the return value, so you have to carefully document its intended use.
Result set
The stored procedure can simply return what it wants as the result set, just like it's returning the other data. A stored procedure is allowed to return multiple result sets, so even if your status is logically separate from the other data, you can return it as a row.
CREATE PROCEDURE [Delete]
#ID nvarchar(64)
AS BEGIN
DECLARE #StatusCode INT = 0;
IF(EXISTS(SELECT * FROM A where Ap = #ID))
BEGIN
SET #StatusCode = 1;
END
ELSE IF(EXISTS(SELECT * FROM B where Bp = #ID))
BEGIN
SET #StatusCode = 2;
END
SELECT #StatusCode AS StatusCode;
IF #StatusCode = 0
BEGIN
select *
from Table_D
END
END
To process this with C#, we need SqlDataReader.NextResult:
using (var reader = command.ExecuteReader()) {
if (!reader.Read()) throw new MyException("Expected result from stored procedure.");
statusCode = reader.GetInt32(reader.GetOrdinal("StatusCode"));
if (statusCode != 0) {
// show alert
return;
}
reader.NextResult();
while (reader.Read()) {
// use the actual result set
}
}
The main drawback here is that it's not intuitive for a stored procedure to return a variable number of result sets, and very few data frameworks/OR mappers support it, so you'll nearly always end up writing manual code like this. Returning multiple result sets is not really a good fit for returning a single piece of data like a status code, but it might be an alternative to returning structured data in an XML output parameter (especially if there's lots).
The return seems to be out of scope of the procedure. Try:
ALTER PROCEDURE Delete
#ID nvarchar(64),
#value int=0 output
AS
BEGIN
IF(EXISTS(SELECT * FROM A where Ap=#ID))
BEGIN
set #value=1
END
ELSE IF(EXISTS(SELECT * FROM B where Bp=#ID))
BEGIN
set #value=2
END
ELSE
BEGIN
set #value=5
end --end if
RETURN #value
end --end procedure
This is where using tabbing properly makes the code a lot more readable, and these problems more obvious
Don't use the output parameter. Rather, use this:
ALTER PROCEDURE Delete
#ID nvarchar(64)
AS
BEGIN
DECLARE #value int
SET #value = 0
IF(EXISTS(SELECT 1 FROM A where Ap=#ID))
BEGIN
set #value=1
END
ELSE IF(EXISTS(SELECT 1 FROM B where Bp=#ID))
BEGIN
set #value=2
END
ELSE
BEGIN
set #value=5
end
Select #value as Value, * from Table_D
end
Can you try running the SP as the script below?
declare #pID as nvarchar(64)
declare #pValue as int
set #pID = 1 -- Your ID filter
exec Delete #pID, #pValue OUTPUT
select #pValue

Although I send the parameter I get "Can not insert NULL value..."

I am developing an ADO.NET application. At some point in the DAL I call a stored-procedure named "CREATE_CUSTOMER". Although I set the SHORT_NAME field I still get the
"Msg 515, Level 16, State 2, Procedure CREATE_CUSTOMER, Line 29
Cannot insert the value NULL into column 'SHORT_NAME', table 'MYDB.app.CUSTOMER';
column does not allow nulls. INSERT fails." error.
When I inspect the query with the SQL profiler I get the following SQL runs on the server. As I Copy&Paste it to a new Query Window I still get the same error.
Do I miss something?
declare #p16 int
set #p16=NULL
exec sp_executesql N'[app].[CREATE_CUSTOMER]',
N'#SHORT_NAME nvarchar(11),
#MAIL_NAME nvarchar(18),
#MT_SALESPERSON_ID int,
#CREDIT_LIMIT decimal(1,0),
#CREDIT_LIMIT_CURRENCY_ID int,
#PAYMENT_TYPE_ID int,
#SALES_TERM_ID int,
#FREE_STORAGE_DAY_ID int,
#RISK_GROUP_ID int,
#SECTOR_ID int,
#OCCUPATION_ID int,
#STORAGE_FEE_ID int,
#STATUS smallint,
#IDENTITY int output',
#SHORT_NAME=N'NEW Corp',
#MAIL_NAME=N'NEW Corporation',
#MT_SALESPERSON_ID=3,
#CREDIT_LIMIT=0,
#CREDIT_LIMIT_CURRENCY_ID=1,
#PAYMENT_TYPE_ID=4,
#SALES_TERM_ID=7,
#FREE_STORAGE_DAY_ID=6,
#RISK_GROUP_ID=3,
#SECTOR_ID=13,
#OCCUPATION_ID=16,
#STORAGE_FEE_ID=6,
#STATUS=0,
#IDENTITY=#p16 output
select #p16
And my Stored Procedure is as follows :
CREATE PROCEDURE [app].[CREATE_CUSTOMER]
#SHORT_NAME varchar(250) = NULL,
#MAIL_NAME varchar(500) = NULL,
#MT_SALESPERSON_ID int = NULL,
#CREDIT_LIMIT decimal(18,2) = NULL,
#CREDIT_LIMIT_CURRENCY_ID int = NULL,
#PAYMENT_TYPE_ID int = NULL,
#SALES_TERM_ID int = NULL,
#FREE_STORAGE_DAY_ID int = NULL,
#RISK_GROUP_ID int = NULL,
#SECTOR_ID int = NULL,
#OCCUPATION_ID int = NULL,
#STORAGE_FEE_ID int = NULL,
#STATUS tinyint = NULL,
#IDENTITY INT = NULL OUTPUT
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO [app].[CUSTOMER]
([SHORT_NAME],
[MAIL_NAME],
[MT_SALESPERSON_ID],
[CREDIT_LIMIT],
[CREDIT_LIMIT_CURRENCY_ID],
[PAYMENT_TYPE_ID],
[SALES_TERM_ID],
[FREE_STORAGE_DAY_ID],
[RISK_GROUP_ID],
[SECTOR_ID],
[OCCUPATION_ID],
[STORAGE_FEE_ID],
[STATUS],
[CREATE_DATE],
[CREATE_USERID])
VALUES
(#SHORT_NAME,
#MAIL_NAME,
#MT_SALESPERSON_ID,
#CREDIT_LIMIT,
#CREDIT_LIMIT_CURRENCY_ID,
#PAYMENT_TYPE_ID,
#SALES_TERM_ID,
#FREE_STORAGE_DAY_ID,
#RISK_GROUP_ID,
#SECTOR_ID,
#OCCUPATION_ID,
#STORAGE_FEE_ID,
#STATUS,
GETDATE(),
CONTEXT_INFO())
SELECT #IDENTITY = SCOPE_IDENTITY()
END
This SQL code is being generated by the ADO.NET. Actual C# code is :
private static ICustomer CreateCustomer(ICustomer customer, int contextUserId)
{
try
{
string sql = "[app].[CREATE_CUSTOMER]";
SqlConnection conn = null;
using (conn = GetConnection())
{
SetContextInfomationToConnection(conn, contextUserId);
SqlCommand cmd = new SqlCommand(sql, conn);
cmd.Parameters.AddWithValue("#SHORT_NAME", customer.ShortName);
cmd.Parameters.AddWithValue("#MAIL_NAME", customer.MailName);
cmd.Parameters.AddWithValue("#MT_SALESPERSON_ID", customer.SalesPersonId);
cmd.Parameters.AddWithValue("#CREDIT_LIMIT", customer.CreditLimit);
cmd.Parameters.AddWithValue("#CREDIT_LIMIT_CURRENCY_ID", customer.CreditLimitCurrencyId);
cmd.Parameters.AddWithValue("#PAYMENT_TYPE_ID", customer.PaymentTypeId);
cmd.Parameters.AddWithValue("#SALES_TERM_ID", customer.SalesTermId);
cmd.Parameters.AddWithValue("#FREE_STORAGE_DAY_ID", customer.FreeStorageDayId);
cmd.Parameters.AddWithValue("#RISK_GROUP_ID", customer.RiskGroupId);
cmd.Parameters.AddWithValue("#SECTOR_ID", customer.SectorId);
cmd.Parameters.AddWithValue("#OCCUPATION_ID", customer.OccupationId);
cmd.Parameters.AddWithValue("#STORAGE_FEE_ID", customer.StorageFeeId);
cmd.Parameters.AddWithValue("#STATUS", customer.Status);
SqlParameter prmNewId = new SqlParameter("#IDENTITY", SqlDbType.Int, 4);
prmNewId.Direction = ParameterDirection.Output;
cmd.Parameters.Add(prmNewId);
cmd.ExecuteNonQuery();
int id = prmNewId.Value != DBNull.Value ? (int)prmNewId.Value : -1;
if (id > 0)
{
customer.Id = id;
return customer;
}
else
{
throw new Exception("Can not insert customer record with Id generation");
}
}
}
catch (Exception ex)
{
throw;
}
}
Your code is the equivalent of doing this:
DECLARE #SHORT_NAME nvarchar(11) = N'NEW Corp';
...
EXEC [app].[CREATE_CUSTOMER];
You are just declaring the parameters, never actually passing them to the procedure invocation. Your code should be like this:
exec sp_executesql N'[app].[CREATE_CUSTOMER] #SHORT_NAME, #MAIL_NAME, ...',
N'#SHORT_NAME nvarchar(11),
#MAIL_NAME nvarchar(18),
...',
#SHORT_NAME=N'NEW Corp',
#MAIL_NAME=N'NEW Corporation',
...
You must not only declare the parameters you pass to the batch, you must also use them when you invoke the procedure.
When I inspect the query with the SQL profiler I get the following SQL runs on the server. As I Copy&Paste it to a new Query Window I still get the same error
This sounds suspiciously like you are using a SqlCommand but forgot to set the CommandType to Procedure. the default is Text and will behave exactly as you observed.
Do not assign null values to ur variable, try only with DECLARING it as bellow
DECLARE #SHORT_NAME varchar(250) ,
instead of
#SHORT_NAME varchar(250) = NULL,

Stored procedure invocation with VARBINARY(MAX) parameters fails with strange error message?

I am trying to store encrypted data into my database (run by MS SQL 2005 Express) using stored procedure.
The stored procedure has the following signature:
ALTER PROCEDURE [dbo].[SaveData]
#FileName AS NVARCHAR(255),
#Data As VARBINARY(MAX),
#Key AS VARBINARY(MAX) = NULL,
#Final AS BIT = NULL
AS
....
In my code I have two variables of type byte[] with my data:
public byte[] EncryptedData { get; set; }
public byte[] EncryptedPassword { get; set; }
var cmd = new SqlCommand("SaveData", conn);
cmd.CommandTimeout = 0;
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("#FileName", FileName);
cmd.Parameters.Add(new SqlParameter("#Data", SqlDbType.VarBinary, EncryptedData.Length, ParameterDirection.Input, false, 0, 0, "", DataRowVersion.Current, (SqlBinary) EncryptedData));
cmd.Parameters.Add(new SqlParameter("#Key", SqlDbType.VarBinary, EncryptedPassword.Length, ParameterDirection.Input, false, 0, 0, "", DataRowVersion.Current, (SqlBinary)EncryptedPassword));
cmd.Parameters.AddWithValue("#Final", true);
Whenever I try to call ExecuteNonQuery() I get the following error message (sorry for German, I just wanted to provide the original text, I also provide translation below!):
Falsche Syntax in der Nähe von '¿'.
Bezeichner (beginnend mit '*è
¯äÅõ42‹áºªž(±Z¦ññú>¬ÕÕ]¥Qî)ÀÍE5 Fäø+ïµ1˜Z}»[–A[Åñ#­šužÿÄ}µ}
%ãVð>TÛ¨¡ªƒ·g·SWFW
Here is the English translation of the error message (done by me, so it might and will deviate from the canonic original error message):
Syntax error near '¿'.
Identifier (beginning with '*è
¯äÅõ42‹áºªž(±Z¦ññú>¬ÕÕ]¥Qî)ÀÍE5 Fäø+ïµ1˜Z}»[–A[Åñ#­šužÿÄ}µ}
%ãVð>TÛ¨¡ªƒ·g·SWFW
It seems as if the SQL string is parsed incorectly and some binary data is parsed as an identifier name.
I tried many different approaches here using .AddWithValue() and setting parameter properties one by one -- this doesn't help.
Any suggesions?
The solution was already pointed out in the comments, so I just answer the question here.
The problem was in using dynamic SQL (the concatenation of SQL strings) in the stored procedure, here is an example:
select #sql = 'insert into dbo.DMSFileData ('
...
select #sql = #sql + ') VALUES ('
select #sql = #sql + '''' + #FileName + ''''
select #sql = #sql + '''' + convert(varchar(max), #Data) + ''''
The correct solution here is to use parameters in concatenation:
select #sql = #sql + ', #filename'
select #sql = #sql + ', #filedata'
And then execute SQL using sp_executesql:
exec sp_executesql #sql, #params, #ForeignKey, #FileName, #Data