ExecuteAsync Changes Passed Dynamic Parameters Names - c#

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));

Related

SQL multiple-value parameter in 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.

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)

Dapper to insert multiple rows into two tables using stored procedure

I am using Dapper (https://github.com/StackExchange/Dapper) in asp.net core myweb api project. I have a requirement to create a master record and insert a set of rows into a child table.
Ex. OrdersMaster table
create an order id in this table
OrderDetails table
ordermasterid
order items
How can I do this using Dapper? Please share some code snippets if possible.
I would implement the insert operations as a transactional stored procedure, and then call that from your .NET application.
You may need a table-valued type to pass in a list of data, like this:
CREATE TYPE List_Of_Items AS TABLE (
ItemID INT NOT NULL,
Quantity INT NOT NULL
)
The procedure might look like this
CREATE PROC Insert_Order_With_Details (
#CustomerId INT,
#Items List_Of_Items
) AS
BEGIN
BEGIN TRANSACTION
INSERT INTO OrdersMaster (CustomerId) VALUES #CustomerId
DECLARE #OrderID INT
SET #OrderID = SCOPE_IDENTITY() --last assigned id
INSERT INTO OrderDetails (OrderId, CustomerId, ItemId, Quantity)
SELECT #OrderID, #CustomerID, ItemID, Quantity
FROM #Items
COMMIT
END
Then in C#, I would suggest creating methods for creating your TVP. It is not as simple as you might like. This requires the using Microsoft.SqlServer.Server and using Dapper.Tvp.
//This is a shell to create any kind of TVP
private static void AddTableCore<T>(
this DynamicParametersTvp dp,
string tvpTypeName,
Func<T, SqlDataRecord> valueProjection,
IEnumerable<T> values,
string parameterTableName)
{
var tvp = values
.Select(valueProjection)
.ToList();
//If you pass a TVP with 0 rows to SQL server it will error, you must pass null instead.
if (!tvp.Any()) tvp = null;
dp.Add(new TableValueParameter(parameterTableName, tvpTypeName, tvp));
}
//This will create your specific Items TVP
public static void AddItemsTable(this DynamicParametersTvp dp, IEnumerable<Item> items, string parameterTableName = "Items")
{
var columns = new[]
{
new SqlMetaData("ItemID", SqlDbType.Int)
new SqlMetaData("Quantity", SqlDbType.Int)
};
var projection = new Func<Item, SqlDataRecord>(item =>
{
var record = new SqlDataRecord(columns);
record.SetInt32(0, item.Id);
record.SetInt32(1, item.Quantity);
return record;
});
AddTableCore(dp, "Items", projection, items, parameterTableName);
}
and then where you need to query you might do:
using (var cn = new SqlConnection(myConnectionString))
{
var p = new DynampicParametersTvp(new {
CustomerId = myCustomerId
});
p.AddItemsTable(items);
cn.Execute("Insert_Order_With_Details", p, commandType: CommandType.StoredProcedure);
}
The commandType argument is super important. It defaults to plain SQL text and will error if you send the name of a proc.
If you want to put in multiple orders at once, you'll need to use table-valued parameters and the Dapper.Tvp package.
See this SO question Using Dapper.TVP TableValueParameter with other parameters as well as this documentation on TVP's from Microsoft https://learn.microsoft.com/en-us/sql/relational-databases/tables/use-table-valued-parameters-database-engine. I don't think all SQL vendors support TVPs.
Using stored procedure as mentioned in other answer is good solution. My answer implements the same without Stored Procedure.
You have to use transaction. This way, either all the changes will be committed or rolled back. Code below assumes you are using Identity as Primary Key. Please refer this question for discussion about ##IDENTITY that I have used in below code.
Though code is not complete, I have put detailed comments to explain steps.
using (var connection = new SqlCeConnection("connection_string"))
{
connection.Open();
//Begin the transaction
using (var transaction = connection.BeginTransaction())
{
//Create and fill-up master table data
var paramMaster = new DynamicParameters();
paramMaster.Add("#XXX", ...);
....
//Insert record in master table. Pass transaction parameter to Dapper.
var affectedRows = connection.Execute("insert into OrdersMaster....", paramMaster, transaction: transaction);
//Get the Id newly created for master table record.
//If this is not an Identity, use different method here
newId = Convert.ToInt64(connection.ExecuteScalar<object>("SELECT ##IDENTITY", null, transaction: transaction));
//Create and fill-up detail table data
//Use suitable loop as you want to insert multiple records.
//for(......)
foreach(OrderItem item in orderItems)
{
var paramDetails = new DynamicParameters();
paramDetails.Add("#OrderMasterId", newId);
paramDetails.Add("#YYY", ...);
....
//Insert record in detail table. Pass transaction parameter to Dapper.
var affectedRows = connection.Execute("insert into OrderDetails....", paramDetails, transaction: transaction);
}
//Commit transaction
transaction.Commit();
}
}

how to run stored procedure in Entity framework

I have a Query in Database which is bringing another query in response using Json via Ajax. I have created the stored procedure which is accepting that query and bringing multiple columns.
I am not getting how to run stored procedure in entity framework.
Help needed.
Method:-
public ActionResult DropDownn(string query)
{
using (DynamicDBEntities db = new DynamicDBEntities())
{
//var dbQuery = db.Database.SqlQuery<Cust_mas>(query).ToList();
//return Json(courseList, JsonRequestBehavior.AllowGet);
}
}
SP:-
alter procedure [dbo].[SP_DynamicCtrl]
#query nvarchar(1000)
As
begin
execute sp_executesql #query;
end
As per my understanding, you want to execute a stored procedure that run on multiple tables, and then return Json Data to View. You can actually do something like below:
Approach 1: (Using ExecuteSqlCommand)
SqlParameter param1 = new SqlParameter("#query", query);
var result = db.Database.ExecuteSqlCommand("SP_DynamicCtrl #query",
param1);
Approach 2: (Using Object Type on SqlQuery)
SqlParameter param1 = new SqlParameter("#query", query);
Var result = db.Database.SqlQuery<Object>("exec SP_DynamicCtrl #query", param1);
Approach 3: (Cleaner Approach)
1.) Create a model as per your return parameters from stored procedure, let's call it YourType class.
2.) Use the below code to call stored pocedure:
SqlParameter param1 = new SqlParameter("#query", query);
Var result = db.Database.SqlQuery<YourType>("exec SP_DynamicCtrl #query", param1);
After you get the result from above query, you can convert it to JSON befor returning in controller:
return Json(result, JsonRequestBehavior.AllowGet); //Typecast the result as per your need
Please modify code as per your need.
if you have mapped it in the edmx try this
public ActionResult DropDownn(string query)
{
using (DynamicDBEntities db = new DynamicDBEntities())
{
var result = context.SP_DynamicCtrl(query);
return result.FirstOrDefault();
}
}

