I am trying to move all of my references to variables in SQL statements to the SqlParameter class however for some reason this query fails.
string orderBy = Request.QueryString["OrderBy"];
//Fix up the get vars
if (orderBy == null)
orderBy = "name ASC";
string selectCommand = "SELECT cat_id AS id, cat_name AS name FROM table_name ORDER BY #OrderBy";
SqlCommand cmd = new SqlCommand(selectCommand, dataConnection);
cmd.Parameters.Add(new SqlParameter("#OrderBy", orderBy));
//Create the SQLDataAdapter instance
SqlDataAdapter dataCommand = new SqlDataAdapter(cmd);
//Create the DataSet instance
DataSet ds = new DataSet();
//Get data from a server and fill the DataSet
dataCommand.Fill(ds);
Here is the error
System.Data.SqlClient.SqlException: The SELECT item identified by the ORDER BY number 1 contains a variable as part of the expression identifying a column position. Variables are only allowed when ordering by an expression referencing a column name.
It fails on this line.
dataCommand.Fill(ds);
You really have three options.
1) Use a dataview to order the result set
2) If you know the columns that can be ordered you can test for the string and then use then select the order. e.g.
For example this will work
DECLARE #orderby varchar(255)
SET #orderby = 'Name ASC'
SELECT [Your Column here ]FROM sys.tables
ORDER BY
case WHEN #orderby = 'Name ASC' Then name ELSE null END ASC,
case WHEN #orderby = 'Name DESC' Then name ELSE null END DESC,
CASE WHEN #orderby = 'Object_id ASC' then object_id ELSE null END ASC,
CASE WHEN #orderby = 'Object_id DESC' then object_id ELSE null END DESC
3) The final option is to do the same as #2 but in your C# code. Just be sure you don't just tack on the ORDER BY clause from user input because that will be vunerable to SQL injection.
This is safe because the OrderBy Url parameter "Name Desc; DROP table Users"will simply be ignored
string SafeOrderBy = "";
string orderBy = Request.QueryString["OrderBy"];
//Fix up the get vars
if (orderBy == null)
orderBy = "name ASC";
if (orderby == "name Desc")
{
SafeOrderBy == "name Desc"
}
string selectCommand = "SELECT cat_id AS id, cat_name AS name FROM table_name ORDER BY "
selectCommand += SafeOrderBy ;
Using SqlCommand is the way to prevent from sql injection. Your way of changing of the order by is the same as using sql injection in this context so it shouldnt be allowed - params are used as the constants, can't be used as column or table names.
u dont have to concatenate content of sortBy just use it as enum and depending on its value concatenate something you're sure that is safe. Like this:
If(orderBy == "some_column")
{
selectColumn += "someColumn";
}
...
I ran in to the same problem as you, however the listed solutions could not be used since my possible sort column was one of the properties of a model and that would mean way too many if-statements if the model is big.
My solution to this related problem is to use Reflection. Something like:
class MyModel {
public string MyField1 { get; set; }
public string MyField2 { get; set; }
// ...
}
//...
using System.Reflection;
// sortBy = "MyField1"
// sortDirection = "Asc";
var sql = "SELECT FROM foo WHERE bar=baz ORDER BY ";
foreach (var prop in typeof(MyModel).GetProperties())
{
if (sortBy.Equals(prop.Name))
{
sql += (prop.Name + (sortDirection.Value.Equals("Asc") ? " ASC" : " DESC"));
break;
}
}
The benefit to this solution is that no matter how my model changes, this code will support sorting by any of its properties and thus doesn't need to be changed as well.
I found an example how to do this here
you can define different sort orders in a CASE-structure and execute them appropriately to your variable value:
SELECT CompanyName,
ContactName,
ContactTitle
FROM Customers
ORDER BY CASE WHEN #SortOrder = 1 THEN CompanyName
WHEN #SortOrder = 2 THEN ContactName
ELSE ContactTitle
I didn't test it myself but it could work. You can give it a try. An obvious disadvantage is that you have to code all the order-by statements.
You're just concatenating strings. A simpler approach would be:
string orderBy = "name ASC";
string selectCommand = "SELECT cat_id AS id, cat_name AS name FROM table_name ORDER BY " + orderBy;
I'm assuming you're doing this at all because you're letting the caller decide sort field/direction, hence orderBy separated.
Parameters, as the error message sort of obliquely hints at, would be used in a WHERE clause, e.g. WHERE someColumn = #someValue etc.
Related
EX: DataTable Dt = connectionname.ReadTable("SELECT * FROM CUSTOMER");
Now I want to read the tablename which is "CUSTOMER" in query.
You can use DataTable.TableName:
string nameOfTable = Dt.TableName;
But if that property is set depends on the way you fill the DataTable.
If you specify the name of the table in DbDataAdapter.Fill this property is set:
adapter.Fill(dataSet, "CUSTOMER");
But you can always set it yourself, for example in connectionname.ReadTable.
Apart from that there is no way to extract the TableName from a sql query.
if you look at the sql string CUSTOMER could also be a view, common-table-expression, named subquery or table-valued-function. Neither a SqlDataAdapter, SqlCommand, SqlReader or DataSet/DataTable knows the name of the table if it was not set manually.
Note that a sql query can also select from multiple tables and the result set can also contain multiple result-sets(selected from multiple tables, f.e. SELECT * FROM Table1; SELECT * FROM Table2;).
In case the SQL originates from user input, you can parse the table names out of the string, like this:
string sql = "your query goes here";
int fromPos = sql.IndexOf("from ", StringComparison.InvariantCultureIgnoreCase);
if (fromPos == -1)
{
MessageBox.Show("No FROM in the SQL, unable to parse tables.");
return;
}
string fromPart = sql.Substring(fromPos);
Regex tableRegex = new Regex(#"from\s*(\w*)|join\s*(\w*)|,\s*(\w*)", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
var ms = tableRegex.Matches(fromPart);
List<string> usedTableNames = new List<string>();
foreach (Match m in ms)
{
string tableName = m.Groups[1].Value;
if (tableName == "") tableName = m.Groups[2].Value;
if (tableName == "") tableName = m.Groups[3].Value;
usedTableNames.Add(tableName.ToUpperInvariant());
}
DataTable Dt = connectionname.ReadTable("SELECT 'CUSTOMER' as TableName,* FROM CUSTOMER");
I'm currently trying to try catch the insert SQL query from my simple C# application, but when i'm trying to insert the data into database with user id which is added into userIDList parameter, the error came out saying that
The incoming request has too many parameters. The server supports a
maximum of 2100 parameters.
The userIDList sometimes will contains like 60 arrays above then the error will popped out.
My SQL CommandText will contain of
"SELECT * FROM TIME_ATTENDANCE_REPORT WHERE TRXDATETIME = #Date AND USERID IN (001,002,003,004,....)
So i think if more then certain number then the error popped out
Here are my sample code :
List<string> userIDList = new List<string>();
using (SqlCommand sqlDBComm = new SqlCommand())
{
openConnection();
SqlDataReader sqlDBReader;
sqlDBReader = null;
sqlDBComm.CommandText = "SELECT * FROM TIME_ATTENDANCE_REPORT WHERE TRXDATETIME = #Date AND USERID IN (" + string.Join(",", userIDList) + ") ORDER BY USERID ASC ";
sqlDBComm.Parameters.Add("#Date", SqlDbType.DateTime);
sqlDBComm.Parameters["#Date"].Value = GetDateFrom;
sqlDBComm.Connection = sqlDB;
sqlDBComm.CommandType = CommandType.Text;
try
{
sqlDBReader = sqlDBComm.ExecuteReader();
t.Load(sqlDBReader);
sqlDBReader.Close();
if (t.Rows.Count > 0)
{
status = "Update";
}
else
{
status = "Insert";
}
}
catch (Exception errMsg)
{
MessageBox.Show("Error Code: " + errMsg.ToString());
}
finally
{
sqlDBReader.Close();
closeConnection();
}
}
Any other solution can resolve this?
Thanks
There are many ways to solve this problem.
Instead of sending a list of IDs as seperate parameters, you can send a single #IDList parameter as a single comma separated string and let it parsed into IDs at the server side. Here is a function that I use for this (borrowed and modified from Jeff Moden's code):
CREATE FUNCTION [dbo].[iSplitter] (#Parameter VARCHAR(MAX))
RETURNS #splitResult TABLE (number INT, [value] INT)
AS
BEGIN
SET #Parameter = ','+#Parameter +',';
WITH cteTally AS
(
SELECT TOP (LEN(#Parameter))
ROW_NUMBER() OVER (ORDER BY t1.Object_ID) AS N
FROM Master.sys.All_Columns t1
CROSS JOIN Master.sys.All_Columns t2
)
INSERT #splitResult
SELECT ROW_NUMBER() OVER (ORDER BY N) AS Number,
SUBSTRING(#Parameter,N+1,CHARINDEX(',',#Parameter,N+1)-N-1) AS [Value]
FROM cteTally
WHERE N < LEN(#Parameter) AND SUBSTRING(#Parameter,N,1) = ','
RETURN
END
With this function created once, I do it like:
sqlDBComm.CommandText = #"SELECT * FROM TIME_ATTENDANCE_REPORT tar
inner Join dbo.iSplitter(#UserIdList) ul on tar.USERID = ul.[value]
WHERE TRXDATETIME = #Date
ORDER BY USERID ASC ";
sqlDBComm.Parameters.AddWithValue("#UserIdList",string.Join(",", userIDList));
This works very well for 5-6K integer ids but times out if used with 20-30K or more IDs. Then I created another alternative as a CLR procedure, and that one parses the list server side in less than a second. But I think this one is sufficient for your needs.
Another way is to send the IDs as an XML parameter and parse server side again.
Yet another way is to send a table parameter.
PS: Here is a link that shows sample code for other ways. The site is in Turkish but the codes are crystal clear in C#, separate per approach.
EDIT: XML sample using Northwind Orders table:
void Main()
{
int[] IDList = { 10265,10266,10267,10268,10269,10270,10271,10272,10273,10274,10275, 10320, 10400 };
var idsAsXML = new XElement("IDS",
from i in IDList
select new XElement("Row", new XAttribute("Id", i)));
string sql = #"
DECLARE #hDoc int;
DECLARE #tbl TABLE (Id int);
exec sp_xml_preparedocument #hDoc OUTPUT, #XML;
INSERT #tbl
SELECT *
FROM OPENXML(#hDoc, #Nodename, 1) WITH (Id int);
EXEC sp_xml_removedocument #hDoc;
select * from Orders o
where exists (select * from #tbl t where t.Id = o.OrderId) ";
DataTable tbl = new DataTable();
using (SqlConnection con = new SqlConnection(#"server=.\SQLExpress;Trusted_Connection=yes;Database=Northwind"))
{
SqlCommand cmd = new SqlCommand(sql, con);
cmd.Parameters.AddWithValue("#XML", idsAsXML.ToString());
cmd.Parameters.AddWithValue("#NodeName", "/IDS/Row");
con.Open();
tbl.Load(cmd.ExecuteReader());
con.Close();
}
//tbl.Dump(); // linqPad luxury
}
You can create a Table-Valued-Parameter and pass it as a parameter. It requires you to create a new type in your database, and will enable you to pass an array to the query and let the database treat it as a table. If this is something you do a lot, it could come in handy.
I no longer have access to the project where I implemented this but everything is available in the blog post. The code below is not tested, but I hope it can get you in the right direction.
1. Create a new type in your database:
CREATE TYPE integer_list_tbltype AS TABLE (n int NOT NULL PRIMARY KEY)
2. Pass it as parameter:
sqlDBComm.Parameters.Add("#userIds", SqlDbType.Structured)
sqlDBComm.Parameters["#userIds"].Direction = ParameterDirection.Input
sqlDBComm.Parameters["#userIds"].TypeName = "integer_list_tbltype"
sqlDBComm.Parameters["#userIds"].Value = CreateDataTable(userIDList)
3. Method for creating the parameter:
private static DataTable CreateDataTable(IEnumerable<int> ids) {
DataTable table = new DataTable();
table.Columns.Add("n", typeof(int));
foreach (int id in ids) {
table.Rows.Add(id);
}
return table;
}
4. Use it in your SQL:
... AND USERID IN (SELECT n FROM #userIds)
CreateDataTable from here:
How to pass table value parameters to stored procedure from .net code
Rest from here:
http://www.sommarskog.se/arrays-in-sql-2008.html#introduction
I am creating a small Visual Studio Windows Form application in C#, and I am using SQLite as my database. Suppose the db stores data for school club members, and they have a value for the state they live in. I am trying to create a parameterized query string so that if the user selects "All States" in my program, then it will load all members. But if they select a specific state, then it will only show members living in that state. How can I dynamically switch between, for example, WHERE USstate = 'FL' and WHERE USstate IS NOT NULL when using a parameterized query string? I want the value of the state to be dynamically chosen, but if "All States" (a check box) is selected, then all records in the db should show up.
Code example of what is not working for me:
string queryString = #"Select * from Membership WHERE USstate = #state;";
List<SQLiteParameter> paramList = new List<SQLiteParameter>();
paramList = addStateParam(paramList);
private List<SQLiteParameter> addStateParam(List<SQLiteParameter> paramList)
{
if (!checkBoxFilterAllStates.Checked)
{
string selectedState = comboBoxFilterState.Items[comboBoxFilterState.SelectedIndex].ToString();
var usState = new SQLiteParameter("#state", selectedState);
paramList.Add(usState);
}
else
{
var usState = new SQLiteParameter("#state", "IS NOT NULL");
paramList.Add(usState);
}
return paramList;
}
I tried changing USstate = #state to just USstate #state and then having #state replaced with either "= " + selectedState or IS NOT NULL, but if there is no equal sign before the #state, an syntax or logic error appears every time. However, if I do keep the equals sign, then the IS NOT NUll code will not work.
How do I dynamically fix this issue? Thanks in advance! And sorry if I was too wordy or redundant.
Select * from Membership WHERE USstate = #state
change this to
Select * from Membership WHERE USstate like #state
after that
var usState = new SQLiteParameter("#state", selectedState);
to
var usState = new SQLiteParameter("#state","%"+selectedState+"%");
and finally
var usState = new SQLiteParameter("#state", "IS NOT NULL");
to
var usState = new SQLiteParameter("#state", "%");
If the checkbox is not checked you will have > UsState = IS NOT NULL
You can't check that with a "="-Operator.If you use a sqlitecommand with your selectstring as commandtext you could than do:
SQLiteCommand cmd = new SQLiteCommand(selectstring);
cmd.Parameters.AddWithValue("#state", " = "+state);
Or
cmd.Parameters.AddWithValue("#state", " IS NOT NULL");
When you have WHERE USstate = #state in the query, then the column value gets compared to the string value passed as parameter. (It is the whole purpose of parameters to avoid being interpreted as SQL commands.)
You have to change the query itself:
if (!checkBoxFilterAllStates.Checked)
{
queryString = #"Select * from Membership WHERE USstate = #state;";
...
paramList.Add(usState);
}
else
{
queryString = #"Select * from Membership WHERE USstate IS NOT NULL;";
}
I'm using a session to store a list of itemID's that a user has added to their shopping cart. When the user goes to the 'View cart' page, I want to dnamically populate a gridview with data about the items from an sql database. (eventually with buttons to remove from cart)
I'm currently feeding the itemID's into a string that reads like an SQL statement. Does anyone know that can be used to populate a gridview? Here's the code:
//empty Session["cart"] into a list, and step through each itemID, making SQL
protected void Page_Load(object sender, EventArgs e)
{
List<int> cartList = new List<int>();
cartList = (List<int>)Session["cart"];
string queryString = ("SELECT * FROM product WHERE productID IS ");
int firstElement = cartList.First();
int lastElement = cartList.Last();
foreach (int element in cartList)
{
if (element.Equals(firstElement))
{
string idString = element.ToString();
queryString = String.Concat(queryString, idString);
}
else if (element.Equals(lastElement))
{
string idString = element.ToString();
queryString = String.Concat(queryString, " OR productID IS " + idString + ";");
}
else
{
string idString = element.ToString();
queryString = String.Concat(queryString, " OR productID IS " + idString);
}
}
Seems like an IN query would be more efficient:
cartList = (List<int>)Session["cart"];
// get comma-delimited list of IDs (e,g, "1, 2, 3")
string idList = string.Join(", ",cartList);
string queryString = string.Format("SELECT * FROM product WHERE productID IN({0}) ", idList);
Instead of keeping the cart in the session, you should probably keep the cart in the database itself, and keep just a cart-id in the session, or even attached to the user, so the user can get the cart from a different browser (say, from their tablet), too.
That way, your query is a simple JOIN, you keep the data for later, if you want to research abandoned carts, and of course - you don't risk the SQL injections your code is susceptible to.
You can use a User-Defined Table Type to fill a parameter with a list of values.
CREATE TYPE [dbo].[IntegerList] AS TABLE(
[value] [int] NOT NULL
)
It can be used in a StoredProcedure like a table, e.g. to join with your data to achieve filtering:
CREATE PROCEDURE Query
#integers dbo.IntegerList READONLY
AS
BEGIN
SELECT p.* from Product p
JOIN #integers i ON p.productid = i.value
END
From within C# it can be called with a DataTable as parameter value.
DataTable dt = new DataTable("IntegerList");
dt.Columns.Add("value", typeof(int));
foreach(var i in cartList) {
dt.Rows.Add(i);
}
using(var command = Connection.CreateCommand()) {
command.CommandText = "Query";
var param = new SqlParameter("#integers", SqlDbType.Structured);
param.TypeName = "dbo.IntegerList";
param.Value = dt;
command.Parameters.Add(param);
Connection.Open();
command.ExecuteNonQuery();
}
The created SQL command will look like this:
declare #p dbo.IntegerList
INSERT INTO #p VALUES(1)
INSERT INTO #p VALUES(2)
EXEC Query #p
I have following function in my DataAcess class, but it is not showing any result.
My code is as follow:
public List<Products> GetProduct(string productName)
{
System.Data.DataSet ds = null;
db = DBWrapper.GetSqlClientWrapper();
db.ClearParameters();
db.AddParameter(db.MakeInParam("#ProductName", DbType.String, 30, productName));
string query = #"SELECT ProductId
FROM [Products]
WHERE Name LIKE '%#ProductName%'";
ds = db.GetDataSet(query);
db.ClearParameters();
// Rest of Code
}
I also tried:
string query = #"SELECT ProductId
FROM [Products]
WHERE Name LIKE '%"+"#ProductName"+"%'";
But it runs fine without parameterized like:
string query = #"SELECT ProductId
FROM [Products]
WHERE Name LIKE '%"+productName+"%'";
How to write this with parameterized using #ProductName???
You should use
LIKE '%' + #ProductName + '%'
instead of
LIKE '%#ProductName%'
Why? Because in query, your parameter is inside quotes. In quotes, SQL will recognize it as a string literal and never sees it as a parameter.
As an alternative, you can use your % % part in your AddParameter method as
Damien_The_Unbeliever mentioned.
Try, instead:
db.AddParameter(db.MakeInParam("#ProductName", DbType.String, 30, "%" + productName + "%"));
string query = #"SELECT ProductId
FROM [Products]
WHERE Name LIKE #ProductName";
SQL doesn't look for parameters inside of literal strings. So you can make the parameter be the entire string argument for the LIKE operator.
Rather than adding a parameter, you can also use:
string query = String.Format("Select ProductId FROM Products where Name LIKE '{0}'", productName);