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
Related
I'm writing a C# class library in which one of the features is the ability to create an empty data table that matches the schema of any existing table.
For example, this:
private DataTable RetrieveEmptyDataTable(string tableName)
{
var table = new DataTable() { TableName = tableName };
using var command = new SqlCommand($"SELECT TOP 0 * FROM {tableName}", _connection);
using SqlDataAdapter dataAdapter = new SqlDataAdapter(command);
dataAdapter.Fill(table);
return table;
}
The above code works, but it has a glaring security vulnerability: SQL injection.
My first instinct is to parameterize the query like so:
using var command = new SqlCommand("SELECT TOP 0 * FROM #tableName", _connection);
command.Parameters.AddWithValue("#tableName", tableName);
But this leads to the following exception:
Must declare the table variable "#tableName"
After a quick search on Stack Overflow I found this question, which recommends using my first approach (the one with sqli vulnerability). That doesn't help at all, so I kept searching and found this question, which says that the only secure solution would be to hard-code the possible tables. Again, this doesn't work for my class library which needs to work for arbitrary table names.
My question is this: how can I parameterize the table name without vulnerability to SQL injection?
An arbitrary table name still has to exist, so you can check first that it does:
IF EXISTS (SELECT 1 FROM sys.objects WHERE name = #TableName)
BEGIN
... do your thing ...
END
And further, if the list of tables you want to allow the user to select from is known and finite, or matches a specific naming convention (like dbo.Sales%), or belongs to a specific schema (like Reporting), you could add additional predicates to check for those.
This requires you to pass the table name in as a proper parameter, not concatenate or token-replace. (And please don't use AddWithValue() for anything, ever.)
Once your check that the object is real and valid has passed, then you will still have to build your SQL query dynamically, because you still won't be able to parameterize the table name. You still should apply QUOTENAME(), though, as I explain in these posts:
Protecting Yourself from SQL Injection in SQL Server - Part 1
Protecting Yourself from SQL Injection in SQL Server - Part 2
So the final code would be something like:
CREATE PROCEDURE dbo.SelectFromAnywhere
#TableName sysname
AS
BEGIN
IF EXISTS (SELECT 1 FROM sys.objects
WHERE name = #TableName)
BEGIN
DECLARE #sql nvarchar(max) = N'SELECT *
FROM ' + QUOTENAME(#TableName) + N';';
EXEC sys.sp_executesql #sql;
END
ELSE
BEGIN
PRINT 'Nice try, robot.';
END
END
GO
If you also want it to be in some defined list you can add
AND #TableName IN (N't1', N't2', …)
Or LIKE <some pattern> or join to sys.schemas or what have you.
Provided nobody has the rights to then modify the procedure to change the checks, there is no value you can pass to #TableName that will allow you to do anything malicious, other than maybe selecting from another table you didn’t expect because someone with too much access was able to create before calling the code. Replacing characters like -- or ; does not make this any safer.
You could pass the table name to the SQL Server to apply quotename() on it to properly quote it and subsequently only use the quoted name.
Something along the lines of:
...
string quotedTableName = null;
using (SqlCommand command = new SqlCommand("SELECT quotename(#tablename);", connection))
{
SqlParameter parameter = command.Parameters.Add("#tablename", System.Data.SqlDbType.NVarChar, 128 /* nvarchar(128) is (currently) equivalent to sysname which doesn't seem to exist in SqlDbType */);
parameter.Value = tableName;
object buff = command.ExecuteScalar();
if (buff != DBNull.Value
&& buff != null /* theoretically not possible since a FROM-less SELECT always returns a row */)
{
quotedTableName = buff.ToString();
}
}
if (quotedTableName != null)
{
using (SqlCommand command = new SqlCommand($"SELECT TOP 0 FROM { quotedTableName };", connection))
{
...
}
}
...
(Or do the dynamic part on SQL Server directly, also using quotename(). But that seems overly and unnecessary tedious, especially if you will do more than one operation on the table in different places.)
Aaron Bertrand's answer solved the problem, but a stored procedure is not useful for a class library that might interact with any database. Here is the way to write RetrieveEmptyDataTable (the method from my question) using his
answer:
private DataTable RetrieveEmptyDataTable(string tableName)
{
const string tableNameParameter = "#TableName";
var query =
" IF EXISTS (SELECT 1 FROM sys.objects\n" +
$" WHERE name = {tableNameParameter})\n" +
" BEGIN\n" +
" DECLARE #sql nvarchar(max) = N'SELECT TOP 0 * \n" +
$" FROM ' + QUOTENAME({tableNameParameter}) + N';';\n" +
" EXEC sys.sp_executesql #sql;\n" +
"END";
using var command = new SqlCommand(query, _connection);
command.Parameters.Add(tableNameParameter, SqlDbType.NVarChar).Value = tableName;
using SqlDataAdapter dataAdapter = new SqlDataAdapter(command);
var table = new DataTable() { TableName = tableName };
Connect();
dataAdapter.Fill(table);
Disconnect();
return table;
}
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
I'm having a simple problem but cant find a solution:
I'm creating a row in my table client, but I don't know how recovery the id of the table that I just created, for example:
ALTER proc [dbo].[spinsert_client]
#idclient int output,
#name varchar(20),
#surname varchar(40),
as
insert into client(name,surname)
values (#name,#surname)
here I insert a client, now I want recovery that exact same idclient to insert "products" with it without have to manually search this client , I tried recovering the last row of the client table but I realise that if more than one person is using the same database in different computers it can be a problem, so I need create a client and recovery his id at the same time (it is an assumption I don't know). Im using sql server and Visual studio with c#
sorry for my bad English and thanks for the attention
edit-------------------
solution:
ALTER proc [dbo].[spinsert_client]
#idclient int output,
#name varchar(20),
#surname varchar(40)
as
insert into client(name,surname)
values (#name,#surname)
Select ##IDENTITY as newId;
them in my c# code:
rpta= SqlCmd.ExecuteScalar().ToString();
The best way is to use the OUTPUT clause. Here is an example that just captures the new id:
ALTER proc [dbo].[spinsert_client] (
#idclient int output,
#name varchar(20),
#surname varchar(40)
)
as
begin
declare #output table (idclient int);
insert into client(name, surname)
output inserted.idclient into #output;
values (#name, #surname);
select *
from #output;
end; -- spinsert_client
In your C# code you have somewhere a sql Statement defined:
string sqlStatement = "INSERT INTO ... (field list) OUTPUT INSERTED.yourfieldwithid values (value list) ";
and use it with ExecuteScalar() for getting the result value (I assume you know how to use Connection and command object)
You can do this:
string query = "INSERT INTO client" +
" (name, surname)" +
" VALUES (#Name, #Surname);" +
" SELECT SCOPE_IDENTITY();";
using (var dbconn = new SqlConnection("your connection string here") )
using (var dbcm = new SqlCommand(query, dbconn) )
{
dbcm.Parameters.Add("#Name", SqlDbType.VarChar, 20).Value = "name value";
dbcm.Parameters.Add("#Surname", SqlDbType.VarChar, 40).Value = "surname value";
dbconn.Open();
var insertedID = (int)dbcm .ExecuteScalar();
}
Check this SO post explaining every way of retrieving the inserted id from the table.
To answer your question, both OUTPUT_CLAUSE and IDENT_CURRENT can be used in this scenario but i would recomment IDENT_CURRENT because an OUTPUT clause will return rows to the client even if the statement encounters errors and is rolled back.
Use it like:
using (SqlCommand com = new SqlCommand("INSERT INTO cient(name, surname)"+
"VALUES (#Name, #Surname) SELECT IDENT_CURRENT('client'); ", con))
I would not suggest SCOPE_IDENTITY or ##IDENTITY because it may return wrong values (null) if you're not using SQL Server 2008 R2 SP2 or higher ( source - last row from the page.) especially for your requirement (inserting the value in some other table).
I am using MySQL database with C# to develop an application.Using MySQL Server 5.0 and odbc connector.
In some cases I am required to execute ddl commands such as ALTER TABLE or CREATE TABLE to manipulate the database. In these cases I need to use the IF EXISTS command to check the database before I execute commands. I write below commands that execute without any problem in Navicat or Workbench, but do not work when send this commands with application by ExecuteNoneQury methods.
what is wrong?
use db;
drop procedure if exists sp_update ;
delimiter //
create procedure sp_update()
begin
if not exists( SELECT column_name FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'tab' AND COLUMN_NAME = 'col' and table_schema = 'db') then
ALTER TABLE `tab` ADD COLUMN `col` int(11) NULL DEFAULT NULL ;
end if;
end//
delimiter ;
call sp_update();
drop procedure if exists sp_update ;
C# Command :
public override int ExecuteNoneQuery(string commandText)
{
int obTemp = 0;
Conn = new MySqlConnection(Connection.ConnectionString);
try
{
MySqlCommand MySqlCommand = new MySqlCommand(commandText, Conn);
if (Conn.State == ConnectionState.Closed)
{
Conn.Open();
}
obTemp = MySqlCommand.ExecuteNonQuery();
}
finally
{
if (Conn.State == ConnectionState.Open)
{
Conn.Close();
}
}
return obTemp;
}
"delimiter" is not MySQL syntax.. It is a convinience function for the mysql command line client and is only understood by it (well, some GUI clients mimic the behavior too, to be able to run scripts that are originally thought for command line client).
But, you do not need "delimiter" in any code executed by connectors. Using it will result in syntax error like the one you got.
I solved my own problem. I needed to split up my sql command into two parts.
Part 1 create procedure:
drop procedure if exists sp_update ;
create procedure sp_update()
begin
if not exists( SELECT column_name FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'tab' AND COLUMN_NAME = 'col' and table_schema = 'db') then
ALTER TABLE `tab` ADD COLUMN `col` int(11) NULL DEFAULT NULL ;
end if;
end
Part 2:
call sp_update();
drop procedure if exists sp_update ;
Send each command to MySQL separately.
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.