I'm trying to parameterize a raw SQL query for an Oracle synonym (non-entity) in EF 4 and I am having some problems. Currently I am doing something like the code below, based on some examples that I saw:
string term="foo";
OracleParameter p = new OracleParameter("#param1", term);
object[] parameters = new object[] { p };
var model = db.Database.SqlQuery<ProjectTask>("SELECT * FROM (SELECT * FROM web_project_task_vw WHERE project_num like '%#param1%') WHERE rownum<=100", parameters).ToList();
Running this doesn't return any results. If I replace the parameter with something like
"SELECT * FROM web_project_task_vw WHERE project_num like '%"+term+"%'"
it returns the results I expect, but this is obviously a SQL injection risk.
Can anyone point me in the right direction for how parameters are supposed to work in EF 4 for an Oracle DB?
Thanks.
First, like Mohammed wrote, you need to prefix the parameter with ':', but not as you define it, just in the query.
Second, you are currently searching not for the value of the parameter but rather strings that contains the string #param1. So surround the value of the parameter with % and you should get a result.
So it should look something like this:
string term="foo";
OracleParameter p = new OracleParameter("param1", term);
object[] parameters = new object[] { p };
var model = db.Database.SqlQuery<ProjectTask>("SELECT * FROM (SELECT * FROM web_project_task_vw WHERE project_num like '%'||:param1||'%') WHERE rownum<=100", parameters).ToList();
Your p might have an incorrect parameter name; the name should be param1, not #param1. Your query is also incorrect; replace '%#param1%' with '%:param1%'.
Related
I have a table with a lot of employees in it, every person has a Name column with their full name.
I then want to do a query similar to this when searching for people:
SELECT * FROM Employees WHERE Name LIKE '%' + #value1 + '%' AND Name LIKE '%' + #value2 +'%' AND so forth...
for an arbitrary array of values.
My Dapper code would look something like this:
public IEnumerable<Employee> Search(string[] words)
{
using var connection = CreateConnection();
connection.Query<Employee>("SELECT * etc.", words);
}
Is there ANY way to do this with SQL without resorting to string concatenation, and the risk of SQL Injection attacks that follows?
Caveat: I don't know how Dapper actually passes an array to the query, which limits my creative ideas for working around this :-D
And also: Changing the Table structure is, unfortunately, out of the question. And I'd rather avoid fetching every single person into .Net memory and doing the filtering there.
Is there ANY way to do this with SQL without resorting to string concatenation, and the risk of SQL Injection attacks that follows?
Because the set of where conditions is not fixed you will need to build the query dynamically. But that does not mean you cannot parameterise the query, you just build the parameter list alongside building the query. Each time a word from the list add to the condition and add a parameter.
As Dapper doesn't directly include anything that takes a collection of DbParameter, consider using ADO.NET to get an IDataReader and then Dappter's
IEnumerable<T> Parse<T>(this IDataReader reader)
for the mapping.
Such a builder would be very roughly
var n = 0;
for (criterion in cirteria) {
var cond = $"{crition.column} like #p{n}";
var p = new SqlPatameter($"#p{n}", $"%{crition.value}%";
conditions.Add(cond);
cmd.Parameters.Add(p);
}
var sql = "select whetever from table where " + String.Join(" and ", conditions);
cmd.CommandText = sql;
var reader = await cmd.ExecuteReaderAsync();
var res = reader.Parse<TResult>();
For performance reasons, it's much better to do this as a set-based operation.
You can pass through a datatable as a Table-Value Parameter, then join on that with LIKE as the condition. In this case you want all values to match, so you need a little bit of relational division.
First create your table type:
CREATE TYPE dbo.StringList AS TABLE (str varchar(100) NOT NULL);
Your SQL is as follows:
SELECT *
FROM Employees e
WHERE NOT EXISTS (SELECT 1
FROM #words w
WHERE e.Name NOT LIKE '%' + w.str + '%' ESCAPE '/' -- if you want to escape wildcards you need to add ESCAPE
);
Then you pass through the list as follows:
public IEnumerable<Employee> Search(string[] words)
{
var table = new DataTable{ Columns = {
{"str", typeof(string)},
} };
foreach (var word in words)
table.Rows.Add(SqlLikeEscape(word)); // make a function that escapes wildcards
using var connection = CreateConnection();
return connection.Query<Employee>(yourQueryHere, new
{
words = table.AsTableValuedParameter("dbo.StringList"),
});
}
Using Database.Query has has made a huge improvement on readability in my code:
String Select = 'sp_getAllUsers';
WebMatrix.Data.Database DB = WebMatrix.Data.Database.Open(MyConString);
var data = DB.Query(Select);
I'd like to switch to a non-stored procedure query. The MSDN says there's an optional parameter to the Query Method, Object[], that can be passed as SQL parameters, however they don't have any further information about it.
So I have two questions:
How can I create a Object[]?
Will adding parameters in this way prevent hacking threats, such as SQL Injection?
Here's an abridged version of what I have tried:
Select = "Select * From Users Where first_name = "Foo" AND last_name = "Bar"
// Like Javascript
Object[] params = {"first_name" : "Foo"}, {"last_name" : "Bar"};
// More Like What I think it will be
Object[] Params = (String Name = "first_name", String First_Name = "Foo");
var data = DB.Query(Select, params);
All the sources I've looked at only seem to reference the old way. This is close, but he doesn't use the parameter parameter of the Query method.
Try using this syntax:
string selectCommand = "sp_getAllUsers(#0, #1)";
// selectCommand = "Select * From Users Where first_name = #0 AND last_name = #1";
...
var data = DB.Query(selectCommand, "Foo", "Bar");
More info, see:
http://www.aspnet101.com/2010/07/webmatrix-tutorial-working-with-data/
Also, using a Parameter will always prevent SQL Injection as it always double quote a single quote.
I'm having a problem where I don't know how I'm supposed to pass in an Oracle parameter where the C# type is a string and the Oracle type is a Varchar2.
Currently I'm passing in this string as CMS','ABC thinking that Oracle will add in the '' that surround this string making it a varchar2 that looks like 'CMS','ABC'.
This works for a single string like CMS but when the value is something longer, like something typically in a IN (list) command the parameter won't be passed in correctly.
This is the code I'm referring too.
string sql = 'SELECT name FROM Pers p WHERE p.FirstName IN (:names)';
The below works when the value of :names being passed in is CML without any quotes.
OracleParameter param = new OracleParameter(":names", OracleDbType.Varchar2, "CML", ParameterDirection.Input);
Below doesn't work when the value of :names being passed in is CML','ABC with quotes on the inside.
OracleParameter param = new OracleParameter(":names", OracleDbType.Varchar2, "CML','ABC", ParameterDirection.Input);
Why is that?
Does Oracle add in single quotes around the parameter when it's passed into the sql statement? Why doesn't it add quotes around the second case?
ODP.NET parameters do not work with multiple, comma separated values. Each parameter is treated as a single value, whatever kind of quotes it contains.
Oracle does not add quotes around parameter values when passed to a query. Quotes are just a way to write a VARCHAR value in a query, but when using parameters, Oracle doesn't "replace your parameter with its value then execute the query", as this would allow SQL injection.
If that was the case, imagine your parameter value was: "CML', 'ABC');DROP DATABASE Test;--". Oracle would then execute SELECT name FROM Pers p WHERE p.FirstName IN ('CML', 'ABC');DROP DATABASE Test;--'!
See this question for ideas on how to solve your problem: Oracle Parameters with IN statement?
From your comments/answers I was able to come up with this solution. I hope it helps others who come.
To get around ODT.NET parameters not working with multiple comma separated values you can divide each value into its own parameter. Like the following.
string allParams = "CML, ABC, DEF";
string formattedParams = allParams.Replace(" ", string.Empty); // Or a custom format
string [] splitParams = formattedParams.Split(',');
List<OracleParamter> parameters = new List<OracleParameter>();
string sql = #"SELECT * FROM FooTable WHERE FooValue IN (";
for(int i = 0; i < splitParams.Length; i++)
{
sql += #":FooParam" + i + ",";
parameters.Add(new OracleParameter(":FooParam" + i, OracleDbType.Varchar2, splitParams[i], ParameterDirection.Input));
{
sql = sql.Substring(0, (sql.Length - 1));
sql += ')';
The string sql will now have this as it's value: SELECT * FROM FooTable WHERE FooValue IN (:FooParam0,:fooParam1, etc...)
This will solve the problem.
Another approach would be to add in a bunch of OR clauses for each parameter. The above example is better since you don't write a bunch of OR clauses though.
Using Database.Query has has made a huge improvement on readability in my code:
String Select = 'sp_getAllUsers';
WebMatrix.Data.Database DB = WebMatrix.Data.Database.Open(MyConString);
var data = DB.Query(Select);
I'd like to switch to a non-stored procedure query. The MSDN says there's an optional parameter to the Query Method, Object[], that can be passed as SQL parameters, however they don't have any further information about it.
So I have two questions:
How can I create a Object[]?
Will adding parameters in this way prevent hacking threats, such as SQL Injection?
Here's an abridged version of what I have tried:
Select = "Select * From Users Where first_name = "Foo" AND last_name = "Bar"
// Like Javascript
Object[] params = {"first_name" : "Foo"}, {"last_name" : "Bar"};
// More Like What I think it will be
Object[] Params = (String Name = "first_name", String First_Name = "Foo");
var data = DB.Query(Select, params);
All the sources I've looked at only seem to reference the old way. This is close, but he doesn't use the parameter parameter of the Query method.
Try using this syntax:
string selectCommand = "sp_getAllUsers(#0, #1)";
// selectCommand = "Select * From Users Where first_name = #0 AND last_name = #1";
...
var data = DB.Query(selectCommand, "Foo", "Bar");
More info, see:
http://www.aspnet101.com/2010/07/webmatrix-tutorial-working-with-data/
Also, using a Parameter will always prevent SQL Injection as it always double quote a single quote.
I have a sql query for my SelectCommand on my SqlDataSource. It looks like the following:
SELECT * FROM Books WHERE BookID = #BookID
A TextBox feeds the #BookID parameter using an Asp:ControlParameter.
When I view the SelectCommand when stepping through the code, I see this:
SELECT * FROM Books WHERE BookID = #BookID
What I want to actually see is that if the person types in 3 in the TextBox, I want to see
SELECT * FROM Books WHERE BookID = 3
I can't figure out how to access the above though?
One way to view the actual query is by using SQL Profiler.
The query is never executed as
SELECT * FROM Books WHERE BookID = 3
It's actually the parameterised query with the parameter passed.
You can do a "Find/Replace" on the query with the related parameters to see what it would look like.
(This answer presumes with the SqlClient implementation.)
No, you cannot see the executed sql code. The SqlCommand class calls sp_execute (see both SqlCommand.BuildExecute methods for the exact implementation) which separates the query from the parameters. You'll need to use Sql Profiler to see the exact query executed.
You could use the provided DbCommand (from the Selecting event) to parse your CommandText and replace the parameters with their actual values. This would need some logic for escaping, and it will not be the exact query that Sql Server executes.
Public Function GenSQLCmd(ByVal InSqlCmd As String, ByVal p As Data.Common.DbParameterCollection) As String
For Each x As Data.Common.DbParameter In p
InSqlCmd = Replace(InSqlCmd, x.ParameterName, x.Value.ToString)
Next
Return InSqlCmd
End Function
I guess you won't be able to see the select statement like you wish, since the parameter is not replaced in the statement with the value 3, but sent just like you wrote it to sql server (with the parameter).
That's actually good since it will prevent one to inject some malicious sql code in your textbox, for example.
Anyway, can't you retrieve the value passed to the parameter using this:
cmd.Parameters(0).Value
where cmd is your SqlCommand?
This is the C# version of Adam's answer
public string GenSQLCmd(string InSqlCmd, System.Data.Common.DbParameterCollection p) {
foreach (System.Data.Common.DbParameter x in p) {
InSqlCmd = InSqlCmd.Replace(x.ParameterName, "'" + x.Value.ToString() + "'");
}
return InSqlCmd;
}
Usage:
string DebugQuery = GenSQLCmd(cmd.CommandText, cmd.Parameters); //cmd is a SqlCommand instance
Yes, you can view that information but you need to do a bit coding for that.
Create an extension method called ToSqlStatement
public static class SqlExtensions
{
public static string ToSqlStatement(this IDbCommand cmd)
{
var keyValue = new List<string>();
foreach (SqlParameter param in cmd.Parameters)
{
var value = param.Value == null ? "NULL" : "'" + param.Value + "'";
keyValue.Add($"{param.ParameterName}={value}");
}
return $"{(cmd.CommandType == CommandType.StoredProcedure ? "exec " : string.Empty)}{cmd.CommandText} {string.Join(", ", keyValue)}";
}
}
Add OnSelecting event handler to SqlDataSource control on your page
In you code behind
protected void sqlDataSource_Selecting(object sender, SqlDataSourceSelectingEventArgs e)
{
MyLogger.WriteLine(e.Command.ToSqlStatement());
}