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.
Related
For some reason my stored procedure is executed without any error from the code-behind in C# but it is not deleting anything at all that the stored procedure has written. I have all the correct parameters and everything. I ran the query from SQL Server with all the same parameters from the C# code and it works perfectly. I don't get why it works when I run from SQL Server but it doesn't work when I run it from my C# code in Visual Studio.
Here is my C# code that is passing the data through to the stored procedure.
string reportType = "PostClaim";
string GRNBRs = "925','926','927";
string PUNBRs = "100','100','100";
string beginningDates = "20120401";
string endDates= "20120430";
try
{
conn = new SqlConnection(ConnectionInfo);
conn.Open();
SqlDataAdapter da = new SqlDataAdapter("RemoveReport", conn);
da.SelectCommand.CommandType = CommandType.StoredProcedure;
da.SelectCommand.Parameters.AddWithValue("#ReportType", reportType);
da.SelectCommand.Parameters.AddWithValue("#GRNBR", GRNBRs);
da.SelectCommand.Parameters.AddWithValue("#PUNBR", PUNBRs);
da.SelectCommand.Parameters.AddWithValue("#DATE1", beginningDates);
da.SelectCommand.Parameters.AddWithValue("#DATE2", endDates);
da.SelectCommand.CommandTimeout = 360;
}
catch (SqlException ex)
{
//something went wrong
throw ex;
}
finally
{
if (conn.State == ConnectionState.Open)
conn.Close();
}
Here is my stored procedure. It's executing with dynamic SQL text.
ALTER PROCEDURE [dbo].[RemoveReport] (
#ReportType NVARCHAR(20),
#GRNBR VARCHAR(4000),
#PUNBR VARCHAR(4000),
#DATE1 DATETIME,
#DATE2 DATETIME
)
AS
DECLARE #SQLTEXT VARCHAR(4000)
BEGIN
SET #SQLTEXT = 'DELETE FROM TestingTable
WHERE Report='''+#ReportType+''' AND
PUNBR IN ('''+#PUNBR+''') AND
[Group] IN ('''+#GRNBR+''') AND
StartedAt BETWEEN '''+CONVERT(VARCHAR(10),#DATE1,121)+'''
AND '''+CONVERT(VARCHAR(10),#DATE2,121)+''''
PRINT #SQLTEXT <---I'll print this out to show you what exactly it is executing.
EXECUTE (#SQLTEXT)
END
Here is what the PRINT #SQLTEXT is running:
DELETE FROM MonthlyReportSchedule
WHERE Report='PostClaim' AND
PUNBR IN ('100','100','100') AND
[Group] IN ('925','926','927') AND
StartedAt BETWEEN '2012-04-01' AND '2012-04-30'
When I actually go into SQL Server to run this query, it works perfectly. But why does it not work on when executed from the C# code. Any help?
Avoid concatenating parameters to your sql, use parameterised query,
Try this...
Just noticed that you have some comma delimited lists in params.....
ALTER PROCEDURE [dbo].[RemoveReport]
#ReportType NVARCHAR(20),
#GRNBR VARCHAR(4000),
#PUNBR VARCHAR(4000),
#DATE1 DATETIME,
#DATE2 DATETIME
AS
BEGIN
SET NOCOUNT ON;
DECLARE #SQLTEXT NVARCHAR(MAX);
Declare #GRNBR_xml xml,#PUNBR_xml xml;
SET #GRNBR_xml = N'<root><r>' + replace(#GRNBR, ',','</r><r>') + '</r></root>';
SET #PUNBR_xml = N'<root><r>' + replace(#PUNBR, ',','</r><r>') + '</r></root>';
SET #SQLTEXT = N'DELETE FROM TestingTable
WHERE Report = #ReportType
AND PUNBR IN (select r.value(''.'',''varchar(max)'') as item
from #PUNBR_xml.nodes(''//root/r'') as records(r))
AND [Group] IN (select r.value(''.'',''varchar(max)'') as item
from #GRNBR_xml.nodes(''//root/r'') as records(r))
AND StartedAt BETWEEN #DATE1 AND #DATE2'
EXECUTE sp_executesql #SQLTEXT
,N'#ReportType NVARCHAR(20) , #GRNBR_xml xml,
#PUNBR_xml xml,#DATE1 DATETIME,#DATE2 DATETIME'
,#ReportType
,#GRNBR_xml
,#PUNBR_xml
,#DATE1
,#DATE2
END
Note
Make sure you pass the comma delimited list as 925,926,927 and not as '925','926','927'
Try adding this line in order to be executed
da.SelectCommand.ExecuteNonQuery();
This will execute a call to your stored procedure.
good luck
I created a SqlDependency so that an event would fire when the results of a particular query change.
// Create a command
SqlConnection conn = new SqlConnection(connectionString);
string query = "SELECT MyColumn FROM MyTable;";
SqlCommand cmd = new SqlCommand(query, conn)
cmd.CommandType = CommandType.Text;
// Register a dependency
SqlDependency dependency = new SqlDependency(cmd);
dependency.OnChange += DependencyOnChange;
When this code executes, a stored procedure is automatically created with a name like
SqlQueryNotificationStoredProcedure-82ae1b92-21c5-46ae-a2a1-511c4f849f76
This procedure is unencrypted, which violates requirements I have been given. I have two options:
Convince the customer that it doesn't matter that the auto generated procedure is unencrypted because it only does cleanup work and contains no real information (thanks to ScottChamberlain for pointing this out).
Find a way to encrypt the stored procedure generated by SqlDependency.
How can I accomplish option 2?
Contents of the stored procedure in question:
CREATE PROCEDURE [dbo].[SqlQueryNotificationStoredProcedure-b124707b-23fc-4002-aac3-4d52a71c5d6b]
AS
BEGIN
BEGIN TRANSACTION;
RECEIVE TOP (0) conversation_handle
FROM [SqlQueryNotificationService-b124707b-23fc-4002-aac3-4d52a71c5d6b];
IF (
SELECT COUNT(*)
FROM [SqlQueryNotificationService-b124707b-23fc-4002-aac3-4d52a71c5d6b]
WHERE message_type_name = 'http://schemas.microsoft.com/SQL/ServiceBroker/DialogTimer'
) > 0
BEGIN
IF (
(
SELECT COUNT(*)
FROM sys.services
WHERE NAME = 'SqlQueryNotificationService-b124707b-23fc-4002-aac3-4d52a71c5d6b'
) > 0
)
DROP SERVICE [SqlQueryNotificationService-b124707b-23fc-4002-aac3-4d52a71c5d6b];
IF (OBJECT_ID('SqlQueryNotificationService-b124707b-23fc-4002-aac3-4d52a71c5d6b', 'SQ') IS NOT NULL)
DROP QUEUE [SqlQueryNotificationService-b124707b-23fc-4002-aac3-4d52a71c5d6b];
DROP PROCEDURE [SqlQueryNotificationStoredProcedure-b124707b-23fc-4002-aac3-4d52a71c5d6b];
END
COMMIT TRANSACTION;
END
GO
Create a DDL trigger that checks if a procedure with a name like "SqlQueryNotificationStoredProcedure-" is being created, and if so, immediately alter it WITH ENCRYPTION instead:
CREATE TRIGGER [TR_EncryptQueryNotificationProcedures]
ON DATABASE
AFTER CREATE_PROCEDURE, ALTER_PROCEDURE
AS
BEGIN
SET ARITHABORT ON;
SET NOCOUNT ON;
IF TRIGGER_NESTLEVEL() > 1 RETURN;
-- For debugging purposes only
PRINT CONVERT(NVARCHAR(MAX), EVENTDATA());
DECLARE #DatabaseName NVARCHAR(128);
SET #DatabaseName = EVENTDATA().value(
'(/EVENT_INSTANCE/DatabaseName)[1]', 'NVARCHAR(128)'
);
DECLARE #Schema NVARCHAR(128);
SET #Schema = EVENTDATA().value(
'(/EVENT_INSTANCE/SchemaName)[1]', 'NVARCHAR(128)'
);
DECLARE #Name NVARCHAR(128);
SET #Name = EVENTDATA().value(
'(/EVENT_INSTANCE/ObjectName)[1]', 'NVARCHAR(128)'
);
DECLARE #Definition NVARCHAR(MAX);
SELECT #Definition =
OBJECT_DEFINITION(
OBJECT_ID(
QUOTENAME(#DatabaseName) + '.' +
QUOTENAME(#Schema) + '.' +
QUOTENAME(#Name),
'P'
)
)
;
-- If the sproc is already encrypted, we can't do anything with it
IF #Definition IS NULL RETURN;
SELECT #Definition = STUFF(
#Definition,
CHARINDEX('CREATE', #Definition),
LEN('CREATE'),
'ALTER'
);
IF
#Name LIKE 'SqlQueryNotificationStoredProcedure-%' AND
-- this should always be false since we can't read encrypted definitions,
-- but just to make sure
#Definition NOT LIKE '%WITH ENCRYPTION AS BEGIN%'
BEGIN;
SET #Definition = REPLACE(
#Definition, 'AS' + CHAR(13) + CHAR(10) + 'BEGIN',
'WITH ENCRYPTION AS BEGIN'
);
EXEC (#Definition);
END;
END;
GO
ENABLE TRIGGER [TR_EncryptQueryNotificationProcedures] ON DATABASE;
Disclaimer: not tested against an actual dependency notification, but the basic idea is sound. It's quite brittle because it depends on the exact form of the procedure, of course -- making it more robust is possible, but tedious.
I'm experimenting with SEQUENCE objects in SQL Server, and getting the next value with C# by specifying the sequence name. Ranges are simple, because there is a stored procedure for them, and you can pass the sequence name;
public static T Reserve<T>(string name, int count, SqlConnection sqlConn)
{
using (var sqlCmd = new SqlCommand("sp_sequence_get_range", sqlConn))
{
sqlCmd.CommandType = CommandType.StoredProcedure;
var firstValueParam = new SqlParameter("#range_first_value", SqlDbType.Variant) { Direction = ParameterDirection.Output };
sqlCmd.Parameters.AddWithValue("#sequence_name", name);
sqlCmd.Parameters.AddWithValue("#range_size", count);
sqlCmd.Parameters.Add(firstValueParam);
sqlCmd.ExecuteNonQuery();
return (T)firstValueParam.Value;
}
}
But what about single values? It seems to me that I can either call the above with a count of '1', or I can construct the SQL dynamically. i.e.
var sqlCmdStr = string.Format("SELECT NEXT VALUE FOR {0}", name);
Which I know to generally be bad practice (i.e. SQL injection).
What would anyone suggest?
Which I know to generally be bad practice (i.e. SQL injection).
Not every dynamic SQL is evil.
Whether you are open to SQL injection depends on where the value (that gets inserted in SQL text) comes from. If it comes from a place under a tight control of your code (e.g. a switch statement that chooses from a set of string constants) then SQL injection is not an issue.
Or, you could simply have a separate query for each sequence (assuming you don't have very many of them).
My suggestion is a combination of both #Gserg's answer and your current solution. Write a stored procedure that takes a VARCHAR parameter #Name. Build the sql string in the stored procedure, using QUOTENAME as suggested by #GSerg. Use EXEC or sp_executesql to run the script.
Something like this (freehand):
CREATE PROCEDURE [GetNext]
#Name VARCHAR(50)
AS
BEGIN
DECLARE #sql VARCHAR(200);
SET #Name = QUOTENAME(#Name, '[');
SET #sql = 'SELECT NEXT VALUE FOR ' + #Name;
EXEC (#sql);
END
Another version of Paul's solution, which will return formatted alphanumeric Key from SQL Sequence
CREATE PROCEDURE [sp_GetNextKey]
#Name NVARCHAR(50),
#FormatText NVARCHAR(50)
AS
--DECLARE #Name NVARCHAR(50)='CustomerKeySequence'
--DECLARE #FormatText NVARCHAR(50) = 'CUS0000#'
DECLARE #sql NVARCHAR(200) = 'SELECT FORMAT((NEXT VALUE FOR ' + QUOTENAME(#Name, '"') + '),'+QUOTENAME(#FormatText, '''')+')';
EXEC (#sql)
/*
RETURNS i.e CUS00184
*/
When I need to do a similar thing, I do this:
string sanitized_name;
using (var sqlCmd = new SqlCommand("select quotename(#unsafe_name, '[');", sqlConn))
{
sqlCmd.Parameters.AddWithValue("#unsafe_name", name);
sanitized_name = (string)sqlCmd.ExecuteScalar();
}
using (var sqlCmd = new SqlCommand(string.Format("select next value for {0};", sanitized_name), sqlConn))
{
...
}
Or create a server-side procedure that does the same.
I have a procedure with a single select statement. I am need to create some 50 procedures like the one below..
create procedure foo1 as
select cityid, cityname from footballteam
the footballteam will be common in all my procedures, Instead of creating 50 single procedures, I want to code like below and send 3 parameters from my c# page
create procedure foo1 (#id bigint, #name varchar(50), #param bigint)as
select #id, #name from footballtem where #id =#param
can i pass like this in sql server ?/ How to do like this
will I am able to do procedure overloading in sql server, some time I need to pass only two parameters and i want to get a particular value , I will pass three or more parameters ....
For a pure TSQL answer:
create table footballtem(id int identity(1,1),cityid int, cityname varchar(50))
go
insert footballtem(cityid, cityname) values (123, 'abc')
insert footballtem(cityid, cityname) values (456, 'def')
go
create procedure foo1 (#id sysname, #name sysname, #param bigint) as
declare #sql nvarchar(100) = 'select ' + QUOTENAME(#id) + ','
+ QUOTENAME(#name) + ' from footballtem where '
+ QUOTENAME(#id) + '=#param'
exec sp_ExecuteSql #sql, N'#param bigint', #param
go
exec foo1 'cityid','cityname',123
(credit is due to Mikael Eriksson re QUOTENAME)
Note that QUOTENAME makes the #name and #id injection safe.
Note also, though, that the varying parameter (#param) is safe from injection - we don't need to validate that anywhere; and that this will allow query-plan re-use via sp_ExecuteSql
No; that would do a comparison on the parameter values, and return the parameter values. To do that, you would have to substitute the values at the caller, for example:
string idColumn = "id", nameColumn = "name";
string tsql = string.Format(#"
create procedure foo1 (#param bigint)
as select [{0}], [{1}] from footballtem where [{0}]=#param", idColumn,nameColumn);
and have 50 SPs; you can do the same in TSQL, using sp_ExecuteSQL against an already replaced string, but IMO it would be better to do this at the app tier than inside the database.
Also; question whether you really need stored procedures... that one isn't really going to help much; a parameterised TSQL query is much simpler, just as fast, and easier to deploy.
I'm not sure if I understand you correctly, but you can specify a default value for a stored procedure parameter in T-SQL. So you can omit it while calling.
CREATE PROCEDURE Proc1 #param1 int, #param2 int = -1 AS SELECT case when #param2=-1 then somefield else #param2 end as column from sometable where somekeyfield=#param1; GO
(assuming MS SQL Server)
MS SQL server does not support procedure overloading (as Oracle Does) but does support input and output parameters like this:
create procedure foo1 (
#param bigint
, #id bigint out
, #name varchar(50) out
)as
select
#id = fbt.id
,#name = fbt.name
from
footballteam fbt
where fbt.id =#param
#id and #name have to be passed in as null value output paramters of the correct type. After execution (cmd.executeNonQuery) you can inspect the command object to get the new parameter values back out.
I am not sure I am reading your question correctly, but if I am then this should get what you want..
*Adding better code sample after question *
//_assumes the following using statements at the top of code file:_
//using System.Data;
//using System.Data.SqlClient;
public string getTeam(int CityID)
{
string name;
using (var cmd = new SqlCommand("foo1",new SqlConnection("myConnectionStringGoesHere")))
{
cmd.Parameters.Add(new SqlParameter("#param", CityID));
cmd.Parameters.Add(new SqlParameter("#id", SqlDbType.BigInt){Direction=ParameterDirection.Output});
cmd.Parameters.Add(new SqlParameter("#name", SqlDbType.VarChar,50) { Direction = ParameterDirection.Output });
cmd.Connection.Open();
cmd.ExecuteNonQuery();
name = cmd.Parameters["#name"].Value.ToString();
cmd.Connection.Close();
}
return name;
}
I think you were asking for the following:
create procedure foo1 (#id bitint out, #name bigint out, #param bigint)
as
select #id=cityid, #name=cityname from footballteam where teamname = #param
But your question makes it seem like you are trying to dynamically change the column names per query.
There is a way to do overloading on MSSQL. Here how it goes:
For example we have a sp_Personel procedure which takes personel type as parameter and lists personel of that type.
CREATE PROCEDURE [dbo].[sp_Personel]
#PersonelType int
AS
SELECT Name, JoinDate, PersonelType, Salary
FROM Personel
WHERE PersonelType = #PersonelType
END
Now, you want another procedure which will be for personel join dates.
CREATE PROCEDURE [dbo].[sp_Personel];2
#JoinDate datetime
AS
SELECT Name, JoinDate, PersonelType, Salary
FROM Personel
WHERE JoinDate <= #JoinDate
END
To call second procedure from management studio;
[dbo].[sp_Personel];2 N'9/26/2010'
I am trying to create a parameterized query in C# against a SQL server database.
Code:
query = new StringBuilder( "SELECT #fields FROM #tables");
using(SqlConnection connection = new SqlConnection(connection))
{
SqlCommand command = new SqlCommand(query.ToString(), connection);
command.Parameters.AddWithValue("#fields", fields.ToString());
command.Parameters.AddWithValue("#tables", tables.ToString());
try
{
connection.Open();
Int32 rowsAffected = command.ExecuteNonQuery();
Console.WriteLine("RowsAffected: {0}", rowsAffected);
}
catch(Exception ex)
{
Console.WriteLine(ex.Message);
}
}
The strange part is this fails with the message "Must declare the table variable \"#tables\". However as you can see, it's clearly been defined.
So my question is:
Can you pass a parameter to define
the table list in the FROM
statement?
If you can, why isn't
this working?
SQL doesn't support the FROM clause to be parameterized. So you have to use either dynamic SQL, or create/concatenate the query string prior to submitting it to the database.
No unfortunately you cant use a parameter in the FROM clause.
I think this is not the way SQL command and its parameters should look like. It should look like
SELECT fieldName1, fieldName2
FROM TableName
WHERE fieldName = #paramName
You cannot use parameters as definition of fields to be selected or the target table. If you need to define fields to be selected, simply compose the command string in StringBuilder before you call it - as you need. Parameters are used for filtering purposes. In your case you don't need any paramters, just build your command and execute.
If you're confident that your table and column names are ok, then you can do some safety checks in the database before building your dynamic SQL.
This is just for illustration - for real life, obviously you'd need to make it a lot cleaner:
declare #TABLE_NAME nvarchar(128)
set #TABLE_NAME = 'Robert'');DROP TABLE Students;--' -- This line will raise an error
set #TABLE_NAME = 'BOOK' -- This line will go through properly
declare #sql varchar(max)
set #sql = 'SELECT * FROM '
if exists (select 1 from sys.objects where type = 'U' and name = #TABLE_NAME)
begin
set #sql = #sql + #TABLE_NAME
exec (#sql)
end
else
begin
raiserror ('ERROR ERROR ERROR', 0, 0)
return
end