SQL Parameters and Question Marks - c#

I am making the switch from classic ASP to ASP.NET. And I am having some trouble doing some basic stuff that I used to do easily with the old method. Below is a handy ASP function that I used to execute scalar queries with a few lines.
FUNCTION ExecuteScalarParams(SQLQuery, Parameter_Array)
Set cmd1 = Server.CreateObject("ADODB.Command")
cmd1.ActiveConnection = con
cmd1.CommandText = SQLQuery
cmd1.CommandType = 1
FOR ParamCount = 0 TO UBOUND(Parameter_Array)
cmd1.Parameters(ParamCount) = Parameter_Array(ParamCount)
NEXT 'ParamCount
Set rstScalar = cmd1.Execute()
IF NOT rstScalar.EOF THEN
arrScalar = rstScalar.GetRows()
IF UBOUND(arrScalar,2) = 0 THEN
ExecuteScalarParams = arrScalar(0,0)
ELSE
ExecuteScalarParams = NULL
END IF
ELSE
ExecuteScalarParams = NULL
END IF
rstScalar.Close
Set rstScalar = Nothing
Set cmd1 = Nothing
END FUNCTION
I used to pass a SQL query with question marks as place holders for the parameters like this:
SELECT TOP 1 UserName FROM Members WHERE (Created>?) AND (AdminLevel=?);
I would then set up a parameters array and pass it on to the function:
MyArray = ARRAY("1-JAN-2012",1)
The parameters in the array would replace the question marks in the query string in the order they appear.
I am trying to mimic this function in C# but I am stuck in the part where I have to pass the parameters. So far I got to the point where I have to used named place holders such as #Created and #AdminLevel instead of the question marks and then I have to set up parameter objects like this:
SqlParameter param = new SqlParameter();
param.ParameterName = "#AdminLevel";
param.Value = 1;
Is there a way to pass the parameters without having to set the parameter names and simply use question marks and the order in which they appear to specify which parameter goes where?

edit: as pointed out by Dana the MSDN Docs for Parameters shows you need to use named parameters for SqlClient but can use positional parameters for OleDb/ODBC.
You can make adding parameters a lot easier by using the code below; it's the skeleton I use but I'm sure there's a better way of doing it.
You still need to used named parameters, but you can simulate your question marks to an extent by naming them #a, #b, #c.. - positional parameters are fine until you get more than a handful of parameters and you have to constantly count the number of question marks to figure out which parameter value is being applied where, often resulting in mistakes.
using (var con = new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString))
{
con.Open();
{
using (var command = con.CreateCommand())
{
command.Connection = conn;
command.CommandText = "SELECT * FROM [dbo].[Table] WHERE [c1] = #a AND [c2] = #b";
command.Parameters.AddWithValue("#a", aVal);
command.Parameters.AddWithValue("#b", bVal);
command.CommandType = CommandType.Text;
using (var reader = command.ExecuteReader())
{
if (reader.HasRows)
{
while (reader.Read())
{
///
}
}
else
{
///
}
}
}
}
}

Related

Can't pass an array to oracle procedure

