sonar is given me a hard time because the following code:
var listaChaves = chaves.ToList();
var parametros = new string[chaves.Count];
var parametrosSql = new List<NpgsqlParameter>();
for (int i = 0; i < listaChaves.Count; i++)
{
parametros[i] = string.Format("#param_{0}", i);
parametrosSql.Add(new NpgsqlParameter(parametros[i], listaChaves[i]));
}
var comandoSql = string
.Format("SELECT distinct on(chave_identificacao) chave_identificacao, data from ultimos_acessos where chave_identificacao in({0}) order by chave_identificacao, data desc",
string.Join(", ", parametros));
var ultimosAcessos = await Entidade.FromSqlRaw(comandoSql, parametrosSql.ToArray())
.Select(a => new ProjecaoListagemUltimoAcesso(a.Data, a.ChaveIdentificacao))
.ToListAsync();
it thinks a sql injection can happen because the string interpolation. So i tried to change to "FromSqlInterpolated" method, as follow:
var listaChaves = chaves.ToList();
var ultimosAcessos = await Entidade.FromSqlInterpolated(#$"SELECT distinct on(chave_identificacao) chave_identificacao, data from ultimos_acessos where chave_identificacao in({string.Join(", ", listaChaves)})) ) order by chave_identificacao, data desc")
.Select(a => new ProjecaoListagemUltimoAcesso(a.Data, a.ChaveIdentificacao))
.ToListAsync();
But it just dont work, anyone can help me on how can I create a secure sql from a interpolated string using a "in" clause?
Something like this should work. Build an OR clause in the same loop as your parameters and stuff it into your SQL:
string ORclause = "";
for (int i = 0; i < listaChaves.Count; i++)
{
ORclause += $" chave_identificacao = #param_{i} OR ";
parametros[i] = string.Format("#param_{0}", i);
parametrosSql.Add(new NpgsqlParameter(parametros[i], listaChaves[i]));
}
//remove last "OR"
ORclause = ORclause.Substring(0, s.Length - 4);
#$"SELECT distinct on(chave_identificacao) chave_identificacao, data
from ultimos_acessos
where {ORclause}
order by chave_identificacao, data desc";
If you have a lot of listaChaves then consider using StringBuilder instead of +=
Related
What is the correct syntax for varname to make this query work?
I can get it to work with a single variable like
string varname = "TOTAL_NORWAY"
However, if I want to have a few variables in there, I get an empty array returned:
string varname = "'TOTAL_NORWAY', 'TOTAL_SWEDEN'";
return await _Context.theDataModel.FromSqlRaw(#"
select data
from data_table
where Variable in ({0})
", varname).ToListAsync();
Remember that you can combine FromSqlRaw with Linq:
string varnames = new [] { "TOTAL_NORWAY", "TOTAL_SWEDEN" };
var query = _Context.theDataModel.FromSqlRaw(#"
select data
from data_table");
query = query.Where(x => varnames.Contains(x.Variable));
// Add more where clauses as needed
return await query.ToListAsync();
ErikEJ's post was very helpful. The solution is not so trivial for someone who doesn't dabble in EF Core regularly.
I also had an extra where clause to consider, and this was done like so for anyone else wondering.
var items = new int[] { 1, 2, 3 };
var parameters = new string[items.Length];
var sqlParameters = new List<SqlParameter>();
for (var i = 0; i < items.Length; i++)
{
parameters[i] = string.Format("#p{0}", i);
sqlParameters.Add(new SqlParameter(parameters[i], items[i]));
}
sqlParameters.Add(new SqlParameter("#userid", "userXYZ123"));
var rawCommand = string.Format("SELECT * from dbo.Shippers WHERE ShipperId IN ({0}) and userid = {1}", string.Join(", ", parameters), "#userid");
var shipperList = db.Set<ShipperSummary>()
.FromSqlRaw(rawCommand, sqlParameters.ToArray())
.ToList();
I'm new to Dapper, and writing a query that will pull from a provided schema and table, along with using dynamic ordering and filtering.
Dapper make dynamic parameters very simple, however, I'm not sure how to do this with tables in the order by and where clauses. Here's my method below, and I see the issues with SQL injection:
public GridData GetGridData(string schema, string table, TableDataParameters tableDataParameters)
{
using (var dbConnection = VarConnection)
{
dbConnection.Open();
if (!this.TableExists(dbConnection, schema, table))
{
throw new ItemNotFoundException($"Could not locate table {schema}.{table}.");
}
string orderyByClause = string.Join(",", tableDataParameters.SortModel.Select(s => $"[{s.ColId}] {(s.Sort.ToLower() == "asc" ? "asc" : "desc")}"));
var parameters = new DynamicParameters();
string whereClause;
if (tableDataParameters.FilterModel == null || !tableDataParameters.FilterModel.Any())
{
whereClause = "1=1";
}
else
{
whereClause = string.Join(" AND ", tableDataParameters.FilterModel.Select((fm, i) =>
{
string whereParam = $"whereParam{i}";
parameters.Add(whereParam, fm.Filter);
if (fm.Operation == "startsWith")
{
return $"[{fm.Column}] LIKE #{whereParam} + '%'";
}
throw new InvalidOperationException($"Unsupported filter operation '{fm.Operation}'");
}));
}
var query = $"SELECT COUNT(1) [total] " +
$"FROM [{schema}].[{table}] " +
$"WHERE {whereClause} " +
$"SELECT * " +
$"FROM [{schema}].[{table}] " +
$"WHERE {whereClause} " +
$"ORDER BY {orderyByClause} " +
$"OFFSET {tableDataParameters.StartIndex.Value} ROWS " +
$"FETCH NEXT {tableDataParameters.StopIndex.Value - tableDataParameters.StartIndex.Value} ROWS ONLY";
int total = 0;
using (var reader = dbConnection.ExecuteReader(query, parameters))
{
// First batch, it's the count
if (reader.Read())
{
total = reader.GetInt32(0);
}
var gridColumns = new List<GridColumn>();
var gridRows = new List<string[]>();
if (reader.NextResult() && reader.Read())
{
for (int i = 0; i < reader.FieldCount; i++)
{
string key = reader.GetName(i);
gridColumns.Add(new GridColumn(key, key, null, ""));
}
var items = new object[reader.FieldCount];
reader.GetValues(items);
gridRows.Add(items.Select(i => i.ToString()).ToArray());
}
while (reader.Read())
{
var items = new object[reader.FieldCount];
reader.GetValues(items);
gridRows.Add(items.Select(i => i.ToString()).ToArray());
}
return new GridData(tableDataParameters.StartIndex.Value, tableDataParameters.StopIndex.Value, total, gridRows.Count(), gridColumns.ToArray(), gridRows.ToArray());
}
}
}
Should I use something like DbCommandBuilder.QuoteIdentifier, https://msdn.microsoft.com/en-us/library/system.data.common.dbcommandbuilder.quoteidentifier(v=vs.110).aspx
in this case? That doesn't seem like it would help so much here.
Thanks!
Dynamic parameters is an oxymoron! Dapper makes parameters easy, but you can't paramaterize table and column names. This is a restriction of SQL, not dapper. If you really want to do this, you have to use dynamic sql and string methods, and you're on your own as regards SQL injection.
You will be happier and live longer if you don't do this. It's just a bad road. You're not adding much value, and you're potentially introducing a load of problems and limitations.
It looks like you're writing an app to browse a database. Good tools already exist for this!
I'm wondering if its possible to make this easier.
So, I have an arraylist of strings called names and an arraylist of strings called last_name.
When I'm constructing select statement, I want to combine every entry in names with everything in last_name and search in database like:
SELECT * FROM DB WHERE (names='NAMES[0]' AND last_name='LAST_NAME[0]')
or (names='NAMES[0]' AND last_name='LAST_NAME[1]')
or (names='NAMES[1]' AND last_name='LAST_NAME[0]')
or (names='NAMES[1]' AND last_name='LAST_NAME[1]')
This is an example, In my project I have 6 Lists, and I need every combination, and the easiest way was to make for in for in for in for...
Thanks a lot
This should work by using a query like this :
SELECT *
FROM DB
WHERE Name IN (#name1,#name2,#name3) AND LastName IN (#lastName1, #lastName2, #lastName3)
You can build this query in a for-loop, like this :
var names = new[] {"John", "Peter"};
var lastnames = new[] { "Doe", "Waylander" };
var nameParams = "";
var lastNameParams = "";
var cnt = 0;
foreach (var name in names)
{
var nameString = "#name" + cnt;
if (cnt!=0)
{
nameParams += ",";
}
nameParams += nameString;
cmd.Parameters.Add(nameString, name);
cnt++;
}
cnt = 0;
foreach (var lastName in lastnames)
{
var lastNameString = "#lastName" + cnt;
if (cnt != 0)
{
lastNameParams += ",";
}
lastNameParams += lastNameString;
cmd.Parameters.Add(lastNameString, lastName);
cnt++;
}
cmd.CommandText = #"SELECT *
FROM DB
WHERE NAME IN (" + nameParams + #")
AND LastName IN (" + lastNameParams + ")";
var result = cmd.ExecuteReader();
The only limitation is the number of parameters (IIRC it is about 1000). Another good alternative would be a stored procedure, like already mentioned in the comments.
I have this method that should take an unknown amount of id's.
I got this method almost done but it isnt secure yet for obvious reasons, i know i could write my own method to strip the parameters but i would be more comfortable by using some build in method for this.
Here is the method
public static List<LocationModel> FetchCitiesByAreas(IEnumerable<string> areas)
{
using (var db = new BoligEnt())
{
var sqlQuery = new StringBuilder();
var first = true;
sqlQuery.Append("SELECT DISTINCT a.city AS City, a.zip AS Zip ");
sqlQuery.Append("FROM zip_city AS a ");
sqlQuery.Append("WHERE country = 1 ");
foreach (var d in areas)
{
if (first)
{
sqlQuery.Append("AND a.area_id = '" + d + "'");
first = false;
}
else
{
sqlQuery.Append("OR a.area_id = '" + d + "'");
}
}
return db.Database.SqlQuery<LocationModel>(sqlQuery.ToString()).ToList();
}
}
i know it have this function built in but as i stated earlier i dont know the exact amount of ids that will come in
db.Database.SqlQuery<LocationModel>("SELECT * FROM table WHERE id = #p0 ;", id).ToList();
Thanks
While I completely agree with paqogomez, in that you should just use LINQ to do the query, the .SqlQuery has the ability to take a parameter array. You could change your statement to look like this:
var sqlQuery = new StringBuilder();
sqlQuery.Append("SELECT DISTINCT a.city AS City, a.zip AS Zip ");
sqlQuery.Append("FROM zip_city AS a ");
sqlQuery.Append("WHERE country = 1 ");
for (int i = 0; i < areas.Count; i++)
{
if (i == 0)
{
sqlQuery.Append("AND (a.area_id = #p" + i.ToString());
}
else
{
sqlQuery.Append(" OR a.area_id = #p" + i.ToString());
}
}
sqlQuery.Append(")");
var results = db.Database.SqlQuery<LocationModel>(sqlQuery.ToString(), areas.ToArray()).ToList();
I added the missing parenthesis needed to your query to correctly filter out the OR results as well. I've also taken the assumption that areas is something like a List, or at least something you can easily get the count from.
Why dont you just use Linq?
var locations = (from zip in db.zip_city
where areas.Contains(zip.area_id) && zip.Country == 1
select new LocationModel{
City = zip.City,
Zip = zip.Zip
})
.Distinct()
.ToList();
If you still want to parameterize your query, you need to use EntityCommand
Also note that your query will fail because you havent put parenthesis around your OR statements.
I suggest structuring your sql like this:
string sqlQuery =
#"SELECT DISTINCT a.city AS City, a.zip AS Zip
FROM zip_city AS a
WHERE country = 1 AND (1=0 "
for (int i = 0; i < areas.Count; i++)
{
sqlQuery.Append("OR a.area_id = #d" + i.ToString() + " ");
}
sqlQuery.Append(")");
Having something similar to:
SELECT (SELECT COUNT(*) from Table1),(SELECT COUNT(*) from Table2 )
How do I write it in linq? Or is it simple not possible?
Limitations:
Can only hit the database one time:
var result = new {
Sum1 = db.Table1.Count(),
Sum2 = db.Table2.Count()
}); // is not valid.....
I do not want to use something similar to (using a "helping" table):
var result = (from t3 in db.Table3
select new {
Sum1 = db.Table1.Count(),
Sum2 = db.Table2.Count()
}).firstOrDefault();
//In order to get only the first row
//but it will not return nothing if the table 3 has no entries......
Not using db.Database.ExecuteSqlCommand
I cannot see a solution which solves all your limitations. This is one of the caveats with using an ORM-mapper, you are not in control of the generated SQL.
In this case, if it is utterly unacceptable for you to send more than one query to the database, the harsh truth is that you will have to write the query yourself.
Update
I got curious and created an extension method that can do this! Of course it constructs its own SQL command, and it just works for Linq2SQL. Also massive disclaimer: It's fairly dirty code, if I have some time I'll fix it up in the weekend :)
public static TOut CountMany<TContext, TOut>(this TContext db, Expression<Func<TContext, TOut>> tableSelector)
where TContext: DataContext
{
var newExpression = (NewExpression) tableSelector.Body;
var tables =
newExpression.Arguments.OfType<MethodCallExpression>()
.SelectMany(mce => mce.Arguments.OfType<MemberExpression>())
.ToList();
var command = new string[tables.Count];
for(var i = 0; i < tables.Count; i++)
{
var table = tables[i];
var tableType = ((PropertyInfo) table.Member).PropertyType.GetGenericArguments()[0];
var tableName = tableType.GetCustomAttribute<TableAttribute>().Name;
command[i] = string.Format("(SELECT COUNT(*) FROM {0}) AS T{1}", tableName, i);
}
var dbCommand = db.Connection.CreateCommand();
dbCommand.CommandText = string.Format("SELECT {0}", String.Join(",", command));
db.Connection.Open();
IDataRecord result;
try
{
result = dbCommand.ExecuteReader().OfType<IDataRecord>().First();
}
finally
{
db.Connection.Close();
}
var results = new object[tables.Count];
for (var i = 0; i < tables.Count; i++)
results[i] = result.GetInt32(i);
var ctor = typeof(TOut).GetConstructor(Enumerable.Repeat(typeof(int), tables.Count).ToArray());
return (TOut) ctor.Invoke(results);
}
the code is called like this:
var counts = dbContext.CountMany(db => new
{
table1Count = db.Table1.Count(),
table2Count = db.Table2.Count()
//etc.
});