Why does String.Format work but SqlCommand.Parameters.Add not? - c#

I have a Project table with two columns -- ProjectId and ProjectName -- and am writing a function that constructs and executes a SqlCommand to query the database for the ids of a Project with a given name. This command works, but is vulnerable to SQL Injection:
string sqlCommand = String.Format("SELECT {0} FROM {1} WHERE {2} = {3}",
attributeParam, tableParam, idParam, surroundWithSingleQuotes(idValue));
SqlCommand command = new SqlCommand(sqlCommand, sqlDbConnection);
using (SqlDataAdapter adapter = new SqlDataAdapter(command))
{
DataTable attributes = new DataTable();
adapter.Fill(attributes);
...
}
attributeParam, tableParam, idParam, and idValue are all strings. For example, they might be "ProjectId", "Project", "ProjectName", and "MyFirstProject", respectively. surroundWithSingleQuotes surrounds a string with '', so surroundWithSingleQuotes(idValue) == "'MyFirstProject'". I am trying to write this function as general as possible since I might want to get all of a given attribute from a table in the future.
Although the above String.Format works, this doesn't:
string sqlCommand = String.Format("SELECT #attributeparam FROM {0} WHERE " +
"#idparam = #idvalue", tableParam);
command.Parameters.Add(new SqlParameter("#attributeparam", attributeParam));
command.Parameters.Add(new SqlParameter("#idparam", idParam));
command.Parameters.Add(new SqlParameter("#idvalue",
surroundWithSingleQuotes(idValue)));
SqlCommand command = new SqlCommand(sqlCommand, sqlDbConnection);
using (SqlDataAdapter adapter = new SqlDataAdapter(command))
{
DataTable attributes = new DataTable();
adapter.Fill(attributes);
...
}
I'm not sure why. I get no error message, but when I fill my DataTable using a SqlDataAdapter, the DataTable contains nothing. Here are various approaches I've taken, to no avail:
Following this answer and Microsoft's documentation, using AddWithValue or using Parameters.Add and SqlParameter.Value.
Selectively replacing {0}, {1}, {2}, and {3}in String.Format with either the actual value or the parameter string.
In other places in my code, I've used parametrized queries (although with just one parameter) no problem.

Basically parameters in SQL only work for values - not identifiers of columns or tables. In your example, only the final parameter represents a value.
If you need to be dynamic in terms of your column and table names, you'll need to build that part of the SQL yourself. Be very careful for all the normal reasons associated with SQL injection attacks. Ideally, only allow a known whitelist of table and column values. If you need to be more general, I'd suggest performing very restrictive validation, and quote the identifiers to avoid conflicts with keywords (or prohibit those entirely, ideally).
Keep using SQL parameters for values, of course.

This is a valid statement:
SELECT * FROM SomeTable WHERE SomeColumn=#param
Whereas this is not:
SELECT * FROM #param
This means that you can use parameters for values and not for table names, view names, column names etc.

Related

How to use writing N for insert query in .NET C#?

In SQL Server
update incident_info
set description = N'ဆေးလိပ်'
where incidentid = 5
This query is correct in SQL Server and shows myanmar font correctly in the database.
I want to write correct query as above in below insert query.
sqlUtil.SqlDataUpdate(false, ("INSERT INTO Incident_Info (incidentid, incidentdate, incidenttime, description, Salesmen_id, name, phone, email, address, cost, currency, solution, status)" +
"VALUES (#incidentid, #incidentdate, #incidenttime, N'+#description+', #salesmenid, N'+#name+', #phone, #email, N'+#address+', #cost, #currency, N'+#solution+', #status)"), SysController.dicParams);
You do NOT need to prefix your SQL parameter names with a N prefix - that's useless, and probably results in an error. The N prefix is only needed when specifying a Unicode string literal in a raw SQL code snippet.
In your case, what you need to ensure is that the code that does the actual insert into SQL Server properly defines your parameters as SqlDbType.NVarChar. So in your sqlUtil class, somewhere, you have a method SqlDataUpdate that parses and executes that SQL statement you send in.
Inside there, you must ensure that code something like this is used:
using (SqlConnection conn = new SqlConnection(-your-connection-string-here-))
using (SqlCommand insertCmd = new SqlCommand(sqlQuery, conn))
{
// here, you need to ensure you define your string parameters correctly
insertCmd.Parameters.Add("#description", SqlDbType.NVarChar, 100);
.....
// and then you need to set the values - since .NET strings are inherently Unicode, no special treatment is needed
insertCmd.Parameters["#description"].Value = SysController.dicParams.......
.....
// open, execute, close
conn.Open();
int rowsInserted = insertCmd.ExecuteNonQuery();
conn.Close();
}
This must be done inside your sqlUtil class - you cannot influence this from the "outside" by simply adding a N prefix to your parameter names...

