Is there a way to write parameterized sql for different providers?
Example:
connection.QuerySingle<string>("select name from user where id = :id", new {id = 4});
This would work with an oracle provider but MsSql requires "#id" as parameter.
The Dapper FAQ says:
It's your job to write the SQL compatible with your database provider.
But how? Currently we have the following workaround:
$".. where id = {db.ParamToken}id"
But that's really ugly to write in larger SQL's.
Is there a way to have one token for all providers?
"Is there a way to have one token for all providers?"
Yes, but it requires some setup. You can retrieve useful database provider specific information from an existing DBConnection. First retrieve the DataSourceInformation table from the connection:
DbConnection connection = GetSomeConnection();
var infoTable = connection.GetSchema(DbMetaDataCollectionNames.DataSourceInformation);
That table will have a single row with various provider info. In regards to parameter naming, there will be a column named ParameterMarkerPattern which represents a Regex pattern string for validating the parameter. If that column has data, the first character will be your DbParameter marker. If the column is blank, the ParameterMarkerFormat can give you a string format to apply when building your parameter name.
"But that's really ugly to write in larger SQL's".
This doesn't really get around that if you're considering directly formatting your SQL, and your workaround is already much simpler than this. However the additional data you get from the DataSourceInformation should be enough for you to pass in your own string to a method of your creation, which would replace a default parameter start character (like #) with the proper one from the provider:
string sql = SqlIfy("SELECT name FROM user WHERE id = #id");
You can go even further with this and do the same for quoted identifiers. You can pass in something like:
"SELECT [Name] FROM [dbo].[SomeTable]"
and have it come out like
SELECT "Name" FROM "dbo"."SomeTable"
All depending on the provider. If you wanted to dynamically build queries on some custom base provider class, you could open an initial connection and store all of the provider specific data. You wouldn't want to call DbConnection.GetSchema each time you use a connection.
SQL Server:
Oracle:
Related
I need to filter a sql request by passing a list of id to , this is the command:
var command = "select Software.Name as SoftwareName,SoftwareType.Name as SoftwareType from AssetManagement.Asset as Software inner join AssetManagement.AssetType as SoftwareType on (SoftwareType.Id = Software.TypeId) where Software.Id in (#P)";
cmd.Parameters.AddWithValue("#P", authorizedSoftwaresId);
authorizedSoftwaresId is a list of string , containing data like :
"7D23968B-9005-47A9-9C37-0573629EECF9,F1982165-3F6D-4F35-A6AB-05FA116BA279"
with that it returns to me just one row, I tried adding quotes foreach value but i got "converstion from string caractere to uniqueidentifier failed " exception
This is a pretty common problem and the answer might depend on your database engine and whether you're using ADO.Net, Dapper, etc.
I'll give you some hints from my own experience with this problem on both MS Sql Server and PostgreSQL.
A lot of people think AddWithValue is a devil. You might consider Add instead.
The IN (#P) in your SQL statement might be the source of your problem. Try the Any option instead. See Difference between IN and ANY operators in SQL ; I've had success with this change in similar situations.
If all of your inputs are GUIDs, you might consider changing the type to a collection of GUIDs, although I don't think this is the fix, you have to try everything when you're stuck.
If you have to, you can parse the string version of your collection and add the ticks (') around each value. This choice has consequences, like it may prevent you from using a parameter (#P), and instead construct the final SQL statement you desire (i.e., manually construct the entire WHERE clause through string manipulations and lose the parameter.)
Good luck.
My odbc connection string for connecting to DB2i looks like this:
Driver={Client Access ODBC Driver (32-bit)};system=xx.xx.xx.xx;dbq=LIB1 LIB2 LIB3 LIB4 LIB5 LIB6 LIB7 LIB8;languageid=ENU;qrystglmt=-1;cmt=0;signon=1
The above connection string specifies multiple libraries/schemas for use. But when I try to access a file/table from a library other than the first one (like from LIB2...LIB8) I get a exception saying "FILE xx not found in LIB1"
Why does not it automatically search for the file/table in other specified libraries, it searches for the file in the first library only?
I need a workaround for this situation.
Use "system naming" mode, by adding naming=1 to your connection string.
In your library list, place a comma before the first library.
Driver={Client Access ODBC Driver (32-bit)};system=systemname;naming=1;
dbq=,LIB1,LIB2,LIB3,LIB4,LIB5,LIB6,LIB7,LIB8;languageid=ENU;cmt=0;signon=1
This works as documented in the manual:
The library list is used for resolving unqualified stored procedure calls and finding libraries in catalog API calls. ...
Note:
The first library listed in this property will also be the default library, which is used to resolve unqualified names in SQL statements.
As stated above, Schema/library list is used to resolve functions/procedure names, not tables.
Let assume you need to read data from lib1.tab1 and lib2.tab2;
Here my personal workarounds (from easy to complex):
a) ask the db admin to have - for each table you need to use - the corresponding schema name, then do "select * from lib1.tab1 join lib2.tab2 on [...]" ;-)
b) ask the db admin to create on schema "MyAlias" several alias (create alias) for each table you want to use. Then do "set current schema=MyAlias" followed by all the SQL statement you need e.g. "select * from tab1 join tab2". Since you’re querying myalias.tab1 which is an alias pointing to table lib1.tab1 it should work.
c) Complex: create your own SQL function that returns the corresponding schema_name for a table (e.g. myfunct('TAB1'). This could be done reading system view “qsys2.systables” where table_name=’TAB1’ and returning TABLE_SCHEMA column, which is a varchar(128). Once you got it, build up a dynamically prepared using the variable you just obtained. E.g.
"set mylib = myfunct('TAB1').
"set mystmt = 'select * from '||table_schema || ‘.tab1’ …”
Prepare mystmt and then execute mystmt.
I did something similar in VBA using ado ibmdrda and it worked.
hope this helps.
f.
While parameter is the best way to guard against Sql injection, there are times which we can't use it while building dynamic query. For example Table/Column/Index names cannot be passed in as parameter but only plain Text.
It seems like
SqlCommandBuilder.QuoteIdentifier
is the only option that I can find. Is calling this method enough to protect ourselves?
MSDN DOC:
Given an unquoted identifier in the correct catalog case, returns the
correct quoted form of that identifier. This includes correctly
escaping any embedded quotes in the identifier.
For example is
"Select * FROM " + SqlCommandBuilder.QuoteIdentifier("CustomTable" + userInputText);
safe to do?
Edit: The query is just an example. I am interested in finding out if Sql injection is ever possible.
It won't protect you from the attacker going to tables you don't want them to.
such as SQL system tables...
That may not be not safe to do.
To protect against any possible security problem with the SqlCommandBuilder.QuoteIdentifier method all that you need to do is get a list of the available table names etc. from the database and validate the user input against them.
Edited to add: I have reason to doubt if QuoteIdentifier is completely safe: the documentation for the SqlCommandBuilder.QuoteIdentifier Method says (as you previously quoted):
Given an unquoted identifier in the correct catalog case, returns the correct quoted form of that identifier. This includes correctly escaping any embedded quotes in the identifier.
Nowhere in that documentation does it state what happens if it is given an unquoted identifier in the wrong catalog case (whatever a "catalog case" is). Or what happens if the identifier is longer than the maximum allowed. Of course, undefined behaviour cannot be relied on.
Seems safe to do to me. Also, this might be a good reference:
Sanitize table/column name in Dynamic SQL in .NET? (Prevent SQL injection attacks)
Using user input in table name fields is never safe no matter how much you try to check it (unless you restrict the entries to a limited set of names or do some other kind of sorcery).
Even removing quotes, the user could type: TableName; DROP DATABASE db; or (SELECT * FROM <sensible table).
Possible solutions would be:
Using a ComboBox or an equivalent where the user can't modify the options
Check the input against a String[] with all allowed table names (where they should be identical to one of the entries)
and if the user input as a table name was just an example but you're going to use the input as part of a WHERE clause of a SELECT, then you should check Bobby Tables.
Quoted from the website:
From the C# Online wiki page ASP.NET Security Hacks--Avoiding SQL Injection
SqlCommand userInfoQuery = new SqlCommand(
"SELECT id, name, email FROM users WHERE id = #UserName",
someSqlConnection);
SqlParameter userNameParam = userInfoQuery.Parameters.Add("#UserName",
SqlDbType.VarChar, 25 /* max length of field */ );
// userName is some string valued user input variable
userNameParam.Value = userName;
Or simpler:
String username = "joe.bloggs";
SqlCommand sqlQuery = new SqlCommand("SELECT user_id, first_name,last_name FROM users WHERE username = ?username", sqlConnection);
sqlQuery.Parameters.AddWithValue("?username", username);
Have you considered using OData? you can pass in text, selecting tables, indexes and so on. But with OData you select what tables you want to publish this way, and it can't be injection attacked as you have to explicitly allow update and insert operations.
http://www.odata.org/documentation/odata-version-2-0/uri-conventions/
https://www.asp.net/web-api/overview/odata-support-in-aspnet-web-api
Currently I simply don't allow apostrophe's at all (along with other character's as you can see) with this, reiterated for each field:
foreach(char c in Comments)
{
if(c=='\'' || c=='$' || c=='\"' || c=='&' || c=='%' || c=='#' || c=='-' || c=='<' || c=='>')
{
errorMessage = "You have entered at least one invalid character in the \"Comments\" field. Invalid characters are: [\'], [\"], [&], [$], [#], [-], [<], [>], and [%]";
}
}
I've been coding this for a while, and I am getting better, but the real problem is that, while I am sure there is a way to effectively "strip-out" or otherwise validate the user input, I am not sure which approach is best and probably wouldn't until a security crisis was imminent.
If I have to, I will settle on simply never allowing single quotes into the form at all (as it is now), however this may aggravate anyone named say... Bill O'Reilly for the name field, etc., etc.
There are probably other (well I don't know what to call them, 'plug-ins?' 'outside programs?') that would do the job for me, but that is virtually useless since not only do I have no idea how to integrate that, I would have no idea how to tailor it specifically to my web interface/database.
Is there any code that could help me detect a sql injection apostrophe (by the characters surrounding it maybe?) from a normal apostrophe? Keep in mind some of my fields can hold up to 500 characters (textareas) and one up to 1,000.
Thanks for any ideas or help!
No amount of input encoding/cleanup will be as safe as parametrized queries.
See SqlCommand.Parameters for details on parametrized queries.
string commandText = "SELECT * FROM Sales WHERE CustomerID = #ID;";
using (SqlConnection connection = new SqlConnection(connectionString))
{
SqlCommand command = new SqlCommand(commandText, connection);
command.Parameters.Add("#ID", SqlDbType.Int);
command.Parameters["#ID"].Value = customerID;
var reader = command.ExecuteReader();
//.....
}
SQL Injections is not a problem with the input containing specific characters, it's a problem with how you handle the input.
By disallowing certain characters you can stop the obvious ways to cause SQL injections, but it's virtually impossible to use that to stop all possible ways.
If encoded correctly, there are no character that causes problems. The best way of doing that for database calls is to use parameterised queries, so that the database driver takes care of encoding the correct characters according to the data type and the specific database.
Also, you need to encode the values correctly when you use them later on, like HTML encoding strings that are put in HTML code, URL encoding strings that are used in an URL (and both for strings that are put in an URL in the HTML code.)
You should use parameterised queries to prevent SQL Injection as other people have already said.
Alexei Levenkov provides a good example of using ADO.NET parameters, but more commonly, you will use the Database Helper when working with WebMatrix Razor pages (ASP.NET Web Pages Framework) where parameter handling is slightly different. The Database.Query method (Query(string commandText, params object[] parameters) takes a string representing the SQL to be executed, and an array of objects, representing the parameter values to be passed to the SQL. The Database helper expects parameter markers to start at #0, and increment by 1 each time e.g.
var sql = "SELECT * From MyTable WHERE TheDate > #0 AND ID > #1";
Then you pass actual values in the following manner:
var data = Database.Open("MyDb").Query(sql, Request["date"], Request["id"]);
Internally, the Database class takes care of matching values to placeholders and creating ADO.NET parameters for you.
Given the following code (which is mostly irrelevant except for the last two lines), what would your method be to get the value of the identity field for the new record that was just created? Would you make a second call to the database to retrieve it based on the primary key of the object (which could be problematic if there's not one), or based on the last inserted record (which could be problematic with multithreaded apps) or is there maybe a more clever way to get the new value back at the same time you are making the insert?
Seems like there should be a way to get an Identity back based on the insert operation that was just made rather than having to query for it based on other means.
public void Insert(O obj)
{
var sqlCmd = new SqlCommand() { Connection = con.Conn };
var sqlParams = new SqlParameters(sqlCmd.Parameters, obj);
var props = obj.Properties.Where(o => !o.IsIdentity);
InsertQuery qry = new InsertQuery(this.TableAlias);
qry.FieldValuePairs = props.Select(o => new SqlValuePair(o.Alias, sqlParams.Add(o))).ToList();
sqlCmd.CommandText = qry.ToString();
sqlCmd.ExecuteNonQuery();
}
EDIT: While this question isn't a duplicate in the strictest manner, it's almost identical to this one which has some really good answers: Best way to get identity of inserted row?
It strongly depends on your database server. For example for Microsoft SQL Server you can get the value of the ##IDENTITY variable, that contains the last identity value assigned.
To prevent race conditions you must keep the insert query and the variable read inside a transaction.
Another solution could be to create a stored procedure for every type of insert you have to do and make it return the identity value and accept the insert arguments.
Otherwise, inside a transaction you can implement whatever ID assignment logic you want and be preserved from concurrency problems.
Afaik there is not finished way.
I solved by using client generated ids (guid) so that my method generated the id and returns it to the caller.
Perhaps you can analyse some SqlServer systables in order to see what has last changed. But you would get concurrency issues (What if someone else inserts a very similar record).
So I would recommend a strategy change and generate the id's on the clients
You can take a look at : this link.
I may add that to avoid the fact that multiple rows can exist, you can use "Transactions", make the Insert and the select methods in the same transaction.
Good luck.
The proper approach is to learn sql.
You can do a SQL command followed by a SELECT in one run, so you can go in and return the assigned identity.
See