SQL multiple-value parameter in C# - c#

This is what I have:
select *
from AuditQuestionnaires
where Companyid = #companyid
and RiskTypeID != #RiskIdList[x];
Why can't I add a parameter like this:
command2.Parameters.Add(new SqlParameter("RiskIdList[x]", RiskIdList[x]));
I'm using SQL Server, and this is the script I'm using in .cs file

Firstly, since you are using SQL Server, you cannot have a parameter named #RiskTypeId[x]. It is simply not a valid name.
Secondly, when passsing parameters from c# the parameter name has to include the initial "#".
Thirdly, as a general rule it is much better to construct the SQLParameter including the DataType. This means that there is no guesswork required, and guesses occasionally go wrong.
Fourthly, it seems to me that you want to iterate through an array. There is nothing to stop you doing this in c#. You would simply create a SqlParameter like this
command2.Parameters.Add(new SqlParameter { ParameterName = "#RiskTypeId", DBType = DBType.Int32, Value = RiskIDList[x] });
However I suspect that what you are really trying to do is to send a list to SQL Server. To achieve this, you need to use a User-Defined Table Type. In your case the following is sufficient:
CREATE TYPE [dbo].[IdList] AS TABLE ([Id] int);
Then you can define a parameter to your query as:
#RiskTypeIdList IdList READONLY
Finally you have to put the contents of your array into a DataTable. Because this is something I do frequently, I have a Class for this job:
public class TDataTable<int> : DataTable
{
public TDataTable(IEnumerable<T> ids) : base()
{
Columns.Add("Id", typeof(T));
var added = new HashSet<T>();
foreach (T id in ids)
{
//ensure unique values
if (added.Add(id))
{
Rows.Add(id);
}
}
}
public TDataTable() : base()
{
Columns.Add("Id", typeof(T));
}
}
You can then pass the parameter like this:
command2.Parameters.Add(new SqlParameter { ParameterName = "#RiskTypeIdList", SqlDBType = SqlDBType.Structured, Value = new TDataTable<int>(RiskIDList) });
Within your SQL you can then do what Thorsten suggested:
WHERE RiskTypeId NOT IN (SELECT Id FROM #RiskTypeIdList)

#Thorsten Kettner, its a valid SQL query, we can != in sql query.
command2.Parameters.Add(new SqlParameter("RiskIdList[x]", RiskIdList[x]));
We cannot pass "RiskIdList[x]" as SQL Parameter, we need to pass parameter like this "RiskIdList", RiskIdList[x] means that you are passing argument at runtime, but query or stored procedure has not dynamic parameter in where clause.

Related

Dapper stored procedure has too many arguments specified when passing IEnumerable to it

I'm calling my procedure by this method:
public async Task<IEnumerable<Algorithm>> GetAlgorithmsByNameAsync(IEnumerable<string> names)
{
var parameters = new DynamicParameters();
parameters.Add("#names", names);
var connection = _connection.GetOpenConnection();
return await connection.QueryAsync<Algorithm>("GetAlgorithmsByName", parameters, commandType: CommandType.StoredProcedure);
}
My Procedure looks like this:
CREATE TYPE [dbo].[StringList] AS TABLE(
[Item] [NVARCHAR](MAX) NULL
);
--PROCEDURE HERE--
CREATE PROCEDURE GetAlgorithmsByName
#names StringList READONLY -- my own type
AS
BEGIN
SELECT ALgorithmId, Name From Algorithms WHERE Name IN (SELECT Item FROM #names)
END
From the code above, I get an error:
"Procedure or function GetAlgorithmsByName has too many arguments specified."
What am I doing wrong? How do I pass IEnumerable<string> to a stored procedure using dapper?
Table valued parameters aren't trivial to use; one way is via the extension method that Dapper adds on DataTable (something like AsTableValuedParameter), but: it doesn't work as simply as IEnumerable<T> - at least, not today. You also probably don't need DynamicParameters here.
If what you want is just a set of strings, then one very pragmatic option is to look at the inbuilt string_split API in SQL Server, if you can define a separator token that is never used in the data. Then you can just pass a single delimited string.
In your stored procedure is expecting [Item] [NVARCHAR](MAX), it means one item Whereas you are passing IEnumerable<string> names. So that's the reason why you are getting the error.
There are numerous way to pass the list of string to sp
XML
Using table-valued parameters like CREATE TYPE NameList AS TABLE ( Name Varchar(100) );
Using names = "Name1, Name2, .. , Namen"; then sql you can use T-SQL split string to get the name list
Updated
You are passing param incorrectly, Let's fix it by this way
using (var table = new DataTable())
{
table.Columns.Add("Item", typeof(string));
for (int i = 0; i < names.length; i++)
table.Rows.Add(i.ToString());
var pList = new SqlParameter("#names", SqlDbType.Structured);
pList.TypeName = "dbo.StringList";
pList.Value = table;
parameters.Add(pList);
}
You can use the IEnumerable (dynamic) rather than IEnumerable (string).
Check this link and try How to Implement IEnumerable (dynamic)

ExecuteAsync Changes Passed Dynamic Parameters Names

I am trying to insert form data to a table using a stored procedure by calling the ExecuteAsync method of Dapper. After sending the parameters names are changed.
public static async Task<bool> Insert(DynamicParameters dp)
{
int IsSuccessed;
using (SqlConnection c = new SqlConnection(con))
{
IsSuccessed = await c.ExecuteAsync("AddStudent", dp, commandType: CommandType.StoredProcedure);
}
return IsSuccessed > 0 ? true : false;
}
Parameters are changed showing in a sql server Profiler
declare #p8 nvarchar(100)
set #p8=NULL
exec AddStudent #Name1=N'Ahmad',#TazkiraNumber1=N'890',#TazkiraPage1=N'0987',#TazkiraVolume1=N'8',#GenderID1=N'1',#VisitorSourceID1=N'2',#msg=#p8 output
select #p8
The following code make a dynamic parameters from form collection:
var dp = new DynamicParameters();
foreach (string key in form.Keys)
{
dp.Add(key.ToString(), form[key]);
}
I am getting form data using IFormCollection and creating DynamicParameters from the key and value of form collection and pass the dynamic parameter to the ExecuteAsync method and it calls the stored procedure to insert the data to the tables. The process of executing the stored procedure fails.
I run the SQL server profiler and observed that 1 is appended to each parameter as #Name became #Name1.
Any idea why is this happing?
For this issue, it is caused by that, StringValues is failed to converted to the DbType.
Try code below:
foreach (string key in form.Keys)
{
dp.Add(key.ToString(), form[key].ToString());
}
In my case, it was a collection of System.Guid I was trying to pass.
var guids = new List<System.Guid>()
guids.Add(new System.Guid());//for the brevity of example
guids.Add(new System.Guid());//for the brevity of example
parameters.Add("#parameterName", guids);
I had to change the last line like below.
parameters.Add("#parameterName", string.Join(",", guids));

Inserting byte array into SQL Server

I am constructing a sql_insert_string to be used in Microsoft.ApplicationBlocks.Data.SqlHelper to be used as follows:
SqlHelper.ExecuteNonQuery(Transaction, CommandType.Text, sql_insert_string)
When I hover over the SQL statement it looks like below:
string sql_insert_string = "Insert into images_table(image_id, image_byte_array) values ('123', System.Byte[])
One of the insert value is a byte array as shown above. The variable has value in the byte array, say like byte[6738] . But after the sql_insert_string is constructed, it comes as System.Byte[]. The image_byte_array column type is varbinary(max). The database is SQL Server 2008. Because of this the database throws the following error:
An object or column name is missing or empty. For SELECT INTO statements, verify each column has a name. For other statements, look for empty alias names. Aliases defined as \"\" or [] are not allowed. Change the alias to a valid name.
you can insert the byte array like so:
private void FireSql(byte[] input)
{
const string sql_insert_string =
"Insert into images_table(image_id, image_byte_array) values (#image_id, #image_byte_array)";
SqlTransaction transaction = null; //wherever you get the transaction obj from.
var imageIdParam = new SqlParameter("#image_id", SqlDbType.Int, 4)
{
Direction = ParameterDirection.Input,
Value = 123
}; //change the data type to whatever data type you are expecting
var byteParam = new SqlParameter("#image_byte_array", SqlDbType.VarBinary)
{
Direction = ParameterDirection.Input,
Size = input.Length,
Value = input
}; //change the data type to whatever data type you are expecting
SqlHelper.ExecuteNonQuery(transaction, CommandType.Text, sql_insert_string, imageIdParam, byteParam);
}
I would suggest looking at an ORM (https://en.wikipedia.org/wiki/Object-relational_mapping) like Entity Framework(http://www.asp.net/entity-framework) to do all of this for you while increasing security and future changes much easier.
You should be using the Parameters while constructing the SQL Query which obviously will avoid SQL Injection attacks. How your queries are getting constructed is still unclear here.
Something like this should do it for you.
SqlParameter sParam = new SqlParameter("#image_byte_array", SqlDbType.VarBinary)
{
Value = image
};
SqlHelper.ExecuteNonQuery(Transaction, CommandType.Text, sql_insert_string, sParam)
You may use
string sql_insert_string =
String.Format("INSERT INTO images_table(image_id, image_byte_array) VALUES ('123', CAST('{0}' AS VARBINARY(MAX)))", System.Byte[].ToString());
And yes, as #marc_s commented, you shouldn't be constructing SQL statements as a security concern.

Parse sql parameters from commandtext

Is it possible to parse sql parameters from plain commandtext?
e.g.
//cmdtext = SELECT * FROM AdWorks.Countries WHERE id = #id
SqlCommand sqlc = new SqlCommand(cmdtext);
SqlParameterCollection parCol = sqlc.Parameters //should contain now 1 paramter called '#id'
If a SQL Server is available, the best option may be to simply ask the server what it thinks; the server has parsing and metadata functions built in, for example sp_describe_undeclared_parameters.
I ended up with this extention method (since I don't think there's a built in function):
public static class SqlParExtension
{
public static void ParseParameters(this SqlCommand cmd)
{
var rxPattern = #"(?<=\= |\=)#\w*";
foreach (System.Text.RegularExpressions.Match item in System.Text.RegularExpressions.Regex.Matches(cmd.CommandText, rxPattern))
{
var sqlp = new SqlParameter(item.Value, null);
cmd.Parameters.Add(sqlp);
}
}
}
usage:
//cmdtext = SELECT * FROM AdWorks.Countries WHERE id = #id
SqlCommand sqlc = new SqlCommand(cmdtext);
sqlc.ParseParameters();
sqlc.Parameters["#id"].Value = value;
I will have to make sure about this but I'm sure you must add the range of parameters to the command. Like I say I will have to come back with this but you can try doing something like:
// Create a collection of parameters with the values that the procedure is expecting in your SQL client.
SqlParameter[] parameters = { new SqlParameter("#id", qid),
new SqlParameter("#otherValue", value) };
// Add teh parameters to the command.
sqlc.Parameters.AddRange(parameters)
You would be very welcome to have a look at my VS2015 extension, QueryFirst, that generates wrapper classes from .sql files, harvesting parameter declarations directly from your sql. You need to declare your parameters in the --designTime section of your request, but then you find them again directly as inputs to the Execute(), GetOne() or ExecuteScalar() methods. These methods return POCOs with meaningul property names. There's intellisense everywhere, and you don't have to type a line of parameter code, or connection code, or command code, or reader code, among NUMEROUS OTHER ADVANTAGES :-).

OleDbParameters and Parameter Names

I have an SQL statement that I'm executing through OleDb, the statement is something like this:
INSERT INTO mytable (name, dept) VALUES (#name, #dept);
I'm adding parameters to the OleDbCommand like this:
OleDbCommand Command = new OleDbCommand();
Command.Connection = Connection;
OleDbParameter Parameter1 = new OleDbParameter();
Parameter1.OleDbType = OleDbType.VarChar;
Parameter1.ParamterName = "#name";
Parameter1.Value = "Bob";
OleDbParameter Parameter2 = new OleDbParameter();
Parameter2.OleDbType = OleDbType.VarChar;
Parameter2.ParamterName = "#dept";
Parameter2.Value = "ADept";
Command.Parameters.Add(Parameter1);
Command.Parameters.Add(Parameter2);
The problem I've got is, if I add the parameters to command the other way round, then the columns are populated with the wrong values (i.e. name is in the dept column and vice versa)
Command.Parameters.Add(Parameter2);
Command.Parameters.Add(Parameter1);
My question is, what is the point of the parameter names if parameters values are just inserted into the table in the order they are added command? The parameter names seems redundant?
The Problem is that OleDb (and Odbc too) does not support named parameters.
It only supports what's called positional parameters.
In other words: The name you give a parameter when adding it to the commands parameters list does not matter. It's only used internally by the OleDbCommand class so it can distinguish and reference the parameters.
What matters is the order in which you add the parameters to the list. It must be the same order as the parameters are referenced in the SQL statement via the question mark character (?).
But here is a solution that allows you to use named parameters in the SQL statement. It basically replaces all parameter references in the SQL statement with question marks and reorders the parameters list accordingly.
It works the same way for the OdbcCommand class, you just need to replace "OleDb" with "Odbc" in the code.
Use the code like this:
command.CommandText = "SELECT * FROM Contact WHERE FirstName = #FirstName";
command.Parameters.AddWithValue("#FirstName", "Mike");
command.ConvertNamedParametersToPositionalParameters();
And here is the code
public static class OleDbCommandExtensions
{
public static void ConvertNamedParametersToPositionalParameters(this OleDbCommand command)
{
//1. Find all occurrences of parameter references in the SQL statement (such as #MyParameter).
//2. Find the corresponding parameter in the commands parameters list.
//3. Add the found parameter to the newParameters list and replace the parameter reference in the SQL with a question mark (?).
//4. Replace the commands parameters list with the newParameters list.
var newParameters = new List<OleDbParameter>();
command.CommandText = Regex.Replace(command.CommandText, "(#\\w*)", match =>
{
var parameter = command.Parameters.OfType<OleDbParameter>().FirstOrDefault(a => a.ParameterName == match.Groups[1].Value);
if (parameter != null)
{
var parameterIndex = newParameters.Count;
var newParameter = command.CreateParameter();
newParameter.OleDbType = parameter.OleDbType;
newParameter.ParameterName = "#parameter" + parameterIndex.ToString();
newParameter.Value = parameter.Value;
newParameters.Add(newParameter);
}
return "?";
});
command.Parameters.Clear();
command.Parameters.AddRange(newParameters.ToArray());
}
}
Parameter NAMES are generic in the SQL support system (i.e. not OleDb specific). Pretty much ONLY OleDb / Odbc do NOT use them. They are there because OleDb is a specific implementation of the generic base classes.

Categories