I am attempting to parameterize multiple values that are going into my query, but keep getting errors. This is what I am currently working on, in which I am getting a "must declare the scalar variable" error on the userClientIds parameter. Can someone help me figure out what I am missing.
public async Task<IEnumerable<SharedUser>> GetUsersForSharing(Guid userId, Guid
templateId, string? searchedEmpName, string? searchedEmpNumber)
{
// this is a list of ints, which will need passed into the WHERE IN clause below
var userClientIds = userClients.Select(client => client.ClientId).ToList();
var sql = $#"SELECT DISTINCT
UserId,
ClientId,
FullName,
EmployeeNumber
FROM dbo.ClientUser
WHERE
UserId <> #UserId
AND ClientId in (
#userClientIds
)";
if(searchedEmpName != null)
{
sql += $#"AND FullName LIKE '%#searchedEmpName%'";
}
if(searchedEmpNumber != null)
{
sql += $#"AND EmployeeNumber LIKE '%#searchedEmpNumber%'";
}
using(var conn = _connectionFactory.GetDbConnection())
{
var parameters = new DynamicParameters();
parameters.Add("#userId", userId.ToString());
parameters.Add("#userClientIds", new[] { userClientIds });
parameters.Add("#searchedEmpName", searchedEmpName);
parameters.Add("#searchedEmpNumber", searchedEmpNumber);
conn.Open();
var result = await conn.QueryAsync<SharedUser>(sql, new { parameters });
return result;
}
}
You just need to pass the whole list to DyanmicParameters without a containing array, and Dapper will inject it for you.
You must remove the parenthesis () of ClientId in (#userClientIds) otherwise you get a syntax error.
Some more notes:
Pass the dynamic parameters directly to Query, not inside a contatining array.
Use of DISTINCT is a code-smell: why does your tabel have duplicates in the first place? Perhaps you should improve your tabel design.
userId.ToString() why? If it's a Guid keep it as such.
The LIKE parameters are not going to work like that. instead you need to concatenate them within SQL $#"AND FullName LIKE '%' + #searchedEmpName + '%' etc.
Dapper will open and close the connection for you automatically.
public async Task<IEnumerable<SharedUser>> GetUsersForSharing(Guid userId, Guid
templateId, string? searchedEmpName, string? searchedEmpNumber)
{
var userClientIds = userClients.Select(client => client.ClientId).ToList();
var sql = $#"
SELECT
UserId,
ClientId,
FullName,
EmployeeNumber
FROM dbo.ClientUser
WHERE
UserId <> #UserId
AND ClientId in #userClientIds
";
if(searchedEmpName != null)
{
sql += $#"AND FullName LIKE '%' + #searchedEmpName + '%'
";
}
if(searchedEmpNumber != null)
{
sql += $#"AND EmployeeNumber LIKE '%' + #searchedEmpNumber + '%'
";
}
using(var conn = _connectionFactory.GetDbConnection())
{
var parameters = new DynamicParameters();
parameters.Add("#userId", userId);
parameters.Add("#userClientIds", userClientIds);
parameters.Add("#searchedEmpName", searchedEmpName);
parameters.Add("#searchedEmpNumber", searchedEmpNumber);
var result = await conn.QueryAsync<SharedUser>(sql, parameters);
return result;
}
}
If you have a big list then do not do the above code, as performance will be terrible. Instead use a table valued parameter, and pass it with .AsTableValuedParameter
First, create a table type
CREATE TYPE dbo.IdList (Id int PRIMARY KEY)
Then pass it like this
var table = new DataTable { Columns = {
{ "Id", typeof(int) },
} };
foreach (var id in userClientIds)
table.Rows.Add(id);
parameters.Add("#userClientIds", table.AsTableValuedParameter("dbo.IdList"));
And the syntax in SQL is
WHERE
UserId <> #UserId
AND ClientId in (SELECT uci.Id FROM #userClientIds uci)
Create client id to comma separated string;
var clientIds = String.Join(",", userClientIds.Select(i => i.ToString()).ToArray())
Then add parameter:
parameters.Add("#userClientIds", clientIds);
Related
Picture with the error
public bool UpdateDistrict(string name, int id,int primarySeller, string primarySellerName)
{
bool result = false;
string connStr = #"Data Source=.;Initial Catalog=DataBase;Integrated Security=True";
string sql = "UPDATE District" + " SET Name = #name, Id = #id, PrimSellerId = #primarySeller , PrimSellerName = #primarySellerName" +
" WHERE Id = #id";
var district=GetDistrict(id);
if(name==null || name=="")
{
name=district.Name;
}
using (var conn = new SqlConnection(connStr))
{
conn.Query(sql, new
{
Name = name,
Id = id,
PrimSellerId = primarySeller,
PrimSellerName = primarySellerName,
});
result = true;
}
return result;
}
This is what I get as error when I try to update in the database
Microsoft.Data.SqlClient.SqlException: 'Must declare the scalar
variable "#primarySeller".
To expand on the comment, when you write a query in SQL:
UPDATE District
SET
Name = #name,
Id = #id,
PrimSellerId = #primarySeller ,
PrimSellerName = #primarySellerName
WHERE Id = #id
It is the parameter names (text after #) that should be specified in the anonymous type passed as the parameter arguments:
new
{
name,
id,
primarySeller,
primarySellerName,
}
In your SQL all your parameter names are the same as the c# variable names you use to make the anonymous type, so you can abbreviate the creation of the AT by just mentioning the variable names; the compiler will use them and the property names for the AT unless you're on an old version of .Net that doesn't support this (you'll find out), in which case you'll have to specify the column name too:
primarySeller = primarySeller
If your names differ you'll need to ensure that the AT property names are matched to the SQL parameter names:
conn.Query(
"UPDATE t SET userAge = #u WHERE id = #i",
new { u = 19, i = someId }
);
If you're running an update, insert, delete, merge (most things that aren't a select and don't produce rows of values, use Execute; it returns an integer of the number of rows affected, which you may want to utilise to indicate something didn't save:
int r = conn.Execute(...);
return r == 1;
The app is .Net Core 3.1, using EF Core 3 and a SQL Server on Azure
So I'm trying to create a table in my database with data from the client and I want to be safe from SQL injection.
So far I've tried with using a FormattableString which according to the doc is safe against SQL injection:
public Task CreateTableAsync(string tableName, JSchema tableSchema)
{
return TransactionAsync(async () =>
{
// Get the fields for the table creation
var fields = await ParseJSchemaForCreationAsync(tableSchema);
var sql = "CREATE TABLE {0} (";
var sqlParams = new List<object>
{
tableName
};
var first = true;
var count = 1;
foreach (var entry in fields)
{
// entry.Value is from code so it's safe againt injection
sql += first ? $"{{{count}}} {entry.Value}" : $", {{{count}}} {entry.Value}";
first = false;
sqlParams.Add(entry.Key);
count++;
}
sql += ");";
var safe = FormattableStringFactory.Create(sql, sqlParams.ToArray());
// Create the table
await _dbContext.Database.ExecuteSqlInterpolatedAsync(safe);
});
}
But I've an error : "incorrect syntax near '#p0'", despite it seems to generate a valid query (when getting the value of sage I got :
"CREATE TABLE sqlDataSourceGrainTest (Id uniqueidentifier NOT NULL PRIMARY KEY, CreatedAt datetime2(0), UpdatedAt datetimeoffset(3), FirstName nvarchar(4000), Birthdate date, XId uniqueidentifier, Datetime datetime2(0), Timestamp timestamp, Height decimal(18, 2), HasFoodAllergy bit, Age bigint);"
I've also tried to use with SQLParameter (which I prefer):
public Task CreateTableAsync(string tableName, JSchema tableSchema)
{
return TransactionAsync(async () =>
{
// Get the fields for the table creation
var fields = await ParseJSchemaForCreationAsync(tableSchema);
var sql = "CREATE TABLE #tableName (";
var sqlParams = new List<SqlParameter>()
{
new SqlParameter
{
ParameterName = "tableName",
Value = tableName,
}
};
var first = true;
foreach (var entry in fields)
{
sql += first ? $"#{entry.Key} {entry.Value}" : $", #{entry.Key} {entry.Value}";
first = false;
var sqlParam = new SqlParameter
{
ParameterName = $"{entry.Key}",
Value = entry.Key
};
sqlParams.Add(sqlParam);
}
sql += ");";
// Create the table
await _dbContext.Database.ExecuteSqlRawAsync(sql, sqlParams);
});
}
But I've have the error : "Incorrect syntax near '#tableName'."
Can someone help me to find the correct way to create the table? Is there any rules that say we can't use sql with parameters to create the table.
I've will also need to made update of the table, insert records and update records
Thanks
Edit: Based on answers from DavidG and HoneyBadger I've tried:
public Task CreateTableAsync(string tableName, JSchema tableSchema)
{
return TransactionAsync(async () =>
{
// Get the fields for the table creation
var fields = await ParseJSchemaForCreationAsync(tableSchema);
var sql = $"CREATE TABLE {tableName} (";
var sqlParams = new List<SqlParameter>();
var first = true;
foreach (var entry in fields)
{
sql += first ? $"#{entry.Key} {entry.Value}" : $", #{entry.Key} {entry.Value}";
first = false;
var sqlParam = new SqlParameter
{
ParameterName = $"{entry.Key}",
Value = entry.Key
};
sqlParams.Add(sqlParam);
}
sql += ");";
// Create the table
await _dbContext.Database.ExecuteSqlRawAsync(sql, sqlParams);
});
}
But now the error is "Incorrect syntax near '#id'" which is the name of the first parameter
SQL I see: CREATE TABLE tableTests ( #Id uniqueidentifier NOT NULL PRIMARY KEY, #CreatedAt datetime2(0), #UpdatedAt datetimeoffset(3), #FirstName nvarchar(4000), #Birthdate date, #XId uniqueidentifier, #Datetime datetime2(0), #Timestamp timestamp, #Height decimal(18, 2), #HasFoodAllergy bit, #Age bigint);"
Can't I use any parameters at all in the creation of a table?
Object names can't be parameters, so you'll need to use concatenation:
var sql = "CREATE TABLE " + tableName + " (";
I hope your users aren't involved in deciding the name of the table, so sql injection shouldn't be an issue.
I'm trying to find out whether it is possible to use Query method in Dapper to insert and update rows since I want to return an object after applying insert and update actions.
I could not find any remarkable answers for that so I just tried that:
public async Task<Hotel> CreateHotel(Hotel hotel)
{
var sql = "INSERT INTO Hotels" +
" (name, city)" +
" VALUES (#name, #city)";
var newHotel = new Hotel()
{
Name = hotel.Name,
City = hotel.City
};
using (var connection = new SqlConnection(CONNECTION_STRING))
{
return (Hotel)await connection.QueryAsync<Hotel>(sql, newHotel);
}
}
But that gives that error: System.InvalidCastException: Unable to cast object of type 'System.Linq.EmptyPartition1[HotelFinder.Entities.Hotel]' to type 'System.Collections.Generic.List1[HotelFinder.Entities.Hotel]'.
Do you know how I can fix this or is that completely wrong?
First, you need to use the OUTPUT clause in order to return rows from an insert statement.
Second, QueryAsync will return an IEnumerable<Hotel> and not a Hotel, so you won't be able to cast it.
Put together, it would look like this
public async Task<Hotel> CreateHotel(Hotel hotel)
{
var sql = "INSERT INTO Hotels" +
" (name, city)" +
" OUTPUT inserted.name, inserted.city" +
" VALUES (#name, #city)";
var newHotel = new Hotel()
{
Name = hotel.Name,
City = hotel.City
};
using (var connection = new SqlConnection(CONNECTION_STRING))
{
return (await connection.QueryAsync<Hotel>(sql, newHotel)).SingleOrDefault();
}
}
I have the following code that uses Dapper:
public async Task GetClientsInvoices(List<long> clients, List<int> invoices)
{
var parameters = new
{
ClientIds = clients,
InvoiceIds = invoices
};
string sqlQuery = "SELECT ClientId, InvoiceId, IsPaid" +
"FROM ClientInvoice " +
"WHERE ClientId IN ( #ClientIds ) " +
"AND InvoiceId IN ( #InvoiceIds )";
var dbResult = await dbConnection.QueryAsync<ClientInvoicesResult>(sqlQuery, parameters);
}
public class ClientInvoicesResult
{
public long ClientId { get; set; }
public int InvoiceId { get; set; }
public bool IsPaid { get; set; }
}
which produces this based on sql server profiler
exec sp_executesql N'SELECT ClientId, InvoiceId, IsPaid FROM ClientInvoice WHERE ClientId IN ( (#ClientIds1) ) AND InvoiceId IN ( (#InvoiceIds1,#InvoiceIds2) )',N'#InvoiceIds1 int,#InvoiceIds2 int,#ClientIds1 bigint',InvoiceIds1=35,InvoiceIds2=34,ClientIds1=4
When it is executed I am getting the following error
Msg 102, Level 15, State 1, Line 1
Incorrect syntax near ','.
I have two questions:
What am I doing wrong and I am getting this exception?
Why dapper translates this query and executes it using sp_executesql . How do I force it to use a normal select query like
SELECT ClientId, InvoiceId, IsPaid
FROM ClientInvoice
WHERE ClientId IN (4)
AND InvoiceId IN (34,35)
Looking at your profiled SQL:
exec sp_executesql
N'SELECT ClientId, InvoiceId, IsPaid
FROM ClientInvoice
WHERE ClientId IN ( (#ClientIds1) )
AND InvoiceId IN ( (#InvoiceIds1,#InvoiceIds2) )',
N'#InvoiceIds1 int,#InvoiceIds2 int,#ClientIds1 bigint',InvoiceIds1=35,InvoiceIds2=34,ClientIds1=4
We can see that you've got two sets of brackets around #ClientIds1 and #InvoiceIds1,#InvoiceIds2, the set which you wrote, and the set which Dapper (or a layer below it) is injecting.
Therefore, try removing the brackets which you added:
string sqlQuery = "SELECT ClientId, InvoiceId, IsPaid " +
"FROM ClientInvoice " +
"WHERE ClientId IN #ClientIds " +
"AND InvoiceId IN #InvoiceIds";
This matches the code in the Dapper README:
Dapper allows you to pass in IEnumerable<int> and will automatically parameterize your query.
For example:
connection.Query<int>("select * from (select 1 as Id union all select 2 union all select 3) as X where Id in #Ids", new { Ids = new int[] { 1, 2, 3 } });
Will be translated to:
select * from (select 1 as Id union all select 2 union all select 3) as X where Id in (#Ids1, #Ids2, #Ids3)" // #Ids1 = 1 , #Ids2 = 2 , #Ids2 = 3
Remove the parentheses would fix the problem.
Wrong: "WHERE ClientId IN ( #ClientIds ) "
Correc: "WHERE ClientId IN #ClientIds "
Try to convert the list into a string and pass as a comma-separated string:
var parameters = new
{
ClientIds = String.Join(",",clients),
InvoiceIds = String.Join(",",invoices)
};
So I am trying to create parameters to pass to my query and was wondering if I could pass a parameter that contains an '=' sign in it that replaces the traditional '=' sign in the SQL statement. It would be like the following:
string aoi = "=" + comboBox1.Text;
string comp = "=" + comboBox2.Text;
//Default values of their respective Combo Boxes.
if(aoi == "Area Of Interest")
aoi = "!= NULL";
if (comp == "Company")
comp = "!= NULL";
cmd.CommandText = "SELECT * FROM JobListInfo" +
"WHERE AreaOfInterest #AOI AND Company #Company";
cmd.Parameters.AddWithValue("#AOI", aoi);
cmd.Parameters.AddWithValue("#Company", comp);
The reason I am asking is because if the user doesn't change the default value, I want it to pull all the records (with respect to the rest of the SQL statement). I understand I could create an OR statement, but I have three other parameters I would like to pass as well and didn't want to create 15 OR cases.
EDIT: I found a solution to my problem. I changed the '=' to Like and changed the strings to '%' if they didn't select a value. This shouldn't cause any SQL injection issues, right?
Sample Code:
if(aoi == "Area of Interest")
aoi = "%"
cmd.CommandText = "SELECT * FROM JobListInfo " +
"WHERE AreaOfInterest LIKE #AOI";
cmd.Parameters.AddWithValue("#AOI", aoi);
Not via a parameter. If this were allowed, it would potentially introduce SQL injection vulnerabilities.
A potential solution would be to dynamically create the CommandText string by appending database column names and parameter placeholders to the query's WHERE clause.
WARNING: Do not append input values to the WHERE clause of your query string! This will leave you vulnerable to SQL injection. Instead, append parameter placeholders and then populate them using the cmd.Parameters.AddWithValue() method.
That being said, something like the code below might work. However, it would depend on you selecting a single default value for your combo-boxes. Consequently, you would need to use UI labels instead of default values to describe the combo-boxes in your app.
string MY_DEFAULT_VALUE = 'Pick One:';
string queryString = "SELECT * FROM my_table";
//Populate Dictionary:
Dictionary<string,ComboBox > columnDictionary= new Dictionary<string, ComboBox>();
columnDictionary.Add("COL_A", comboBox1);
columnDictionary.Add("COL_B", comboBox2);
columnDictionary.Add("COL_C", comboBox3);
//etc...
List<KeyValuePair<string, ComboBox>> appendedColumns = new List<KeyValuePair<string, ComboBox>>();
foreach (KeyValuePair<string, ComboBox> entry in columnDictionary)
{
if (!String.Equals(entry.Value.Text, MY_DEFAULT_VALUE, StringComparison.OrdinalIgnoreCase))
{
string currentColumnName = entry.Key;
string currentColumnParameter = "#" + entry.Key;
if (appendedColumns.Count>1)
{
queryString += " AND ";
}
else
{
queryString += " WHERE ";
}
queryString += currentColumnName + " = " + currentColumnParameter;
appendedColumns.Add(entry);
}
}
cmd.CommandText = queryString;
if (appendedColumns.Count > 0)
{
foreach (KeyValuePair<string, ComboBox> entry in appendedColumns)
{
string currentColumnParameter = "#" + entry.Key;
string currentParameterValue = entry.Value.Text;
cmd.Parameters.AddWithValue(currentColumnParameter, currentParameterValue);
}
}
//Continue on your way...
Short answer to your question is, you cannot use '=' as you have shown. So change your code like this;
string aoi = comboBox1.Text;
string comp = comboBox2.Text;
string sql;
if (aoi == "Area Of Interest" && comp == "Company")
{
sql = #"SELECT * FROM JobListInfo WHERE AreaOfInterest Is Not NULL AND Company Is Not NULL";
}
else if(....)
{
sql =............................
}
else
{
sql = #"SELECT * FROM JobListIn WHERE AreaOfInterest = #AOI AND Company = #Company";
}
cmd.CommandText = sql;
cmd.Parameters.AddWithValue("#AOI", aoi);
cmd.Parameters.AddWithValue("#Company", comp);