Using an arbitrary number of parameters in ORMLite Query - c#

I am in the process of fixing some of our bad sql queries that are vulnerable to sql injection. Most are straight queries with no inputs, but our search field takes search terms that are not parameterised. A snippet is below:
using (var db = ORMLite.Open())
{
StringBuilder sb = new StringBuilder();
sb.Append("select * from column1, column2");
if (terms.Count() > 0)
{
sb.Append("where (column1 like '%#term0%' or " + column2 + " like '%#term0%') ");
if (terms.Count() > 1)
{
for (int i = 1; i < terms.Count(); i++)
{
sb.Append("and (column1 like '%#term" + i + "%' or " + column2 + " like '%#term" + i + "%') ");
}
}
}
List<POCO> testQuery = db.Select<POCO>(sb.ToString());
}
The #term components are where I intend to use parameters (they used to be of the form '" + term[i] + '", but any term with malicious code would just be inserted. When I move to my select statement, I would like to add the parameters. This is normally done as so:
List testQuery = db.Select(sb.ToString(), new { term0 = "t", term1 = "te", term2 = "ter" });
However I can have any number of terms (term.count() is the number of terms). How can I pass in an anonymous object with any number of terms? Or is there a better method?

I'm looking for almost the same thing in Postgresql. Based on this SO question
the answer looks like "you have to perform multiple queries."
I can get the unique row IDs from my table given the partial parameterized
query, and then directly paste those unique IDs back into the query -- since those
row IDs will be safe.
Here's an example of what I mean, but the c# is probably wrong (sorry):
string query = "SELECT unique_id FROM table WHERE (column1 LIKE '%#term%' OR column2 LIKE '%#term%')";
string safeIDs;
List uniqueRowIDs = db.Select(query, new {term = term[0]});
for (int i = 1; i < terms.Count(); i++) {
// Loop to narrow down the rows by adding the additional conditions.
safeIDs = uniqueRowIDs.Aggregate( (current, next) => current + string.Format(", '{0}'", next) );
uniqueRowIDs = db.Select(
query + string.Format(" AND unique_id IN ({0})", safeIDs),
new {term = term[i]});
}
// And finally make the last query for the chosen rows:
safeIDs = uniqueRowIDs.Aggregate( (current, next) => current + string.Format(", '{0}'", next) );
List testQuery = db.Select(string.Format("SELECT * FROM table WHERE unique_id IN ({0});", safeIDs));
Another option for your case specifically could be to just get all of the values that
are like term0 using a parameterized query and then, within the c# program, compare
all of the results against the remaining terms the user entered.

Related