Parameterized SQL queries don't work

I have posted this question already because I had problems with the "LIKE" statement but now I realized there are problems with parameterized statements at all. Here is my code:
for example when I write:
sqlCmd = new SqlCommand(#"SELECT #cusId,#cusName FROM " + form1.getTable() + " WHERE #cusId LIKE #filter", connection);
sqlCmd.Parameters.AddWithValue("cusId", form1.cusId.Text);
sqlCmd.Parameters.AddWithValue("cusName", form1.cusName.Text);
sqlCmd.Parameters.AddWithValue("filter", form1.filterType().Trim() + "%");
sqlDatAdapter = new SqlDataAdapter(sqlCmd.CommandText, connection);
sqlDatAdapter.Fill(datTable);
form1.setDataGrid = datTable;
Somehow I always get
"Must declare the scalar variable #..."
for each variable when I fill the data table. I tried that already with
sqlCmd.ExecuteNonQuery();
and it seems to work in this case (when I update a database it works fine) but I need to bind the values to my DataGridView.
EDIT: Even when I just try to write something like:
... WHERE cusId.Text = #cusId
I get the same error
As noted, you aren't working with parameters quite right. Your SqlCommand should be something more like this:
SqlCommand sqlcmd = new SqlCommand(#"SELECT cusId,cusName FROM " + form1.getTable() + " WHERE cusId LIKE #filter", connection);
Note how the columns you are trying to select aren't parameters (so don't include '#' symbol).
When you add parameters you DO need to add the '#' symbol. Something like this:
sqlCmd.Parameters.AddWithValue("#cusId", form1.cusId.Text);
You can't parameterize your table and column names . You can only parameterize your values.
If you really wanna get your table and column names dynamically, you can use dynamic SQL but which is a bad idea.
Best way to get your column names dynamically is creating a strong validation for your column names or creating a black list for them.
Using ExecuteNonQuery doesn't effect anything since it's because just execute your query, does not return any data. Also use using statement to dispose your connections, commands and adapter.
Here an example;
string str = string.Format("SELECT {0}, {1} FROM {2} WHERE {0} LIKE #filter",
form1.cusId.Text,
form1.cusName.Text,
form1.getTable());
sqlCmd = new SqlCommand(str, connection);
sqlCmd.Parameters.AddWithValue("#filter", form1.filterType().Trim() + "%");
sqlDatAdapter = new SqlDataAdapter(sqlCmd);
sqlDatAdapter.Fill(datTable);
But I have to say again, you really need strong validation or black list for your column and table name when you try get them outside of your program.
I used AddWithValue in this example but this method might be dangerous in some cases. Using .Add() overloads to specify it's db type and parameter size implicitly would be better.
Finally solved it! I tried:
using (sqlDatAdapter = new SqlDataAdapter(sqlCmd.CommandText, connection))
{
sqlDatAdapter.SelectCommand.Parameters.Add("#filter", SqlDbType.Int, 25).Value = CusIdEnter;
sqlDatAdapter.Fill(datTable);
form1.setDataGrid = datTable;
}
and now it works! One had to bind the parameters to the Adapter

Convert C# SQL Loop to Linq

I have a list Called ListTypes that holds 10 types of products. Below the store procedure loops and gets every record with the product that is looping and it stores it in the list ListIds. This is killing my sql box since I have over 200 users executing this constantly all day.
I know is not a good architecture to loop a sql statement, but this the only way I made it work. Any ideas how I can make this without looping? Maybe a Linq statement, I never used Linq with this magnitude. Thank you.
protected void GetIds(string Type, string Sub)
{
LinkedIds.Clear();
using (SqlConnection cs = new SqlConnection(connstr))
{
for (int x = 0; x < ListTypes.Count; x++)
{
cs.Open();
SqlCommand select = new SqlCommand("spUI_LinkedIds", cs);
select.CommandType = System.Data.CommandType.StoredProcedure;
select.Parameters.AddWithValue("#Type", Type);
select.Parameters.AddWithValue("#Sub", Sub);
select.Parameters.AddWithValue("#TransId", ListTypes[x]);
SqlDataReader dr = select.ExecuteReader();
while (dr.Read())
{
ListIds.Add(Convert.ToInt32(dr["LinkedId"]));
}
cs.Close();
}
}
}
Not a full answer, but this wouldn't fit in a comment. You can at least update your existing code to be more efficient like this:
protected List<int> GetIds(string Type, string Sub, IEnumerable<int> types)
{
var result = new List<int>();
using (SqlConnection cs = new SqlConnection(connstr))
using (SqlCommand select = new SqlCommand("spUI_LinkedIds", cs))
{
select.CommandType = System.Data.CommandType.StoredProcedure;
//Don't use AddWithValue! Be explicit about your DB types
// I had to guess here. Replace with the actual types from your database
select.Parameters.Add("#Type", SqlDBType.VarChar, 10).Value = Type;
select.Parameters.Add("#Sub", SqlDbType.VarChar, 10).Value = Sub;
var TransID = select.Parameters.Add("#TransId", SqlDbType.Int);
cs.Open();
foreach(int type in types)
{
TransID.Value = type;
SqlDataReader dr = select.ExecuteReader();
while (dr.Read())
{
result.Add((int)dr["LinkedId"]);
}
}
}
return result;
}
Note that this way you only open and close the connection once. Normally in ADO.Net it's better to use a new connection and re-open it for each query. The exception is in a tight loop like this. Also, the only thing that changes inside the loop this way is the one parameter value. Finally, it's better to design methods that don't rely on other class state. This method no longer needs to know about the ListTypes and ListIds class variables, which makes it possible to (among other things) do better unit testing on the method.
Again, this isn't a full answer; it's just an incremental improvement. What you really need to do is write another stored procedure that accepts a table valued parameter, and build on the query from your existing stored procedure to JOIN with the table valued parameter, so that all of this will fit into a single SQL statement. But until you share your stored procedure code, this is about as much help as I can give you.
Besides the improvements others wrote.
You could insert your ID's into a temp table and then make one
SELECT * from WhatEverTable WHERE transid in (select transid from #tempTable)
On a MSSQL this works really fast.
When you're not using a MSSQL it could be possible that one great SQL-Select with joins is faster than a SELECT IN. You have to test these cases by your own on your DBMS.
According to your comment:
The idea is lets say I have a table and I have to get all records from the table that has this 10 types of products. How can I get all of this products? But this number is dynamic.
So... why use a stored procedure at all? Why not query the table?
//If [Type] and [Sub] arguments are external inputs - as in, they come from a user request or something - they should be sanitized. (remove or escape '\' and apostrophe signs)
//create connection
string queryTmpl = "SELECT LinkedId FROM [yourTable] WHERE [TYPE] = '{0}' AND [SUB] = '{1}' AND [TRANSID] IN ({2})";
string query = string.Format(queryTmpl, Type, Sub, string.Join(", ", ListTypes);
SqlCommand select = new SqlCommand(query, cs);
//and so forth
To use Linq-to-SQL you would need to map the table to a class. This would make the query simpler to perform.

How to use a SqlCommand Parameter to specify the schema name for a select query

The name of the Schema in our database is dynamic. Why won't the following work?
public void ReadVersion(string connString, string schemaName)
{
string selectCommand = "SELECT major FROM [#SchemaName].[version]");
using (SqlConnection sqlConn = new SqlConnection(connString))
{
using (SqlCommand cmd = new SqlCommand(selectCommand, sqlConn))
{
sqlConn.Open();
cmd.Parameters.AddWithValue("#SchemaName", schemaName);
object result = cmd.ExecuteScalar();
}
}
}
When the command is executed, the parameter value is not substituted. Is this a limitation of the SqlCommand Parameters?
You can't do the following in plain SQL (so ignoring all of the ADO.Net overheads):
DECLARE #SchemaName sysname
SET #SchemaName = 'dbo'
SELECT major FROM #SchemaName.[version]
And the reason is simple. What SQL wants after FROM is a name. What you're trying to give it is a string. SQL Server doesn't randomly start peering inside of strings to see if they resemble some other construct.
To your sanitation query, assuming that you're keeping the schema names sane, just use a simple regex on the schema name before you trust it (e.g. ^[A-Z][A-Z0-9]+$ should be enough for most uses). Make sure you use a whitelist (allowed characters) rather than a blacklist.
Is this a limitation of the SqlCommand Parameters?
Yes, it is a limitation. In ADO.NET you cannot use parameters for the schema (things like table and column names). Your best bet is to use string concatenation and obviously as a result your own filtering for injection.

i'm lost: what is wrong with this ado.net code?

well, the question is clear i hope, the code is this:
string sql = "delete from #tabelnaam";
SqlCommand sc = new SqlCommand();
sc.Connection = getConnection();
sc.CommandType = CommandType.Text;
sc.CommandText = sql;
SqlParameter param = new SqlParameter();
param.Direction = ParameterDirection.Input;
param.ParameterName = "#tabelnaam";
param.Value = tableName;
sc.Parameters.Add(param);
OpenConnection(sc);
sc.ExecuteScalar();
tableName is supplied to this function.
I get the exception:
Must declare the table variable #tabelnaam
IIRC, you cant use a substitute the table name for a parameter.
Rather build the SQL string containing the correct table name.
Make to changes
rather than using paramter use this
string sql = string.format( "delete from {0}",tableName);
make use of executenonquery intead of ExecuteScalar
sc.ExecuteNonQuery();
As mentioned by others, you can't parameterise the table name.
However, as you rightly mention in comments on other answers, using simple string manipulation potentialy introduces a SQL injection risk:
If your table name input is fro an untrusted source, such as user input, then using this:
string sql = string.format( "DELETE FROM {0}",tableName);
leaves you open to the table name "myTable; DROP DATABASE MyDb" being inserted, to give you:
DELETE FROM myDb; DROP DATABASE MyDB
The way round this is to delimit the table name doing something such as this:
string sql = string.format("DELETE FROM dbo.[{0}]", tableName);
in combination with checking that the input does not contain either '[' or ']'; you should probably check it also doesn't contain any other characters that can't be used as a table name, such as period and quotes.
I dont think you can parameterize the table name. From what I have read you can do it via Dynamic sql and calling sp_ExecuteSQL.
Your SQL is incorrect, you are deleting from a table variable yet you haven't defined that variable.
Update: as someone has pointed out, you are trying to dynamically build a query string but have inadvertantly used SQL parameters (these do not act as place holders for string literals).
More here:
Parameterise table name in .NET/SQL?
You cannot parameterise the table name, you have to inject it into the command text.
What you can and should do is protect yourself against SQL injection by delimiting the name thus:
public static string Delimit(string name) {
return "[" + name.Replace("]", "]]") + "]";
}
// Construct the command...
sc.CommandType = CommandType.Text;
sc.CommandText = "delete from " + Delimit(tableName);
sc.ExecuteNonQuery();
See here and here for more background info.

Categories