C# running temporary stored procedure - c#

I have a SQL statement that I need to run in C# and would need to get parameters from C# code. I know stored procedures are preferred to avoid SQL injection but I am just looking to do this in C#.
I am translating this SQL to C# but I encountered an error even though the query works in SQL Server Management Studio. It uses temporary stored procedure and temp table below:
-- 1.) Declare a criteria table which can be any number of rows
BEGIN TRY
DROP TABLE #CriteriaTable
END TRY
BEGIN CATCH
END CATCH
CREATE TABLE #CriteriaTable (ParameterCode VARCHAR(64), Value VARCHAR(64))
-- 2.) Declare a procedure to add criteria table
BEGIN TRY
DROP PROCEDURE #AddCriteriaTable
END TRY
BEGIN CATCH
END CATCH
go
CREATE PROCEDURE #AddCriteriaTable
(#ParameterCode VARCHAR(64), #Value VARCHAR(64))
AS
INSERT #CriteriaTable
VALUES(#ParameterCode, #Value)
GO
-- 3.) Do a computation which accesses the criteria
BEGIN TRY
DROP PROCEDURE #ComputeBasedOnCriteria
END TRY
BEGIN CATCH
END CATCH
go
CREATE PROCEDURE #ComputeBasedOnCriteria
(#product VARCHAR(36) = 'ABC',
#currency VARCHAR(3) = 'USD',
#zScore FLOAT = .845)
AS
-- Code inside this procedure is largely dynamic sql.
-- This is just a quick mock up
SELECT
#Product ProductCode,
#currency Currency,
950 ExpectedRevenue,
*
FROM
#CriteriaTable c
PIVOT
(min (Value) FOR ParameterCode IN
([MyParam1], MyParam2, MyParam3)
) AS pvt
GO
--End of code for Configuration table
-- Samples: Execute this to add criteria to the temporary table that will be used by #ComputeBasedOnCriteria
EXEC #AddCriteriaTable 'MyParam1', 'MyValue1'
EXEC #AddCriteriaTable 'MyParam2', 'MyValue3'
EXEC #AddCriteriaTable 'MyParam3', 'MyValue3'
--Execute the procedure that will return the results for the screen
EXEC #ComputeBasedOnCriteria
Now trying this in C# I encounter an error when I try to run the #AddCriteriaTable procedure. When I try to run the ExecuteQuery on the second to the last line it throws:
Exception: System.Data.SqlClient.SqlException, Incorrect syntax near the keyword 'PROC'.
Why does it work in SQL Server but not in C# code? Is there another way to do this in C#? Let me know if there are c# guidelines I should follow as I am still learning this c# - db work.
EDIT:
I know I could do this as a normal stored proc and pass in a DataTable however there are team issues I cannot say and it forces me to use the sp as a text.

The reason that it is failing is you are passing parameters to the CREATE PROC section here:
cmd.CommandText = #"CREATE PROC #AddCriteriaTable (#ParameterCode VARCHAR(64), #Value VARCHAR(64)) AS INSERT #CriteriaTable VALUES (#ParameterCode, #Value)";
cmd.Parameters.AddWithValue("#ParameterCode", request.Criteria.First().Key;
cmd.Parameters.AddWithValue("#Value", request.Criteria.First().Value;
var reader2 = cmd.ExecuteReader();
It does not make sense to pass the values here, since you are just creating the procedure, you only need to pass them when executing the procedure. If you run a trace you will see something like this being executed on the server:
EXEC sp_executesql
N'CREATE PROC #AddCriteriaTable (#ParameterCode VARCHAR(64), #Value VARCHAR(64)) AS INSERT #CriteriaTable VALUES (#ParameterCode, #Value)',
N'#ParameterCode VARCHAR(64),#Value VARCHAR(64)',
#ParameterCode = 'MyParam1',
#Value = 'MyValue1'
Which will throw the same incorrect syntax error when run in SSMS. All you need is:
EXEC sp_executesql
N'CREATE PROC #AddCriteriaTable (#ParameterCode VARCHAR(64), #Value VARCHAR(64)) AS INSERT #CriteriaTable VALUES (#ParameterCode, #Value)';
So in c# you would need:
//First Create the procedure
cmd.CommandText = #"CREATE PROC #AddCriteriaTable (#ParameterCode VARCHAR(64), #Value VARCHAR(64)) AS INSERT #CriteriaTable VALUES (#ParameterCode, #Value)";
cmd.ExecuteNoneQuery();
//Update the command text to execute it, then add parameters
cmd.CommandText = "EXECUTE #AddCriteriaTable #ParameterCode, #Value;";
cmd.Parameters.AddWithValue("#ParameterCode", request.Criteria.First().Key;
cmd.Parameters.AddWithValue("#Value", request.Criteria.First().Value;
var reader2 = cmd.ExecuteReader();
I think you are over complicating everything, a temporary stored procedure to add data to a temporary table seems over kill.
If you are executing from code it seems likely that you need to reuse everything, so why not just have a permanent procedure for your computation,
and then use a defined type to manage instances of the execution.
So first create your type:
CREATE TYPE dbo.CriteriaTableType AS TABLE (ParameterCode VARCHAR(64), Value VARCHAR(64));
Then create your procdure:
CREATE PROC dbo.ComputeBasedOnCriteria
(
#product VARCHAR(36)='ABC',
#currency VARCHAR(3)='USD',
#zScore FLOAT = .845,
#CriteriaTable dbo.CriteriaTableType READONLY
)
AS
--Code inside this proc is largely dynamic sql. This is just a quick mock up
SELECT
#Product ProductCode
,#currency Currency
,950 ExpectedRevenue
,*
FROM #CriteriaTable c
PIVOT (MIN (Value) FOR ParameterCode IN (MyParam1, MyParam2,MyParam3)) AS pvt;
GO
Then finally to run:
DECLARE #Criteria dbo.CriteriaTableType;
INSERT #Criteria
VALUES
('MyParam1', 'MyValue1'),
('MyParam2', 'MyValue2'),
('MyParam3', 'MyValue3');
EXECUTE dbo.ComputeBasedOnCriteria #CriteriaTable = #Criteria;
You can even populate the criteria table in c#, and just pass this from c# to the procedure.
var table = new DataTable();
table.Columns.Add("ParameterCode", typeof(string)).MaxLength = 64;
table.Columns.Add("Value", typeof(string)).MaxLength = 64;
foreach (var criterion in request.Criteria)
{
var newRow = table.NewRow();
newRow[0] = criterion.Key;
newRow[1] = criterion.Value;
table.Rows.Add(newRow);
}
using (var connection = new SqlConnection("connectionString"))
using (var command = new SqlCommand("dbo.ComputeBasedOnCriteria", connection))
{
var tvp = command.Parameters.Add("#CriteriaTable", SqlDbType.Structured);
tvp.TypeName = "dbo.CriteriaTableType";
tvp.Value = table;
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
//Do Something with your results
}
}
}

If you are executing SQL to create a stored procedure via C# then you might as well just execute your SQL via C# and forget about the Procedure.
The point of using a stored procedure to avoid SQL Injection only applies when the stored procedure already exists on the server and you are not creating it via the code.
You can avoid SQL injection here by using a Parameterised query.
Parameters prevent sql injection by validating the data type. So if you insert a integer in your code then someone attempting injection cannot supply a string with special characters which changes your expected result.
BUT apart from all that, you're getting an error because you have CREATE PROC in your SQL in C# instead of CREATE PROCEDURE

Related

Error - stored procedure has no parameters and arguments were supplied in c#

When I'm trying to call store procedure and return the value from the procedure, I'm getting the error message - procedure has no parameters and arguments were supplied
Below is the c# code:
using (SqlCommand command2 = new SqlCommand("getservername8", conn1))
{
command2.CommandType = CommandType.StoredProcedure;
command2.Parameters.Add("#s", SqlDbType.NVarChar, 500);
command2.Parameters["#s"].Direction = ParameterDirection.Output;
command2.ExecuteNonQuery();
string server = (string)command2.Parameters["#s"].Value;
}
Below is the stored procedure:
GO
ALTER procedure [dbo].[getservername9]
#s varchar(50)
as begin
declare #server_name varchar(500)
select #server_name = short_description from [Event_alerts].[dbo].[event_alerts]
select #s= SUBSTRING(#server_name, CHARINDEX('-', #server_name) + 15, 50)
return #s
end
Stored procedure gets executed with no error.Any help will be much appreciated
Use ExecuteScalar instead of Executenonquery. Please refer..
https://learn.microsoft.com/en-us/dotnet/api/system.data.sqlclient.sqlcommand.executescalar?view=dotnet-plat-ext-3.1
Please make below changes to your code -
Mark your variable in the stored procedure as output like below -
#s varchar(50) output
You cannot return varchar output values like you added in stored procedure.
Stored procedures always return integer values when you use return
statement here. In your case you will get below error when executed -
(the same can be observed in .NET end as well).
declare #s varchar(50)
exec [dbo].[getservername9] #s
Conversion failed when converting the varchar value '' to data
type int.
Remove the return statement from stored procedure which will automatically return the value back to .NET code.
Finally make the stored procedure names consistent in both .NET and SQL procedure.
First is to change your store procedure name, in the code you are using getservername8 while your stored procedure name is getservername9, the second point you need to mark your parameter as output as show in the code below
Code :
using (SqlCommand command2 = new SqlCommand("getservername", conn1))
{
command2.CommandType = CommandType.StoredProcedure;
command2.Parameters.Add("#s", SqlDbType.NVarChar, 500);
command2.Parameters["#s"].Direction = ParameterDirection.Output;
command2.ExecuteNonQuery();
string server = (string)command2.Parameters["#s"].Value;
}
Stored Procedure :
GO
ALTER procedure [dbo].[getservername]
#s varchar(50) output
as begin
declare #server_name varchar(500)
select #server_name = short_description from [Event_alerts].[dbo].[event_alerts]
select #s= SUBSTRING(#server_name, CHARINDEX('-', #server_name) + 15, 50)
return #s
end

Passing array to a SQL Server Stored Procedure

How can I pass an array variable to a SQL Server stored procedure using C# and insert array values into a whole row?
Thanks in advance.
SQL Server table:
ID | Product | Description
-------------------------------
8A3H | Soda | 600ml bottle
C# array:
string[] info = new string[] {"7J9P", "Soda", "2000ml bottle"};
SQL Server stored procedure:
ALTER PROC INSERT
(#INFO_ARRAY ARRAY)
AS
BEGIN
INSERT INTO Products VALUES (#INFO_ARRAY)
END
In SQL Server 2008 and later
Create a type in SQL Server like so:
CREATE TYPE dbo.ProductArray
AS TABLE
(
ID INT,
Product NVARCHAR(50),
Description NVARCHAR(255)
);
Alter your procedure in SQL Server:
ALTER PROC INSERT_SP
#INFO_ARRAY AS dbo.ProductArray READONLY
AS
BEGIN
INSERT INTO Products SELECT * FROM #INFO_ARRAY
END
Then you'll need to create a DataTable object with values to pass in C#:
DataTable dt = new DataTable();
//Add Columns
dt.Columns.Add("ID");
dt.Columns.Add("Product");
dt.Columns.Add("Description");
//Add rows
dt.Rows.Add("7J9P", "Soda", "2000ml bottle");
using (conn)
{
SqlCommand cmd = new SqlCommand("dbo.INSERT_SP", conn);
cmd.CommandType = CommandType.StoredProcedure;
SqlParameter dtparam = cmd.Parameters.AddWithValue("#INFO_ARRAY", dt);
dtparam.SqlDbType = SqlDbType.Structured;
}
here is a way simpler example:
I've been searching through all the examples and answers of how to pass any array to sql server,till i found this linK, below is how I applied it to my project:
--The following code is going to get an Array as Parameter and insert the values of that
--array into another table
Create Procedure Proc1
#INFO_ARRAY ARRAY nvarchar(max) //this is the array your going to pass from C# code
AS
declare #xml xml
set #xml = N'<root><r>' + replace(#INFO_ARRAY,',','</r><r>') + '</r></root>'
Insert into Products
select
t.value('.','varchar(max)')
from #xml.nodes('//root/r') as a(t)
END
Hope you enjoy it

Calling oracle stored procedure repeatedly

Is it a good practice to call a stored procedure again and again inside a foreach loop to insert data into an Oracle table? Or is there an alternate way to do this?
I have the following procedure:
procedure proc1 (id in varchar2,
level in varchar2,
title in varchar2,
p_id in varchar2,
url in varchar2)
This is the code calling it:
foreach (var c in xDoc.Descendants("cat"))
{
// call store procedure provide all values
foreach (var a in xDoc.Descendants("abc"))
{
// call store procedure provide values
foreach (var d in xDoc.Descendants("def"))
{
// call stored procedure provide values
}
}
}
Is there a better way to do this?
I would personally architect the Stored Procedure to give you the table that you want so that you only have to call it once. Calling the procedure multiple times like this is horribly inefficient because the database is generating a result set multiple times and you have the network overhead. If you create the procedure to return the table that you need, instead of bits and pieces of it, you can call the procedure once and iterate through the table with the cursor.
Assuming you are using ODP.NET, you can use array binding to call your procedure multiple times during a single database round-trip. You pretty much bind your parameters as you normally would, except you assign an array (instead of just one value) to OracleParameter.Value and set OracleCommand.ArrayBindCount accordingly.
Let me give you a simplified example and I'm sure you won't have trouble adapting it to your needs...
Oracle:
CREATE TABLE TEST (
ID INT PRIMARY KEY
);
CREATE OR REPLACE PROCEDURE TEST_INSERT (ID IN NUMBER) AS
BEGIN
INSERT INTO TEST VALUES(ID);
END TEST_INSERT;
C#:
using (var conn = new OracleConnection("your connection string")) {
conn.Open();
var cmd = conn.CreateCommand();
cmd.CommandType = System.Data.CommandType.StoredProcedure;
cmd.CommandText = "TEST_INSERT";
var param = cmd.Parameters.Add("ID", OracleDbType.Int32, System.Data.ParameterDirection.Input);
int[] arr = { 1, 2, 3, 4, 5, 6 };
param.Value = arr;
cmd.ArrayBindCount = arr.Length;
cmd.ExecuteNonQuery();
}
After this piece of code executes, the TEST table will contain all six values from the arr.
So, instead of calling your procedure in each iteration, simply memorize values in an array then pass it to your procedure in one big call at the very end.

Delete with params in SqlCommand

I use ADO.NET to delete some data from DB like this:
using (SqlConnection conn = new SqlConnection(_connectionString))
{
try
{
conn.Open();
using (SqlCommand cmd = new SqlCommand("Delete from Table where ID in (#idList);", conn))
{
cmd.Parameters.Add("#idList", System.Data.SqlDbType.VarChar, 100);
cmd.Parameters["#idList"].Value = stratIds;
cmd.CommandTimeout = 0;
cmd.ExecuteNonQuery();
}
}
catch (Exception e)
{
//_logger.LogMessage(eLogLevel.ERROR, DateTime.Now, e.ToString());
}
finally
{
conn.Close();
}
}
That code executes without Exception but data wasn't deleted from DB.
When I use the same algorithm to insert or update DB everything is OK.
Does anybody know what is the problem?
You can't do that in regular TSQL, as the server treats #idList as a single value that happens to contain commas. However, if you use a List<int>, you can use dapper-dot-net, with
connection.Execute("delete from Table where ID in #ids", new { ids=listOfIds });
dapper figures out what you mean, and generates an appropriate parameterisation.
Another option is to send in a string and write a UDF to perform a "split" operation, then use that UDF in your query:
delete from Table where ID in (select Item from dbo.Split(#ids))
According to Marc's Split-UDF, this is one working implementation:
CREATE FUNCTION [dbo].[Split]
(
#ItemList NVARCHAR(MAX),
#delimiter CHAR(1)
)
RETURNS #IDTable TABLE (Item VARCHAR(50))
AS
BEGIN
DECLARE #tempItemList NVARCHAR(MAX)
SET #tempItemList = #ItemList
DECLARE #i INT
DECLARE #Item NVARCHAR(4000)
SET #tempItemList = REPLACE (#tempItemList, ' ', '')
SET #i = CHARINDEX(#delimiter, #tempItemList)
WHILE (LEN(#tempItemList) > 0)
BEGIN
IF #i = 0
SET #Item = #tempItemList
ELSE
SET #Item = LEFT(#tempItemList, #i - 1)
INSERT INTO #IDTable(Item) VALUES(#Item)
IF #i = 0
SET #tempItemList = ''
ELSE
SET #tempItemList = RIGHT(#tempItemList, LEN(#tempItemList) - #i)
SET #i = CHARINDEX(#delimiter, #tempItemList)
END
RETURN
END
And this is how you could call it:
DELETE FROM Table WHERE (ID IN (SELECT Item FROM dbo.Split(#idList, ',')));
I want to give this discussion a little more context. This seems to fall under the topic of "how do I get multiple rows of data to sql". In #Kate's case she is trying to DELETE-WHERE-IN, but useful strategies for this user case are very similar to strategies for UPDATE-FROM-WHERE-IN or INSERT INTO-SELECT FROM. The way I see it there are a few basic strategies.
String Concatenation
This is the oldest and most basic way. You do a simple "SELECT * FROM MyTable WHERE ID IN (" + someCSVString + ");"
Super simple
Easiest way to open yourself to a SQL Injection attack.
Effort you put into cleansing the string would be better spent on one of the other solutions
Object Mapper
As #MarcGravell suggested you can use something like dapper-dot-net, just as Linq-to-sql or Entity Framework would work. Dapper lets you do connection.Execute("delete from MyTable where ID in #ids", new { ids=listOfIds }); Similarly Linq would let you do something like from t in MyTable where myIntArray.Contains( t.ID )
Object mappers are great.
However, if your project is straight ADO this is a pretty serious change to accomplish a simple task.
CSV Split
In this strategy you pass a CSV string to SQL, whether ad-hoc or as a stored procedure parameter. The string is processed by a table valued UDF that returns the values as a single column table.
This has been a winning strategy since SQL-2000
#TimSchmelter gave a great example of a csv split function.
If you google this there are hundreds of articles examining every aspect from the basics to performance analysis across various string lengths.
Table Valued Parameters
In SQL 2008 custom "table types" can be defined. Once the table type is defined it can be constructed in ADO and passed down as a parameter.
The benefit here is it works for more scenarios than just an integer list -- it can support multiple columns
strongly typed
pull string processing back up to a layer/language that is quite good at it.
This is a fairly large topic, but Table-Valued Parameters in SQL Server 2008 (ADO.NET) is a good starting point.

getting sql statement behind view

I have a c# application (2008) that gets data from sql server (2005).
I have a view in sql server that prepares data for display, something like this (simplified):
select Places.Name as [Location], Parts.Name as [Part Name]
from Places inner join Parts
on Places.Id=Parts.Location
I have to filter this with "where" statement that is built in code and is like:
where (Places.Id=1 or Places.Id=15) and
(Parts.Id=56 or Parts.Id=8 or Parts.Id=32)
I can of course keep the basic select statement in my code, but i likw to have things defined only in one place :) and the question is if there is any way to get the select statement behind the view in sql server? Or to get the contents of stored procedure?
Thanks a lot!
Take a look at Information Schema View, you may find your solution.
Using the information schema views as jani suggested is one option.
Another is using the sp_helptext system stored procedure. sp_helptext YourView or sp_helptext YourStoredProcedure gets you the entire object definition.
You can find more information about the at sp_helptext system stored procedure here.
If you want a stored procedure to execute your query (and combining your basic query string, with your where clause), you can accomplish this by using the following code:
using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open();
string selectCommand = "EXEC sp_YourStoredProcedure #whereClause";
SqlCommand command = new SqlCommand(selectCommand, connection);
command.Parameters.Add("#whereClause", System.Data.SqlDbType.NVarChar);
command.Parameters["#whereClause"] = whereClause;
using (SqlDataReader reader = command.ExecuteReader())
{
while (reader.NextResult())
{
string location = reader.GetString(0);
string partName = reader.GetString(1);
// do something
}
}
connection.Close();
}
Edit: Example of dynamic stored procedure:
CREATE PROCEDURE sp_YourStoredProcedure
(
#whereClause NVARCHAR(MAX)
)
AS
BEGIN
DECLARE #sql AS NVARCHAR(MAX)
SET #sql = N'
select Places.Name as [Location], Parts.Name as [Part Name]
from Places inner join Parts
on Places.Id=Parts.Location '
+ #whereClause
EXEC sp_executesql #sql
END

Categories

Resources