Must declare a scalar variable (C# and OleDb)

I know this question have been asked several times, but none of answers has helped me resolving this issue.
So, i'm writing data transfer utility, copying data from one table of OleDb database to table of another OleDb database.
I have read all the data from the source database, and i'm trying to write, but always gets this error
Must declare the scalar variable "#CategoryID"
Here's the code
// generating the insert string below
string insert = "INSERT INTO Categories VALUES (";
for(int i = 0; i < cols.Length; i++)
{
string coma = ", ";
if (i == cols.Length - 1)
coma = " )";
insert += "#" + cols[i] + coma;
}
try
{
while (src_reader.Read()) // reading from source database
{
dstcmd.CommandText = insert;
for (int i = 0; i < cols.Length; i++)
{
string temp = "#" + cols[i]; // cols is array of column names
dstcmd.Parameters.AddWithValue(temp, src_reader[cols[i]]);
// for debug purposes... below is screenshot of error
Console.Write(temp + " " + src_reader[cols[i]] + "\n");
}
Console.WriteLine("");
// point of error
dstcmd.ExecuteNonQuery();
}
}
catch(Exception ex)
{
Console.WriteLine(ex);
}
Here's the screenshot of error.
CategoryID is the first column of the table and hence the first value that is being inserted.
Any help will be appreciated. If i missed any information or something does not make sense, please do let me know.
Try changing this part:
// generating the insert string below
string insert = "INSERT INTO Categories VALUES (";
for(int i = 0; i < cols.Length; i++)
{
string coma = ", ";
if (i == cols.Length - 1)
coma = " )";
insert += "#" + cols[i] + coma;
}
to
// generating the insert string below
string insert = "INSERT INTO Categories VALUES (";
for(int i = 0; i < cols.Length; i++)
{
string coma = ", ";
if (i == cols.Length - 1)
coma = " )";
insert += "?" + coma;
}
You don't need to use parameter names in VALUES, but just ? placeholders. However, make sure the order of parameters when you add them matches the order of columns in the table.
Also, it may be better to explicitly specify the column list in the INSERT clause, like:
string insert = "INSERT INTO Categories (Col1, Col2, Col3, etc.) VALUES (";
See if you want to make that column names list dynamically generated too. But I suggest to get it working for the static column list first and then convert it to dynamic version.
Also, if you don't specify the column name list for INSERT you will have specify values for all columns.

Use a database value to call a variable with the same name

I need to update a table called Calculated in my database, but because I have so many values that I have stored in my system as variables to add/update in the table, I created a separate table in the database called Database Relationships.
This Database Relationships table has a column called Calculated Value which holds all the field names of the Calculated table. The other column, System Field holds all the names of variables that I have created and given values to, which are of all string type and that relate to the corresponding Calculated Value
So by using a FOREACH loop
OleDbDataAdapter relationshipAdapter = new OleDbDataAdapter(relationshipCmd);
DataTable relationshipTable = new DataTable();
relationshipAdapter.Fill(relationshipTable);
string update = "Update [Calculated] SET ";
int i = 0;
int len = relationshipTable.Rows.Count;
foreach (DataRow drr in relationshipTable.Rows)
{
string calc = drr["Calculated Field"].ToString();
var sys = drr["System Field"].ToString();
if (i == len - 1)
{
update += "[" + calc + "] = " + sys + ";";
}
else
{
update += "[" + calc + "] = " + sys + ", ";
}
i++
}
update += "WHERE [NSN] = '" + NSN + "';";
OleDbCommand updateCmd = new OleDbCommand(update);
But this is not working, because after some debugging(?) I did a simple Console.WriteLine(sys) and it would print out the string in the System Field column, instead of the variable with the same name in the system.
I am currently using Microsoft Access as my database platform.
I think having "intermediate" table for temporary storing values in runtime for future saving in another table sounds little bid complicated for me.
If you want to map variables at runtime with column name in the database - use dictionary for example:
Dictionary<string, string> myValues = new Dictionary<string, string>();
Using in the application:
myValues["SomeColumn"] = "your value";
Then saving data to database will be:
var updateCmd = new OleDbCommand();
var query = new StringBuilder();
foreach(KeyValuePair<string, string> value in myValues)
{
string columnName = value.Key;
query.AppendLine($", {columnName} = ?");
var param = new OleDbParameter("#v", value.Value);
// Name of parameter not important, only order
}
if(query.Length > 0)
{
query.Remove(0, 1); // Remove first ',' character
query.Insert("Update [Calculated] SET ");
query.AppendLine("$WHERE [NSN] = '{NSN}';");
}
updateCmd.CommandText = query.ToString();
Very important: you need to use OleDbParameter for passing values to the query.
In you foreach loop, use this:
if (i == len - 1)
{
update += "[" + calc + "] = " + this.GetType().GetProperty(sys).GetValue(this, null)+ ";";
}
else
{
update += "[" + calc + "] = " + this.GetType().GetProperty(sys).GetValue(this, null)+ ", ";
}
The code above assumes that the variables are scoped in the same scope where you are generating your Sql.
In loop , you use the condition:
if (i == len - 1)
but you never change "len" or "i" value in the code.

SQL find rows with key specified in list, C#

I want to make a sql query in C# that finds all rows with a key that is specified in a list. Can I do this with one query? I suppose that is much more efficent than my solution which finds one item at the time inside a for loop, se below:
foreach (int i in list)
{
string Q = "... where pk = " + i.ToString();
using (SqlCommand CM = new SqlCommand(Q, C))
{
using (SqlDataReader R = CM.ExecuteReader())
{
while (R.Read())
{
...
}
}
}
}
list contains different in values.
Thanks in advance!
Replace
string Q = "... where pk = " + i.ToString();
with
string Q = "... where pk IN ('" + string.Join("','", list)+"')";
then you can remove the loop. The result should look like ... where pk IN ('1','2','3')
You can use the IN keyword and pass your list by converting it to a comma seperated string in your query.
Something like
string Q = "select * from tablename where pk IN " + (comma seperated list here);

C# & SQL Server CE : treat words after defined symbols (+ or &) as a seperate word in search

I'm working on a simple translation application based on C# & SQL Server CE 3.5
I have a search textbox that searches certain columns in database through textBox1.Text with normal SQL query [SELECT.. LIKE '% %']
What I want to achieve :
I want to search for all the words after certain symbols (+ for example) in all locations in database , so they don't need to be written in the full context (word after word as they exist in database)
In other words :
I want to split words after certain symbols , so that the program search for each word independently (search the word before symbol and each word after symbol separately)
Example:
If I tried to search for the value "burden of proof" , I've to write it in the previous context, but for the user this will not apply. So I want him to put a symbol in-between the two words he is willing to search for (namely he should search for "burden+proof")
Picture 1 : http://i.imgur.com/sd5Y5B7.jpg , Picture 2 : http://i.imgur.com/gVj41xP.jpg
Edit - my search button code :
sqlcmd = new SqlCeCommand
("SELECT * FROM T1 WHERE EnglishWord like '%" + textBox1.Text + "%' OR EnglishDesc like '%" + textBox1.Text + "%' OR ArabicWord like '%" + textBox1.Text + "%' OR ArabicDesc like '%" + textBox1.Text + "%' ", sqlcon);
try
{
listView1.Items.Clear();
sqldr = sqlcmd.ExecuteReader();
while (sqldr.Read())
{
ListViewItem item = new ListViewItem(sqldr["ID"].ToString());
item.SubItems.Add(sqldr["EnglishWord"].ToString());
item.SubItems.Add(sqldr["EnglishDesc"].ToString());
item.SubItems.Add(sqldr["ArabicDesc"].ToString());
item.SubItems.Add(sqldr["ArabicWord"].ToString());
item.SubItems.Add(sqldr["Subject"].ToString());
listView1.Items.Add(item);
}
listView1.Enabled = true;
label7.Text = listView1.Items.Count.ToString();
}
catch (SqlCeException ex)
{
MessageBox.Show("Error: " + ex.Message, "Something wrong");
}
Ok. So you got different words from the answer here.
Split textbox.text into other textboxes
You have this string[] result that you know if the length is 1 than it didnt split at all. If it is more than 1 than the original text was split with + or whatever.
I am not sure exactly what you are trying to achieve but you could do this.
if(result.Length == 1)
{
//Select * from database where column = result[0]
}
else
{
foreach(string s in result)
{
//Select * from database where column like '%'+s+'%' (better if you do with parameters)
}
}
Answer by : Richard Deeming # CodeProject
Assuming you want to find all records where each word appears in at least one of the four columns, something like this should work:
sqlcmd = sqlcon.CreateCommand();
string[] words = textBox1.Text.Split(new[] { '+' }, StringSplitOptions.RemoveEmptyEntries);
StringBuilder query = new StringBuilder("SELECT * FROM T1 WHERE 1 = 1");
for (int index = 0; index < words.Length; index++)
{
string wordToFind = words[index];
string parameterName = "#word" + index;
sqlcmd.Parameters.AddWithValue(parameterName, "%" + wordToFind + "%");
query.AppendFormat(" AND (EnglishWord Like {0} OR EnglishDesc Like {0} OR ArabicWord Like {0} OR ArabicDesc Like {0})", parameterName);
}
sqlcmd.CommandText = query.ToString();
This will also fix the SQL Injection[^] vulnerability in your code.
If the user searches for burden+proof, the resulting query will look something like this:
SELECT
*
FROM
T1
WHERE
1 = 1
And
(
EnglishWord Like #word0
Or
EnglishDesc Like #word0
Or
ArabicWord Like #word0
Or
ArabicDesc Like #word0
)
And
(
EnglishWord Like #word1
Or
EnglishDesc Like #word1
Or
ArabicWord Like #word1
Or
ArabicDesc Like #word1
)
/*
Parameters:
- #word0 = '%burden%'
- #word1 = '%proof%'
*/

Insert a new record in access using oledb

Can I add a new record into access database which has 15 columns? It's very unconvinient for me to using this sql:
insert into Account(col1,col2,col3,col4,col5,col6,col7,col8,col9,col10,col11,col12,col13,col14,col15) Values(val1,val2,val3,val4,val5,val6,val7,val8,val9,val10,val11,val12,val13,val14,val15)
There are 2 List consist columns name (name) and values (info). Example:
name[1]="col1";
info[1]="val1";
Is the ordinary of columns name important? Can I use this Sql:
insert into Account(col1) Values(val1)
After that I use an "update" sql and a "for-loop" to set values?
I also get an error:
The changes you requested to the table were not successful because they would create duplicate values in the index, primary key, or relationship. Change the data in the field or fields that contain duplicate data, remove the index, or redefine the index to permit duplicate entries and try again.
Thank you so much. :)
Do one insert before for loop and update in the for loop.
A sample code is given below.
string Query = "insert into Account(col1) Values(val1)";
// execute it
for(int i=1;i<name.count -1;i++)
{
Query = "Update Account set " + name[i] + " = " + info[i] + " where col1 = val1";
// execute
}
Code not tested.
public static void insert(String[] name ,String[] info)
{
String Names = "";
String Cols = "";
for(int i=0;i < name.Length;i++)
{
Names += (Names == "" ? "" : ", ") + name[i];
Cols += (Cols == "" ? "" : ", ") + "'" + info[i] + "'";
}
String Query = "insert into Account (" + Names + ") Values (" + Cols + ")";
Console.WriteLine(Query);
}
Code not tested and note that I added single quotes for values assuming that all values are string type.

Categories