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.
Related
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 want to pass a table as a string into stored procedure as a parameter and want to retrieve data from that particular table for e.g. I have made some tables which is year wised like purchase20162017,transfer20162017.
so problem is this i have created a dropdown box in which i have shown the years list like 2016-2017,2017-2018,2018-2019 user select the year from the dropdown box and click a button called generate ledger button. so i take a string from this dropdown box like this
string str = DropDownList1.SelectedItem.ToString();
str = str.Replace(#"-", "");
string purdtb = "purchase" + str;
now i have got exact table in purdtb but it is in string, i want to pass this string value as a parameter in stored procedure. Please tell me how to do this so that i can convert this string as a table name.
There are 2 ways.
A) The best one is:
string connectionString =
ConsoleApplication1.Properties.Settings.Default.ConnectionString;
//
// In a using statement, acquire the SqlConnection as a resource.
//
using (SqlConnection con = new SqlConnection(connectionString))
{
//
// Open the SqlConnection.
//
con.Open();
//
// The following code uses an SqlCommand based on the SqlConnection.
//
using (SqlCommand command = new SqlCommand("SELECT TOP 2 * FROM Dogs1", con))
using (SqlDataReader reader = command.ExecuteReader())
{
while (reader.Read())
{
Console.WriteLine("{0} {1} {2}",
reader.GetInt32(0), reader.GetString(1), reader.GetString(2));
}
}
}
In Dogs1 put your table, use variables to modify the query via code. Why you want to put dynamic table names which is pretty awkward in you stored procedures when you can manage everything from the code with a variable?
B) The second way (not good but works):
Use in your stored procedure sp_executesql, pass the table names as parameters and build the query inside the stored procedure as shown in the documentation and catch the result.
This way you can build the query piece by piece and put in it everything you want (except for SQL reserved keyword, with which you should be careful anyway).
declare #SqlString nvarchar(2000)
declare #ParamDef nvarchar(2000)
set #SqlString = N'exec proc1 #param1, #param2, #param3'
set #ParamDef = N'#param1 bit, #param2 bit, #param3 bit'
EXECUTE sp_executesql #SqlString ,#ParamDef, #param1 = 0, #param2 = 1, #param3 = 1
To explain, sp_executesql works something like this
EXECUTE sp_executesql
N'proc1', -- SQL
N'#param1 bit, #param2 bit, #param3 bit', -- DECLARE
#param1 = 0, #param2 = 1, #param3 = 1 -- VALUES
Which gets translated to
EXECUTE sp_executesql
N'proc1', -- SQL
N'#param1 bit, #param2 bit, #param3 bit', -- DECLARE
#param1 = 0, #param2 = 1, #param3 = 1 -- VALUES
-- DECLARE
declare #param1 bit, #param2 bit, #param3 bit
-- VALUES
select #param1 = 0, #param2 = 1, #param3 = 1
-- SQL
proc1
3) Worst scenario:
Calling sp_executesql from C# code via Sqlcommand
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.
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