How to send string-named arguments to a stored procedure?

I have a stored procedure with a large number of parameters (>50), the vast majority of which are optional. I would like to be able to call it like this:
var result = context.MySproc(
new Dictionary<string, string>()
{
{"foo","bar"},
{"baz","xyzzy"}
});
The parameters needed will vary dynamically.
The DB call must be a stored procedure (not up to me), and it must use the existing DataContext rather than setting up a new connection.
If I understood you correctly, than this is the article telling how to call a ms sql stored procedure with optional parameters. And this is how I used it to call such stored proc with LINQ to SQL:
1) Suppose you have a stored proc with optional parameters like:
CREATE PROCEDURE [dbo].[MyProc]
#arg1 bigint,
#arg2 datetime,
#arg3 decimal(18,2)=null,
#arg4 decimal(18,2)=null,
#arg5 int
BEGIN
...
END
2) You have some DataContext using LINQ to SQL
DataContext dc = new DataContext("Your connection string, for example");
3) Stored proc name
string procName = "dbo.MyProc";
4) Params dictionary (for example):
Dictionary<string, object> paramList = new Dictionary<string, object>()
{
{"arg1",72},
{"arg2",DateTime.Now.Date},
//arg3 is omitted and should get its default value in stored
{"arg4",250}, proc
{"arg5",777}
};
5) Then you may use the following method:
static int Foo(DataContext dc,
string procName,
Dictionary<string, object> paramList)
{
StringBuilder command = new StringBuilder("EXECUTE ");
command.Append(procName);
int i = 0;
foreach (string key in paramList.Keys)
{
command.AppendFormat(" #{0} = ", key);
command.Append("{");
command.Append(i++);
command.Append("},");
}
return dc.ExecuteCommand(command.ToString().TrimEnd(','),
paramList.Values.ToArray());
}
like this
//You should add exception handling somewhere, of course, as you need
Foo(dc, procName, paramList);
It will invoke your stored procedure. Compulsory params should always be present in the dictionary or it will fail. Optional parameters may be omitted, then they'll get the default values, defined by the stored procedure itself.
I used Dictionary<string,object>, so it may contain not only string values, but any type of parameters. Of course, they should reflect what the stored procedure expects.
P.S.: I tested on ms sql 2008, I'm not completely sure, how it'll work on ms sql 2005
In SQL Server, the number of parameters must be static, so you won't be able to do what you want.
You have some other solutions:
1: Use 1 delimited string as a parameter and then substring the parameter in your stored proc.
2: Save those 50 or so strings in a table (attached to a unique ID), use that table from your stored procedure (using the unique ID as the only parameter) and then make your stored procedure delete those temporary strings.
The only way to do this would be creating an xml file and send it to your proc. You can fetch all parameters within sql.
How to read xml in t-sql?
http://www.stylusstudio.com/sqlxml_tutorial.html

Categories