I want to write a C# method that creates a table and adds a column. The default values for the column is given as argument to the method. What is the safest way to quote the default value in the SQL command?
The code would be something like this:
var defaultValueThatOriginatesFromAnEvilSource = "'; DROP TABLE #Entities; SELECT '"; // the argument
database.RawExecute(string.Format("create table #Entities (somecolumn int default {0})", defaultValueThatOriginatesFromAnEvilSource));
Would a simple replace of the single quotes in the argument take care of all types of injection? Or is there a special method in C# or SQL I should use for this?
One thing to note, is that I create a temp table, and I need that to be accessible to the the next SQL statement (using the same connection).
As what shA.t said you can do this by sqlcommand in C# though it's quite hardwork if you'll be having multiple columns
but here's how to do it using stored procedure in SQL and sqlcommand in C#
in SQL
CREATE PROCEDURE sp_CreateTable
#TableName VARCHAR(50),
#ColumnName VARCHAR(50)
AS
BEGIN
DECLARE #SQLCreate VARCHAR(MAX)
SET #SQLCreate ='CREATE TABLE ' + #TableName
+ '('+#ColumnName+')'
EXEC (#SQLCreate)
END
GO
and in C#
add this using directives
using System.Data;
using System.Data.Client;
SqlConnection con = new SqlConnection("your connection string here");
con.Open();
SqlCommand cmd = new SqlCommand("sp_CreateTable");
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add("#TableName", SqlDbType.VarChar).Value = "someName";
cmd.Parameters.Add("#ColumnName", SqlDbType.VarChar).Value = "someColumnName";
cmd.ExecuteNonQuery();
con.Close();
Related
I've tried two different thoughts in resolving this issue without success. I've look at the following links here, here and here but I end up with
Object reference not set to an instance of an object.
or
Procedure or function InsertHero has too many arguments specified.
When I execute the stored procedure in SSMS, my IDs also appear off in that they are incrementing from 1110 instead of the original number set at 100 (this might be a different issue).
It seems like I'm 99% of the way there and I'm missing a small piece. What am I missing?
C#:
using (SqlConnection connection = new SqlConnection(ConnectionString))
{
connection.Open();
using (SqlCommand command = new SqlCommand("InsertHero", connection))
{
command.CommandType = CommandType.StoredProcedure;
command.Parameters.AddWithValue("#Name", hero.Name);
command.Parameters.AddWithValue("#Universe", hero.Universe);
// command.Parameters.Add("#Created_Id", SqlDbType.Int).Direction = ParameterDirection.Output;
int id = (int)command.ExecuteScalar();
}
}
Stored procedure:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[InsertHero]
#Name nvarchar(50),
#Universe nvarchar(15),
#Created_Id int OUTPUT
AS
INSERT INTO Hero
VALUES (#Name, #Universe);
SET #Created_Id = SCOPE_IDENTITY()
GO
Stored procedure (different way):
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[InsertHero]
#Name nvarchar(50),
#Universe nvarchar(15) OUTPUT
AS
INSERT INTO Hero
OUTPUT inserted.Id
VALUES (#Name, #Universe);
GO
EDIT:
Leaving this here in case someone stumbles upon it. Turned out to be a problem with the connection string so be sure to double and maybe triple check
There is no need for the #CreatedID parameter in the first example. Just change your stored procedure to
ALTER procedure [dbo].[InsertHero] #Name nvarchar(50), #Universe nvarchar(15)
as
Insert into Hero values (#Name, #Universe);
SELECT SCOPE_IDENTITY()
When you call ExecuteScalar the result is expected as the return from a SELECT statement not from an output parameter. If you want to use an output paramenter then you need to add it to the SqlCommand parameters collection and read it back its value from the same collection after executing the command.
Also do not use AddWithValue, this method while handy has numerous drawbacks documented in Can we Stop using AddWithValue Already and also in How data access code affects Database performances
Instead use Add specifying type and size of the two parameters
using (SqlConnection connection = new SqlConnection(ConnectionString))
{
connection.Open();
using (SqlCommand command = new SqlCommand("InsertHero", connection))
{
command.CommandType = CommandType.StoredProcedure;
command.Parameters.Add("#Name", SqlDbType.NVarChar, 50).Value = hero.Name;
command.Parameters.Add("#Universe", SqlDbType.NVarChar, 15).Value = hero.Universe;
int id = (int)command.ExecuteScalar();
}
}
The second method should work as well if you change back the #Universe parameter to the same definition of the first example (No output clause).
ALTER procedure [dbo].[InsertHero] #Name nvarchar(50), #Universe nvarchar(15)
I just came across a situation where i had to insert multiple records into the database. The records i had were in generic list and now i have found two ways to insert multiple records.
i just do it as usual using ExecuteNonQuery and i use foreach loop to insert every records one by one(i don't think that this is the best way to do it since this way makes multiple trips to the database to insert the record).
using (SqlCommand cmd = new SqlCommand()
{
using (SqlConnection conn = new SqlConnection()
{
cmd.Connection = conn;
foreach (var entry in entries)
{
cmd.CommandText = "INSERT INTO Employees(id, name) VALUES
(#id,#name);";
cmd.Parameters.AddWithValue("#id", entry.Id);
cmd.Parameters.AddWithValue("#name", entry.Name);
cmd.ExecuteNonQuery();
}
}
}
i tried using SqlBulkCopy class i converted the Generic List to DataTable and used SqlBulkCopy.
using (SqlConnection con = new SqlConnection(consString))
{
using (SqlBulkCopy sqlBulkCopy = new SqlBulkCopy(con))
{
sqlBulkCopy.DestinationTableName = "Employees";
con.Open();
sqlBulkCopy.WriteToServer(dt);
con.Close();
}
}
Both the methods do the intended work. But i want to know which is the best way. If there are any other better way to do this i am open to suggesstions.
Use Table-Valued Parameters (TVPs) ,which were introduced in SQL Server 2008. Before SQL Server 2008, it was not possible to pass a table variable in stored procedure as a parameter, after SQL Server now we can pass Table-Valued Parameter to send multiple rows of data to a stored procedure or a function without creating a temporary table or passing so many parameters. have a look at below example
CREATE TYPE UT_Employee AS TABLE
(
Emp_Id int NOT NULL,
EmployeeName nvarchar(MAX),
EmpSalary varchar(50),
StateId varchar(50),
CityId varchar(50)
)
CREATE PROCEDURE USP_Insert_Employee_Info
(
#Employee_Details [UT_Employee] READONLY
)
AS
BEGIN
INSERT INTO dbo.Employee
(
Emp_Id,
EmployeeName,
EmpSalary,
StateId,
CityId
)
SELECT * FROM #Employee_Details
END
Inserting multiple values into stored procedure as below
DECLARE #Employee_Details AS UT_Employee
INSERT INTO #Employee_Details
SELECT 1001,'Abcd',10000,101,21 UNION ALL
SELECT 1002,'dggg',20000,121,15 UNION ALL
SELECT 1003,'esse',22222,122,35 UNION ALL
SELECT 1004,'uyyy',44333,121,32 UNION ALL
SELECT 1005,'dghd',13233,656,87
EXEC dbo.USP_Insert_Employee_Info #Employee_Details = #Employee_Details
I'd normally use a Table-Valued Parameter for this. The explanation of how to do that is a bit long to put here, but check out Table-Valued Parameters on Microsoft Docs for how to do this.
With MySqlCommand I'm tryting to execute a .sql that updates my database.
The large file contains updates, deletes, stored procedures and all. It also uses some variables like:
SET #_count := (
SELECT COUNT(*)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = 'easycertlistfwk_audit'
AND TABLE_NAME = 'AUDIT_TITLE'
AND COLUMN_NAME = 'TI_ORDER');
IF #_count = 0 THEN
ALTER TABLE `AUDIT_TITLE`
ADD COLUMN `TI_ORDER` int(11) NULL AFTER `TI_DESCR`;
The code that I use to execute it withing c# is:
using (MySqlConnection conn = new MySqlConnection(constring))
{
using (MySqlCommand cmd = new MySqlCommand())
{
conn.Open();
cmd.CommandText = File.ReadAllText(dbUpdatesFile);
cmd.ExecuteNonQuery();
conn.Close();
}
}
the cmd.ExecuteNonQuery() method rises an Exception telling me that #_count must be defined. I guess it takes it for one of the parameters that I'm supposed to pass to the command, but in this case, all the code (and hence temporary variable declarations) is within the sql file that I'd like just to execute.
How to do this? Is there a way to ignore params checking and binding?
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'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.