This question already has answers here:
Parameterize an SQL IN clause
(41 answers)
Closed 9 years ago.
In my application written in C# , I am writing a SQL query. Following is the query
SELECT [Resource No_] where [Resource No_] In (#resources)
#resources is user input parameters having one or more that one strings.
My query is failing without showing an error
According to me, query is failing because in #resources parameter following is being passed
"'123,'124','125'"
(there are 2 inverted commas in the beginning and at the end and that is failing my query).
[Resource No_] is of type NVARCHAR in the database.
After Googling, I have found some help on this topic but all are applicable when [Resource No_] is of type Integer
While I don't agree with the selected answer (or many of the tricky answers) for the "duplicate question", here is an answer to it which shows an approach very similar with my following recommendation.
(I've voted to close this question as a duplicate, because there are such answers, even if buried.)
Only one SQL value can be bound to any given placeholder.
While there ways to send all the data as "one value", I'd recommend creating the placeholders dynamically: it's simple, clean, and will work reliably in most cases.
Consider this:
ICollection<string> resources = GetResources();
if (!resources.Any()) {
// "[Resource No_] IN ()" doesn't make sense
throw new Exception("Whoops, have to use different query!");
}
// If there is 1 resource, the result would be "#res0" ..
// If there were 3 resources, the result would be "#res0,#res1,#res2" .. etc
var resourceParams = string.Join(",",
resources.Select((r, i) => "#res" + i));
// This is NOT vulnerable to classic SQL Injection because resourceParams
// does NOT contain user data; only the parameter names.
// However, a large number of items in resources could result in degenerate
// or "too many parameter" queries so limit guards should be used.
var sql = string.Format("SELECT [Resource No_] where [Resource No_] In ({0})",
resourceParams);
var cmd = conn.CreateCommand();
cmd.CommandText = sql;
// Assign values to placeholders, using the same naming scheme.
// Parameters prevent SQL Injection (accidental or malicious).
int i = 0;
foreach (var r in resources) {
cmd.Parameters.AddWithValue("#res" + i, r);
i++;
}
Use a user defined table type to accept your parameter, then a JOIN clause in your select to limit the results set. See http://social.msdn.microsoft.com/Forums/en-US/2f466c93-43cd-436d-8a7c-e458feae71a0/how-to-use-user-defined-table-types
Do something like
resources.Aggregate(r1, r2 => r1 + "', '" + r2 + "'")
and pass the list in one string.
Related
Recent bug report states that a method being called is crashing the service causing it to restart. After troubleshooting, the cause was found to be an obnoxious Oracle SQL call with thousands of strings passed. There is a collection of strings being passed to a method from an external service which often is more than 10,000 records. The original code used a where clause on the passed collection using the LIKE keyword, which I think is really, really bad.
public IList<ContainerState> GetContainerStates(IList<string> containerNumbers)
{
string sql =
String.Format(#"Select CTNR_NO, CNTR_STATE FROM CONTAINERS WHERE CTRN_SEQ = 0 AND ({0})",
string.Join("OR", containerNumbers
.Select(item => string.Concat(" cntr_no LIKE '", item.SliceLeft(10), "%' ")))
);
return DataBase.SelectQuery(sql, MapRecordToContainerState, new { }).ToList();
}
Clarification of in house methods used which may be confusing:
DataBase.SelectQuery is an internal library method using generics which gets passed the sql string, a function to map the records to .NET objects, and the parameters being passed and returns an IEnumerable of Objects of type retuned by the Mapping function.
SliceLeft is an extension method from another internal helper library that just returns the first part of a string up to the number of characters specified by the parameter.
The reason that the LIKE statement was apparently used, is that the strings being passed and the strings in the database only are guaranteed to match the first 10 characters. Example ("XXXX000000-1" in the strings being passed should match a database record like "XXXX000000-8").
I believed that the IN clause using the SUBSTR would be more efficent than using multiple LIKE clauses and replaced the code with:
public IList<ContainerRecord> GetContainerStates(IList<string> containerNumbers)
{
string sql =
String.Format(#"Select CTNR_NO, CNTR_STATE FROM CONTAINERS WHERE CTRN_SEQ = 0 AND ({0})",
string.Format("SUBSTR(CNTR_NO, 1, 10) IN ({0}) ",
string.Join(",", containerNumbers.Select(item => string.Format("\'{0}\'", item.SliceLeft(10) ) ) )
)
);
return DataBase.SelectQuery(sql, MapRecordToContainerState, new { }).ToList();
}
This helped slightly, and there were fewer issues in my tests, but when there are huge amounts of records passed, there is still an exception thrown and core dumps occur, as the SQL is longer than the server can parse during these times. The DBA suggests saving all the strings being passed to a temporary table, and then joining against that temp table.
Given that advice, I changed the function to:
public IList<ContainerRecord> GetContainerStates(IList<string> containerNumbers)
{
string sql =
#"
CREATE TABLE T1(cntr_num VARCHAR2(10));
DECLARE GLOBAL TEMPORARY TABLE SESSION.T1 NOT LOGGED;
INSERT INTO SESSION.T1 VALUES (:containerNumbers);
SELECT
DISTINCT cntr_no,
'_IT' cntr_state
FROM
tb_master
WHERE
cntr_seq = 0
AND cntr_state IN ({0})
AND adjustment <> :adjustment
AND SUBSTR(CTNR_NO, 1, 10) IN (SELECT CNTR_NUM FROM SESSION.T1);
";
var parameters = new
{
#containerNumbers = containerNumbers.Select( item => item.SliceLeft(10)).ToList()
};
return DataBase.SelectQuery(sql, MapRecordToContainerState, parameters).ToList();
}
Now I'm getting a "ORA-00900: invalid SQL statement". This is really frustrating, how can I properly write a SQL Statement that will put this list of strings into a temporary table and then use it in a SELECT Statement to return the list I need?
There are couple possible places could cause this error, it seams that the "DECLARE GLOBAL TEMPORARY" is a JAVA API, I don't think .net has this function. Please try "Create global temporary table" instead. And, I don't know whether your internal API could handle multiple SQLs in one select sql. As far as I know, ODP.net Command class can only execute one sql per call. Moreover, "create table" is a DDL, it therefore has its own transaction. I can't see any reason we should put them in the same sql to execute. Following is a sample code for ODP.net,
using (OracleConnection conn = new OracleConnection(BD_CONN_STRING))
{
conn.Open();
using (OracleCommand cmd = new OracleCommand("create global temporary table t1(id number(9))", conn))
{
// actually this should execute once only
cmd.ExecuteNonQuery();
}
using (OracleCommand cmd = new OracleCommand("insert into t1 values (1)", conn)) {
cmd.ExecuteNonQuery();
}
// customer table is a permenant table
using (OracleCommand cmd = new OracleCommand("select c.id from customer c, t1 tmp1 where c.id=tmp1.id", conn)) {
cmd.ExecuteNonQuery();
}
}
I have a serious problem of formatting queries, it may result in SQL injection too, I saw some similar qstns, but not sure how could i use it in C# as I am new to it. I use c#,Odbc command
I have 3 strings like
qry ="select description from TableA" , qryOrder = " order by description" , qryAppend = " where ID = '{0}' order by description\", _selectedPlantID" provided _selectedId is another variable, Now I want to use these variables to form diff queries at different scenarios, for eg, qry + qry order , or qry + qryAppend.
Since _selectedPlantId is also needed, I use string.Format as :
_cmd.CommandText = "string.Format(\"" + qry + qryAppend + ")";
But its not working. any solution ?
Error is SQL syntax error with quotes
thanks in advance !!
Simply put, this should make it work. You'll need two variables (bool), I'll explain later why:
var shouldOrder = true;
var shouldAppend = true;
_cmd.CommandText = String.Format(
"{0} {1} {2}",
qry,
shouldOrder ? qryOrder : String.Empty,
shouldAppend ? qryAppend : String.Empty
);
These two variables (shouldOrder and shouldAppend) will help you with the "diff queries at different scenarios" as you've said.
Providing these variables with true or false will change what text goes into the String.Format and will change query accordingly.
So, if you use shouldOrder = false; the query command won't get the order part. Setting shouldAppend = false; will avoid including the extra part (append) into the SQL command.
Now, be careful!
This won't solve your SQL injection problem. I've just shown a quick fix.
To avoid SQL injections, you'll have to change your SQL command and you cannot use String.Format anymore.
To understand how to do that, take a look into DGibbs comment.
This question already has answers here:
Avoiding SQL injection without parameters
(21 answers)
Closed 9 years ago.
If I change my select from
String insSQL2
= "select * from Produtos where nome = '" + txtBuscaNome.Text + "'"
To
String insSQL2
= "select * from Produtos where nome = ''" + txtBuscaNome.Text + "''"
Will it prevent sql injection?
No.
SQL injection isn't about creatively using quote characters. It's about treating input as data instead of as code. Take a look at a classic SQL injection vulnerability:
"SELECT * FROM Users WHERE Id = " + someValue;
It may intuitively look like you're using someValue as a data value, but you're actually using it as actual SQL code. The SQL engine doesn't see this as a value parameter, it sees it as part of the command being executed. That code should just be a value, but it can be anything. And you'd be executing whatever code is supplied.
Thinking of it in this way, it becomes clear that you should never execute user-supplied code in your application.
The alternative is to treat the user input as values in pre-defined code. That way you control the complete scope of the code and users are only supplying values. Which would look more like this:
"SELECT * FROM Users WHERE Id = #id";
Now the SQL engine sees that parameter (#id) and expects you to supply a value for that parameter. In ADO.NET it might look something like:
someCommand.Parameters.AddWithValue("#id", someValue);
Now the SQL engine knows that this is a data value and not code, so it treats it as data instead of executing it.
No, it won't prevent sql injection.
Use parameterized sql:
var insSQL2 = "select * from Produtos where nome = #nome";
var connection = new SqlConnection(/* connection info */);
var command = new SqlCommand(insSQL2, connection);
command.Parameters.AddWithValue("#nome", txtBuscaNome.Text);
This question already has answers here:
List of tables used in an SQL Query
(5 answers)
Closed 9 years ago.
I need extract from simple string that represent an sql query the tables that are used on the query without execute the query itself in C#.
Example:
string strQuery = "SELECT * FROM table1 LEFT JOIN (SELECT * FROM table2) tt WHERE tt.name IN (SELECT name FROM table3)";
ArrayList arrUsedTables = GetUsedTablesFromQuery(strQuery);
and after this line the object arrUsedTables would contain:
table1,table2,table3
Remember that the query may be much complicated!
Without going to the DB you can't know for certain the names of the tables used in the query.
What if your query uses a view or a stored procedure?
Without consulting the database, these are transparent to the consumer.
The only way to be certain is to query the list of the tables from the database and then to attempt to parse them from your inline sql.
You will have to add references and directives for the following assemblies:
using Microsoft.Data.Schema.ScriptDom;
using Microsoft.Data.Schema.ScriptDom.Sql;
using System.IO;
Then, you may create the GetUsedTablesFromQuery method:
private static ArrayList GetUsedTablesFromQuery(string strQuery)
{
var parser = new TSql100Parser(true);
IList<ParseError> errors = new List<ParseError>();
using (TextReader r = new StringReader(strQuery))
{
var result = parser.GetTokenStream(r, out errors);
var tables = result
.Select((i, index) => (i.TokenType == TSqlTokenType.From) ? result[index + 2].Text : null)
.Where(i => i != null)
.ToArray();
return new ArrayList(tables);
}
}
You can certainly use a SQL parser such as ANTLR, as described in this question and answer, in order to get a full parse of the SQL, and then extract the table names.
Another option is to execute some raw SQL to get the execution plan of the query (using the instructions here). The execution plan is in XML, and then you can use Linq to XML to query the plan for all Table attributes on any ColumnReference tag.
Suppose that I want to create an SQL SELECT statement dynamically with reflection on primary key. I search in the table for primary keys and then, I make the statement.
Problem is, I don't know the type of fields that compose the primary key before getting them. So, if it's a string or date, I must add quotation marks but not if it's an int.
Atm, I am doing like that :
var type = field.GetType().Name;
if (type.ToLower().StartsWith("string") || type.ToLower().StartsWith("date"))
{
field = "\"" + field + "\"";
} else if (type.ToLower().StartsWith("char"))
{
field = "\'" + field + "\'";
}
With this code, I can handle some SQL types but there are a lot more.
My problem is that it's combined with LinQ. I got a DataContext object and a generic type table from the context. And context.ExecuteQuery only allows parameters to be passed has values. I also tried with Dynamic LinQ but I got the same problem
Does anyone know a better solution?
That is simply the wrong way to write SQL. Parameterize it and all these problems evaporate (as do problems with "which date format to use", etc. And of course the biggie: SQL injection.
Then it just becomes a case of adding #whatever into the TSQL, and using cmd.Parameters.AddWithValue("whatever", field) (or similar).
Update (from comments): since you mention you are using DataContext.ExecuteQuery, this becomes easier: that method is fully parameterized using the string.Format convention, i.e.
object field = ...;
var obj = db.ExecuteQuery<SomeType>(
"select * from SomeTable where Id = {0}", field).ToList(); // or Single etc
No string conversions necessary.
(the last parameter is a params object[], so you can either pass multiple discreet terms, or you can populate an object[] and pass that, if the number of terms is not fixed at compile-time; each term in the array maps by (zero-based) index to the {0}, {1}, {2}... etc token in the query)
Have you tried with parameters? For instance if you are using SQLServer as a database and you want to do this query:
"SELECT * FROM someTable WHERE id = " + field;
Then you should use sometething like this:
"SELECT * FROM someTable WHERE id = #field"
and add parameter to your command:
SqlParameter param1 = new SqlParameter("#field", field);
command.Parameters.Add(param1);
EDIT: Watch out that for different database providers the syntax for the SQL query is different, the same for the Access would be
"SELECT * FROM someTable WHERE id = ?";
command.Parameters.AddWithValue("field", field);