I've created a procedure for test this problem and it works right in oracle developer. There is a typed named "dizi" (array and varchar2). And procedure has input parameter. I'm trying to pass an array to this to this procedure as a parameter in c#. I've searched a lot but i couldn't solve the problem. The error is: "Not all veriables bound"
public void InsertQuestion(List<string> area_list)
{
quest_areas = area_list.ToArray();
command = new OracleCommand();
command.Connection = connect;
connect.Open();
var arry = command.Parameters.Add("area_array",OracleDbType.Varchar2);
arry.Direction = ParameterDirection.Input;
arry.Size = quest_areas.Length;
arry.CollectionType = OracleCollectionType.PLSQLAssociativeArray;
arry.Value = quest_areas;
command.BindByName = true;
command.CommandText ="TESTPROCEDURE(:area_array)";
command.CommandType = CommandType.StoredProcedure;
command.ExecuteNonQuery();
connect.Close();
}
Here is my procedure (it is just for test but i'll use something like that)
CREATE OR REPLACE PROCEDURE TESTPROCEDURE (t_in IN dizi)
IS
BEGIN
FOR i IN 1..t_in.count LOOP
dbms_output.put_line(t_in(i));
END LOOP;
END;
I've got code that successfully passes array down to oracle sprocs. Takes a slightly different approach to yours. Not entirely sure how much is relevant, but in case it helps my code:
uses the correct name parameter name (t_in in your case)
doesn't bother setting the size of the parameter
creates an object array that is the correct length and copies the contents across into it (ie from quest_areas in your case)
then sets this object array as the Value for the command parameter
doesn't use bind variables when calling the proc, rather just uses the proc name by itself as the CommandText.
That said, I suspect your problem might be around your use of a bind variable when calling the procedure. What happens if you just set 'TESTPROCEDURE' as the CommandText?
Or go the other way and put change it into a proper anonymous PLSQL block 'begin TESTPROCEDURE(:area_array); end;' and change the CommandType to CommandType.Text (as just suggested by Wernfried while I was typing...)
Update
public void InsertQuestion(List<string> area_list)
{
var input_array = area_list.Select(s => (object)s).ToArray();
command = new OracleCommand();
command.Connection = connect;
connect.Open();
var arry = command.Parameters.Add("area_array",OracleDbType.Varchar2);
arry.Direction = ParameterDirection.Input;
arry.CollectionType = OracleCollectionType.PLSQLAssociativeArray;
arry.Value = input_array;
command.CommandText ="TESTPROCEDURE";
command.CommandType = CommandType.StoredProcedure;
command.ExecuteNonQuery();
connect.Close();
}

why we use "#" while inserting or updating or deleting data in sql table

I just want to know why we use "#" while inserting or updating or deleting data in sql table, as I used #name like below.
cmd.Parameters.Add(new SqlParameter("#fname", txtfname.Text));
See: SqlParameter.ParameterName Property - MSDN
The ParameterName is specified in the form #paramname. You must
set ParameterName before executing a SqlCommand that relies on
parameters.
# is used by the SqlCommand so that the value of the parameter can be differentiatd in the Command Text
SqlCommand cmd = new SqlCommand("Select * from yourTable where ID = #ID", conn);
^^^^^^^
//This identifies the parameter
If # is not provided with the parameter name then it is added. Look at the following source code, (taken from here)
internal string ParameterNameFixed {
get {
string parameterName = ParameterName;
if ((0 < parameterName.Length) && ('#' != parameterName[0])) {
parameterName = "#" + parameterName;
}
Debug.Assert(parameterName.Length <= TdsEnums.MAX_PARAMETER_NAME_LENGTH, "parameter name too long");
return parameterName;
}
}
EDIT:
If you don't use # sign with the parameter then consider the following case.
using (SqlConnection conn = new SqlConnection(connectionString))
{
using (SqlCommand cmd = new SqlCommand())
{
conn.Open();
cmd.CommandText = "SELECT * from yourTable WHERE ID = ID";
cmd.Connection = conn;
cmd.Parameters.AddWithValue("ID", 1);
DataTable dt = new DataTable();
SqlDataAdapter da = new SqlDataAdapter(cmd);
da.Fill(dt);
}
}
The above will fetch all the records, since this will translate into SELECT * from yourTable WHERE 1=1, If you use # above for the parameter ID, you will get only the records against ID =1
OK, no offense to the posters before me but I will try to explain it to you as simple as possible, so even a 7 year old understands it. :)
From my experience '#' in .SQL is used when you are "just not making it clear what exact data type or exact name will be used". "Later" you are pointing out what the exact value of '#' is.
Like, say, someone has developed some huge .SQL query which contains, say, the name of every person who has received it.
SELECT column_name,column_name FROM table_name WHERE column_name = #YOURNAME;
#YOURNAME = 'John Doe';
So, in this case, it's easier for everyone to just write their name at #YOURNAME and it will automatically convert the query to (upon launch):
SELECT column_name,column_name FROM table_name WHERE column_name = 'John Doe';
P.S: I am sorry for my syntax errors and incorrect terminology but I am sure you should have understood it by now. :)
Variables and parameters in SQL Server are preceded by the # character.
Example:
create procedure Something
#Id int,
#Name varchar(100)
as
...
When you create parameter objects in the C# code to communicate with the database, you also specify parameter names with the # character.
(There is an undocumented feature in the SqlParameter object, which adds the # to the parameter name if you don't specify it.)

how to prevent an SQL Injection Attack?

Currently, I am creating an SQL Query by doing something like
string SQLQuery = "SELECT * FROM table WHERE ";
foreach(word in allTheseWords)
{
SQLQuery = SQLQuery + " column1 = '" + word + "' AND";
}
I understand that this can lead to an SQL Injection attack. I don't know how to pass an array as a parameter
where report in #allTheseWords
===========
I am using SQL Server 2012
Unfortunately, you cannot pass an array as a parameter without adding a user-defined type for table-valued parameters. The simplest way around this restriction is to create individually named parameters for each element of the array in a loop, and then bind the values to each of these elements:
string SQLQuery = "SELECT * FROM table WHERE column1 in (";
for(int i = 0 ; i != words.Count ; i++) {
if (i != 0) SQLQuery += ",";
SQLQuery += "#word"+i;
}
...
for(int i = 0 ; i != words.Count ; i++) {
command.Parameters.Add("#word"+i, DbType.String).Value = words[i];
}
You can also create a temporary table, insert individual words in it, and then do a query that inner-joins with the temp table of words.
Here is the recommendation from Microsoft:
Use Code Analysis to detect areas in your Visual Studio projects that are prone to sql injection;
Refer to the article on how to reduce risk of attack:
On short they talk about:
using a stored procedure.
using a parameterized command string.
validating the user input for both type and content before you build the command string.
Btw, you can enable static analysis as part of your build process and configure it so that when a security rule is broken, the build also breaks. Great way to make sure your team writes secure code!
Using ADO you can do it with the help of params
SqlConnection Con = new SqlConnection(conString);
SqlCommand Com = new SqlCommand();
string SQLQuery = "SELECT * FROM table WHERE ";
int i=1;
foreach(word in words)
{
Com.Parameters.Add("#word"+i.ToString(),SqlDbType.Text).Value = word;
SQLQuery = SQLQuery + " column1 = '#word"+i.ToString()+"' AND ";
i++;
}
Com.CommandText =SQLQuery;
For SQL Server, you'd use a Table-Valued Parameter. SQL has one structure that represents a collection of multiple items of the same type. It's called a table. It doesn't have arrays.
Of course, your supposed updated query:
where report in #allTheseWords
Isn't equivalent to your original query, but may be closer to the intent. In the query constructed using AND, you're saying that the same column, in the same row has to be equal to multiple different words. Unless all of the words are equal, this will never return any rows. The updated query answers whether any of the words match, rather than all.
You need to use prepared statements. The way those are handled is that you write your query and put placeholders for the values you want to use. Here's an example:
SELECT * FROM table WHERE column1 = #word
You then have to go through a prepare phase where the SQL engine knows it will need to bind parameters to the query. You can then execute the query. The SQL engine should know when and how to interpret the parameters you bind to your query.
Here's some code to do that:
SqlCommand command = new SqlCommand(null, rConn);
// Create and prepare an SQL statement.
command.CommandText = "SELECT * FROM table WHERE column1 = #word";
command.Parameters.Add ("#word", word);
command.Prepare();
command.ExecuteNonQuery();
I combine the use of params with HtmlEncoding(to get rid of special characters where not needed). Give that a shot.
using (SqlConnection conn = new SqlConnection(conString))
{
string sql = "SELECT * FROM table WHERE id = #id";
using (SqlCommand cmd = new SqlCommand(sql, conn))
{
cmd.paramaters.AddWithValue("#id", System.Net.WebUtility.HtmlEncode(id));
conn.Open();
using (SqlDataReader rdr = cmd.ExecuteReader())
{
}
}
}

Is a cursor parameter really to be declared differently than a "regular" parameter?

OK, nobody seems to know how to solve the problem I'm having looping through a cursor/result set for storage into a List, so I'm going to break it down into pieces and try to slog through it that way. So, first of all:
I add SQL Parameters to an OracleCommand object this way (works fine):
cmd.Parameters.Add("ABCID", _ABCID);
cmd.Parameters["ABCID"].Direction = ParameterDirection.Input;
cmd.Parameters["ABCID"].DbType = DbType.String;
IOW, when I add the param, I pass the name of the parameterized portion of the SQL ("ABCID" above) and a value to give it (_ABCID is a variable that has been assigned, let's say, "42").
However, when adding a Cursor (output) param, it seems to want, not a value (such as an initialized cursor object), but simply the data type:
cmd.Parameters.Add("cur", Devart.Data.Oracle.OracleDbType.Cursor);
cmd.Parameters["cur"].Direction = ParameterDirection.Output;
(I tried both ways, and neither one works, so...?)
Verily/thus, my question is: Is this really the correct way of declaring a Cursor parameter to be outputted back for traversal/access?
I'm using the brand new version of DevArt DotConnect components (6.80.332), VS 2010, .NET 4
Updated:
Here's the code in more context:
public void PopulateCurrentUserRoles(String AUserName, List<String> ACurrentUserRoles) {
_UserName = AUserName;
String query = "select roleid from ABCrole where ABCid = :ABCID";
Devart.Data.Oracle.OracleCommand cmd = new Devart.Data.Oracle.OracleCommand(query, con);
cmd.CommandType = CommandType.Text;
int _ABCID = GetABCIDForUserName();
cmd.Parameters.Add("cur", Devart.Data.Oracle.OracleDbType.Cursor);
cmd.Parameters["cur"].Direction = ParameterDirection.Output;
cmd.Parameters.Add("ABCID", _ABCID);
cmd.Parameters["ABCID"].Direction = ParameterDirection.Input;
cmd.Parameters["ABCID"].DbType = DbType.String;
//cmd.ExecuteNonQuery(); blows up: "illegal variable name/number"
//cmd.ExecuteCursor(); " "
//cmd.ExecuteReader(); " "
Devart.Data.Oracle.OracleCursor oraCursor =
(Devart.Data.Oracle.OracleCursor)cmd.Parameters["cur"].Value;
Devart.Data.Oracle.OracleDataReader odr = oraCursor.GetDataReader(); // "Object reference not set to an instance of an object"
while (odr.Read()) {
ACurrentUserRoles.Add(odr.GetString(0));
}
}
the following is from the Oracle Data Provider for .NET Developer's Guide. yes, I know, "Devart". Nonetheless, It suggests the following:
Be careful with your parameter typing declaration.
add that cursor/output parameter to the Parameters collection before
any others.
As a long shot... my guide shows a OracleDbType.RefCursor but not a OracleDbType.Cursor. If DevArt has RefCursor, try that. In visual studio, what type does .NET think that parameter is? This question is not as dumb as I used to think.
... On the other hand, if the parameter is set as an OracleDbType.Char type by setting the OracleDbType property, the output data is returned
as an OracleString type. If both DbType and OracleDbType properties
are set before the command execution, the last setting takes affect.
. . .
"An application should not bind a value for output parameters; it is
the responsibility of ODP.NET to create the value object and populate
the OracleParameter Value property with the object. When binding by
position (default) to a function, ODP.NET expects the return value to
be bound first, before any other parameters."
EDIT:
Based on #Clay's self-answer... so there is no parameter specified for the output, rather one simply does this: OracleDataReader odr = cmd.ExecuteReader();
Straight from the horse's mouth (the DevArt folks):
_UserName = AUserName;
// From the DevArtisans:
String query = "select roleid from ABCrole where ABCid = :ABCID";
Devart.Data.Oracle.OracleCommand cmd = new Devart.Data.Oracle.OracleCommand(query, con);
cmd.CommandType = CommandType.Text;
int _ABCID = GetABCIDForUserName();
cmd.Parameters.Add("ABCID", _ABCID);
cmd.Parameters["ABCID"].Direction = ParameterDirection.Input;
cmd.Parameters["ABCID"].DbType = DbType.String;
Devart.Data.Oracle.OracleDataReader odr = cmd.ExecuteReader();
while (odr.Read()) {
ACurrentUserRoles.Add(odr.GetString(0));
}
To quote Casey and the Sonshine Banned, "That's the way, Uh huh Uh huh, I like it, Uh huh Uh huh"; actually, I can't stand that crap, but I do kind of relate to that sentiment right about now.

OleDbParameters and Parameter Names

I have an SQL statement that I'm executing through OleDb, the statement is something like this:
INSERT INTO mytable (name, dept) VALUES (#name, #dept);
I'm adding parameters to the OleDbCommand like this:
OleDbCommand Command = new OleDbCommand();
Command.Connection = Connection;
OleDbParameter Parameter1 = new OleDbParameter();
Parameter1.OleDbType = OleDbType.VarChar;
Parameter1.ParamterName = "#name";
Parameter1.Value = "Bob";
OleDbParameter Parameter2 = new OleDbParameter();
Parameter2.OleDbType = OleDbType.VarChar;
Parameter2.ParamterName = "#dept";
Parameter2.Value = "ADept";
Command.Parameters.Add(Parameter1);
Command.Parameters.Add(Parameter2);
The problem I've got is, if I add the parameters to command the other way round, then the columns are populated with the wrong values (i.e. name is in the dept column and vice versa)
Command.Parameters.Add(Parameter2);
Command.Parameters.Add(Parameter1);
My question is, what is the point of the parameter names if parameters values are just inserted into the table in the order they are added command? The parameter names seems redundant?
The Problem is that OleDb (and Odbc too) does not support named parameters.
It only supports what's called positional parameters.
In other words: The name you give a parameter when adding it to the commands parameters list does not matter. It's only used internally by the OleDbCommand class so it can distinguish and reference the parameters.
What matters is the order in which you add the parameters to the list. It must be the same order as the parameters are referenced in the SQL statement via the question mark character (?).
But here is a solution that allows you to use named parameters in the SQL statement. It basically replaces all parameter references in the SQL statement with question marks and reorders the parameters list accordingly.
It works the same way for the OdbcCommand class, you just need to replace "OleDb" with "Odbc" in the code.
Use the code like this:
command.CommandText = "SELECT * FROM Contact WHERE FirstName = #FirstName";
command.Parameters.AddWithValue("#FirstName", "Mike");
command.ConvertNamedParametersToPositionalParameters();
And here is the code
public static class OleDbCommandExtensions
{
public static void ConvertNamedParametersToPositionalParameters(this OleDbCommand command)
{
//1. Find all occurrences of parameter references in the SQL statement (such as #MyParameter).
//2. Find the corresponding parameter in the commands parameters list.
//3. Add the found parameter to the newParameters list and replace the parameter reference in the SQL with a question mark (?).
//4. Replace the commands parameters list with the newParameters list.
var newParameters = new List<OleDbParameter>();
command.CommandText = Regex.Replace(command.CommandText, "(#\\w*)", match =>
{
var parameter = command.Parameters.OfType<OleDbParameter>().FirstOrDefault(a => a.ParameterName == match.Groups[1].Value);
if (parameter != null)
{
var parameterIndex = newParameters.Count;
var newParameter = command.CreateParameter();
newParameter.OleDbType = parameter.OleDbType;
newParameter.ParameterName = "#parameter" + parameterIndex.ToString();
newParameter.Value = parameter.Value;
newParameters.Add(newParameter);
}
return "?";
});
command.Parameters.Clear();
command.Parameters.AddRange(newParameters.ToArray());
}
}
Parameter NAMES are generic in the SQL support system (i.e. not OleDb specific). Pretty much ONLY OleDb / Odbc do NOT use them. They are there because OleDb is a specific implementation of the generic base classes.

Categories