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
Related
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)
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));
I created a user-defined table type in SQL Server:
CREATE TYPE dbo.TestType AS TABLE
(
ColumnA int,
ColumnB nvarchar(500)
)
And I'm using a stored procedure to insert records into the database:
create procedure [dbo].[sp_Test_CustomType]
#testing TestType READONLY
as
insert into [dbo].[myTable]
select ColumnA, ColumnB
from #testing
And I would like to use EF to execute this stored procedure, but here's the problem: how can I pass a user defined table to the stored procedure?
I tried adding the stored procedure to the model, but I'm unable to find the desired stored procedure in the updated context.
What I'm trying to do is to execute a bulk insert to a table, here's the method that I'm currently using:
List<items> itemToInsertToDB = //fetchItems;
foreach(items i in itemToInsertToDB)
{
context.sp_InsertToTable(i.ColumnA, i.ColumnB)
}
Currently, I use a foreach loop to loop through the list to insert item to DB, but if the list have a lot of items, then there will be a performance issue, so, I'm thinking of passing a list to the stored procedure and do the insert inside.
So how to solve this problem? or are there any better ways to do this?
Lets say you want to send a table with a single column of GUIDs.
First we need to create a structure using SqlMetaData which represents the schema of the table (columns).
The below code demonstrates one column named "Id" of the GUID is the SQL stored procedure parameter table type
var tableSchema = new List<SqlMetaData>(1)
{
new SqlMetaData("Id", SqlDbType.UniqueIdentifier)
}.ToArray();
Next you create a list of records that match the schema using SqlDataRecord.
The below code demonstrates how to add the items inside a list using the above created schema. Create a new SqlDataRecord for each of the items in the list. Replace SetGuid with the corresponding type and Replace Guid.NewGuid() as the corresponding value.
Repeat new SqlDataRecord for each item and add them to a List
var tableRow = new SqlDataRecord(tableSchema);
tableRow.SetGuid(0, Guid.NewGuid());
var table = new List<SqlDataRecord>(1)
{
tableRow
};
Then create the SqlParameter:
var parameter = new SqlParameter();
parameter.SqlDbType = SqlDbType.Structured;
parameter.ParameterName = "#UserIds"; //#UserIds is the stored procedure parameter name
parameter.TypeName = "{Your stored procedure type name}"
parameter.Value = table;
var parameters = new SqlParameter[1]
{
parameter
};
Then simply call the stored procedure by using the Database.SqlQuery.
IEnumerable<ReturnType> result;
using (var myContext = new DbContext())
{
result = myContext.Database.SqlQuery<User>("GetUsers #UserIds", parameters)
.ToList(); // calls the stored procedure
// ToListAsync(); // Async
{
In SQL Server, create your User-Defined Table Type (I suffix them with TTV, Table Typed Value):
CREATE TYPE [dbo].[UniqueidentifiersTTV] AS TABLE(
[Id] [uniqueidentifier] NOT NULL
)
GO
Then specify the type as a parameter (don't forget, Table Type Values have to be readonly!):
CREATE PROCEDURE [dbo].[GetUsers] (
#UserIds [UniqueidentifiersTTV] READONLY
) AS
BEGIN
SET NOCOUNT ON
SELECT u.* -- Just an example :P
FROM [dbo].[Users] u
INNER JOIN #UserIds ids On u.Id = ids.Id
END
I suggest you not using Stored Procedure to insert bulk data, but just rely to Entity Framework insert mechanism.
List<items> itemToInsertToDB = //fetchItems;
foreach(items i in itemToInsertToDB)
{
TestType t = new TestType() { ColumnA = i.ColumnA, ColumnB = i.ColumnB };
context.TestTypes.Add(t);
}
context.SaveChanges();
Entity framework will smartly perform those insertion in single transaction and (usually) in single query execution, which will almost equal to executing stored procedure. This is better rather than relying on stored procedure just to insert bulk of data.
In Transact-SQL I have the input parameter to the store procedure
#DataTable InputTabel READONLY,
And the "InputTabel" is defined as:
CREATE TYPE InputTabel AS TABLE
(
ID INT NULL,
RESULT_INDEX INT NULL
)
And on the C# side I use this code to define the parameter:
SqlParameter parameter = new SqlParameter( );
parameter.ParameterName = "#DataTable ";
parameter.SqlDbType = System.Data.SqlDbType.Structured;
parameter.TypeName = "InputTabel";
parameter.Value = theInputTable;
Where theInputTable is defined as:
DataTable theInputTable = new DataTable("TheInputTableName");
theInputTable.Columns.Add( "ID", typeof( Int32 ) );
theInputTable.Columns.Add( "RESULT_INDEX", typeof( string ) );
I now have to transfer this approach to MySQL and wounder how do I do that?
Both on C# side and on the server side.
Unfortunately MySql didn't implement table-valued parameters, but alternatives do exist:
Using a temporary table which drops immediately after the connection closes
but is visible to all queries coming via this connection. The only con is having to execute an additional query (or more) before the intended query, just to insert data into the temp table.
Using a delimited string ("str1,str2,str3") which significantly downgrades performance, especially when the string is very long.
For more info about both methods:
Create table variable in MySQL
I am working on a new project that needs to use Linq To SQL. I have been asked to create a generic or reusable Linq to SQL class that can be used to execute stored procedures.
In ADO.Net I knew how to do this by just passing in a string of what I wanted to execute and I could pass in different strings for each query I need to run:
SqlCommand cmd = new SqlCommand("myStoredProc", conn); // etc, etc
I am struggling with how to create something similar in Linq To SQL, if it is even possible. I have created a .dbml file and added my stored procedure to it. As a result, I can return the results using the code below:
public List<myResultsStoreProc> GetData(string connectName)
{
MyDataContext db = new MyDataContext (GetConnectionString(connectName));
var query = db.myResultsStoreProc();
return query.ToList();
}
The code works but they want me to create one method that will return whatever stored procedure I tell it to run. I have searched online and talked to colleagues about this and have been unsuccessful in finding a way to create reusable stored proc class.
So is there a way to create a reusable Linq to SQL class to execute stored procs?
Edit:
What I am looking for is if there is a way to do something like the following?
public List<string> GetData(string connectName, string procedureName)
{
MyDataContext db = new MyDataContext (GetConnectionString(connectName));
var query = db.procedureName();
return query.ToList();
}
I have reviewed the MSDN docs on Linq To Sql and these are showing the table in the IEnumerable:
IEnumerable<Customer> results = db.ExecuteQuery<Customer>(
#"select c1.custid as CustomerID, c2.custName as ContactName
from customer1 as c1, customer2 as c2
where c1.custid = c2.custid"
);
I am looking for something very generic, where I can send in a string value of the stored proc that I want to execute. If this is not possible, is there any documentation on why it cannot be done this way? I need to prove why we cannot pass a string value of the name of the procedure to execute in Linq To Sql
DataContext.ExecuteCommand is not quite what you are looking for, as it only returns an int value. What you want instead is DataContext.ExecuteQuery, which is capable of executing a stored procedure and returning a dataset.
I would create a partial class for your DBML in which to store this function.
public List<T> GetDataNoParams(string procname)
{
var query = this.ExecuteQuery<T>("Exec " + procname);
return query.ToList();
}
public List<T> GetDataParams(string procname, Object[] parameters)
{
var query = this.ExecuteQuery<T>("Exec " + procname, parameters);
return query.ToList();
}
To call a stored procedure you would do:
GetDataNoParams("myprocedurename");
or
GetDataParams("myotherprocedure {0}, {1}, {2}", DateTime.Now, "sometextValue", 12345);
or
GetDataParams("myotherprocedure var1={0}, var2={1}, var3={2}", DateTime.Now, "sometextValue", 12345);
If you want to call procedures with no return value that is easy enough too, as I'm sure you can see, by creating a new method that doesn't store/return anything.
The inspiration came from http://msdn.microsoft.com/en-us/library/bb361109(v=vs.90).aspx.
The simplest answer to your question is that you can grab the Connection property of your MyDataContext and create and execute your own SqlCommands just like you would in straight up ADO.Net. I'm not sure if that will serve your purposes, especially if you want to retrieve entities from your LINQ to SQL model.
If you want to return entities from the model, then have a look at the DataContext.ExecuteCommand method.
When we drop a Table or StoredProcedure in our .dbml file it creates its class which communicates with the data layer and our business logic.
In Linq to SQL we have to have the StoredProcedures or Tables present in the .dbml file otherwise there is no way to call a generic method in Linq to SQL for calling a stored procedure by passing its name to a method.
But in ADO.Net we can do it (like you know)
SqlCommand cmd = new SqlCommand("myStoredProc", conn);