Pass Dictionary<string,int> to Stored Procedure T-SQL - sql-server

I have mvc application. In action I have Dictionary<string,int>. The Key is ID and Value is sortOrderNumber. I want to create stored procedure that will be get key(id) find this record in database and save orderNumber column by value from Dictionary. I want to call stored procedure once time and pass data to it, instead of calling many times for updating data.
Have you any ideas?
Thanks!

The accepted answer of using a TVP is generally correct, but needs some clarification based on the amount of data being passed in. Using a DataTable is fine (not to mention quick and easy) for smaller sets of data, but for larger sets it does not scale given that it duplicates the dataset by placing it in the DataTable simply for the means of passing it to SQL Server. So, for larger sets of data there is an option to stream the contents of any custom collection. The only real requirement is that you need to define the structure in terms of SqlDb types and iterate through the collection, both of which are fairly trivial steps.
A simplistic overview of the minimal structure is shown below, which is an adaptation of the answer I posted on How can I insert 10 million records in the shortest time possible?, which deals with importing data from a file and is hence slightly different as the data is not currently in memory. As you can see from the code below, this setup is not overly complicated yet highly flexible as well as efficient and scalable.
SQL object # 1: Define the structure
-- First: You need a User-Defined Table Type
CREATE TYPE dbo.IDsAndOrderNumbers AS TABLE
(
ID NVARCHAR(4000) NOT NULL,
SortOrderNumber INT NOT NULL
);
GO
SQL object # 2: Use the structure
-- Second: Use the UDTT as an input param to an import proc.
-- Hence "Tabled-Valued Parameter" (TVP)
CREATE PROCEDURE dbo.ImportData (
#ImportTable dbo.IDsAndOrderNumbers READONLY
)
AS
SET NOCOUNT ON;
-- maybe clear out the table first?
TRUNCATE TABLE SchemaName.TableName;
INSERT INTO SchemaName.TableName (ID, SortOrderNumber)
SELECT tmp.ID,
tmp.SortOrderNumber
FROM #ImportTable tmp;
-- OR --
some other T-SQL
-- optional return data
SELECT #NumUpdates AS [RowsUpdated],
#NumInserts AS [RowsInserted];
GO
C# code, Part 1: Define the iterator/sender
using System.Collections;
using System.Data;
using System.Data.SqlClient;
using System.IO;
using Microsoft.SqlServer.Server;
private static IEnumerable<SqlDataRecord> SendRows(Dictionary<string,int> RowData)
{
SqlMetaData[] _TvpSchema = new SqlMetaData[] {
new SqlMetaData("ID", SqlDbType.NVarChar, 4000),
new SqlMetaData("SortOrderNumber", SqlDbType.Int)
};
SqlDataRecord _DataRecord = new SqlDataRecord(_TvpSchema);
StreamReader _FileReader = null;
// read a row, send a row
foreach (KeyValuePair<string,int> _CurrentRow in RowData)
{
// You shouldn't need to call "_DataRecord = new SqlDataRecord" as
// SQL Server already received the row when "yield return" was called.
// Unlike BCP and BULK INSERT, you have the option here to create an
// object, do manipulation(s) / validation(s) on the object, then pass
// the object to the DB or discard via "continue" if invalid.
_DataRecord.SetString(0, _CurrentRow.ID);
_DataRecord.SetInt32(1, _CurrentRow.sortOrderNumber);
yield return _DataRecord;
}
}
C# code, Part 2: Use the iterator/sender
public static void LoadData(Dictionary<string,int> MyCollection)
{
SqlConnection _Connection = new SqlConnection("{connection string}");
SqlCommand _Command = new SqlCommand("ImportData", _Connection);
SqlDataReader _Reader = null; // only needed if getting data back from proc call
SqlParameter _TVParam = new SqlParameter();
_TVParam.ParameterName = "#ImportTable";
// _TVParam.TypeName = "IDsAndOrderNumbers"; //optional for CommandType.StoredProcedure
_TVParam.SqlDbType = SqlDbType.Structured;
_TVParam.Value = SendRows(MyCollection); // method return value is streamed data
_Command.Parameters.Add(_TVParam);
_Command.CommandType = CommandType.StoredProcedure;
try
{
_Connection.Open();
// Either send the data and move on with life:
_Command.ExecuteNonQuery();
// OR, to get data back from a SELECT or OUTPUT clause:
SqlDataReader _Reader = _Command.ExecuteReader();
{
Do something with _Reader: If using INSERT or MERGE in the Stored Proc, use an
OUTPUT clause to return INSERTED.[RowNum], INSERTED.[ID] (where [RowNum] is an
IDENTITY), then fill a new Dictionary<string, int>(ID, RowNumber) from
_Reader.GetString(0) and _Reader.GetInt32(1). Return that instead of void.
}
}
finally
{
_Reader.Dispose(); // optional; needed if getting data back from proc call
_Command.Dispose();
_Connection.Dispose();
}
}

Using Table Valued parameters is really not that complex.
given this SQL:
CREATE TYPE MyTableType as TABLE (ID nvarchar(25),OrderNumber int)
CREATE PROCEDURE MyTableProc (#myTable MyTableType READONLY)
AS
BEGIN
SELECT * from #myTable
END
this will show how relatively easy it is, it just selects out the values you sent in for demo purposes. I am sure you can easily abstract this away in your case.
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
namespace TVPSample
{
class Program
{
static void Main(string[] args)
{
//setup some data
var dict = new Dictionary<string, int>();
for (int x = 0; x < 10; x++)
{
dict.Add(x.ToString(),x+100);
}
//convert to DataTable
var dt = ConvertToDataTable(dict);
using (SqlConnection conn = new SqlConnection("[Your Connection String here]"))
{
conn.Open();
using (SqlCommand comm = new SqlCommand("MyTableProc",conn))
{
comm.CommandType=CommandType.StoredProcedure;
var param = comm.Parameters.AddWithValue("myTable", dt);
//this is the most important part:
param.SqlDbType = SqlDbType.Structured;
var reader = comm.ExecuteReader(); //or NonQuery, etc.
while (reader.Read())
{
Console.WriteLine("{0} {1}", reader["ID"], reader["OrderNumber"]);
}
}
}
}
//I am sure there is a more elegant way of doing this.
private static DataTable ConvertToDataTable(Dictionary<string, int> dict)
{
var dt = new DataTable();
dt.Columns.Add("ID",typeof(string));
dt.Columns.Add("OrderNumber", typeof(Int32));
foreach (var pair in dict)
{
var row = dt.NewRow();
row["ID"] = pair.Key;
row["OrderNumber"] = pair.Value;
dt.Rows.Add(row);
}
return dt;
}
}
}
Produces
0 100
1 101
2 102
3 103
4 104
5 105
6 106
7 107
8 108
9 109

Stored procedures do not support arrays as inputs. Googling gives a couple of hacks using XML or comma separated strings, but those are hacks.
A more SQLish way to do this is to create a temporary table (named e.g. #Orders) and insert all the data into that one. Then you can call the sp, using the same open Sql Connection and insie the SP use the #Orders table to read the values.
Another solution is to use Table-Valued Parameters but that requires some more SQL to setup so I think it is probably easier to use the temp table approach.

Related

Dapper - return GUID generated by SQL Server

All of the Dapper posts on S/O related to returning a value after insert seem to relate only to the Identity value. I have tried to apply the logic in those answers to my issue, but it's not working.
I want to return a GUID generated by SQL Server which is not an Identity field. This is my code:
public bool Insert(Record record)
{
SqlConnection connection = new SqlConnection(_configuration.GetConnectionString("Production"));
connection.Open();
using (connection)
{
string query = "DECLARE #RSGUID uniqueidentifier SET #RSGUID = NEWID(); INSERT INTO [dbo].[Table] ([Result], [ResultSetKey]) VALUES (#Result, #RSGUID); SELECT #RSGUID";
// it's this next line that I'm confused on:
var resultSetKey = connection.Query<string>(query, #RSGUID).Single();
return connection.Execute(query, record) > 0;
}
}
I know the var resultsSetKey line is not correct, and this is what I need help with. How can I get the GUID generated by SQL Server into a variable?
I can't test right now, but this should work:
public bool Insert(Record record)
{
using (var connection = new SqlConnection(_configuration.GetConnectionString("Production"))
{
string query = "DECLARE #RSGUID uniqueidentifier; SET #RSGUID = NEWID(); INSERT INTO [dbo].[Table] ([Result], [ResultSetKey]) VALUES (#Result, #RSGUID); SELECT #RSGUID";
var resultSetKey = connection.ExecuteScalar<string>(query).SingleOrDefault();
return !String.IsNullOrEmpty(resultSetKey);
}
}
You can get a kickstart with Dapper following the tutorial here:
https://medium.com/dapper-net/get-started-with-dapper-net-591592c335aa
but honestly it's really not clear what you are trying to do. What is the #Result variable that you are using in the T-SQL code?
A very old question, I know - but I've just had exactly the same issue here and found a working solution, so I'm sure somebody else will in benefit from this late answer in the future.
The following works as desired. The key for me was understanding the datatype to use in C# to contain the returned stream_id value, which is System.Guid, not string. Though of course you may wish to convert it to a string to display it to a user for example as plain text.
My stored procedure for entering a new file into the filetable is
USE [dbname]
GO
/****** Object: StoredProcedure [dbo].[newFile] Script Date: 28/11/2022 14:34:50 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[newFile]
#fileBinary varbinary(max) = 0,
#fileName varchar(256) = 0
AS
BEGIN
SET NOCOUNT ON;
DECLARE #newFileId table (stream_id uniqueidentifier);
INSERT INTO dbo.files (file_stream, name) OUTPUT INSERTED.[stream_id] into #newFileId
VALUES (#fileBinary, #fileName);
SELECT * FROM #newFileId
END
And here is the method to add said new file programmatically (I'm fully aware this example includes no error handling etc)
public class fileAccess
{
public System.Guid newFileId;
public void uploadNewFile(byte[] newFileBinary, string newFileName)
{
var procedure = "[newFile]";
var parameters = new
{
fileBinary = newFileBinary,
fileName = newFileName,
};
using (DbConnection connection = new Microsoft.Data.SqlClient.SqlConnection(helper.cnnVal("dbname")))
{
newFileId = connection.ExecuteScalar<System.Guid>(procedure, parameters, commandType: CommandType.StoredProcedure);
}
}
}
Called here (a test example, uploading a file by clicking a button, which pulls a file from my desktop)
private void button6_Click(object sender, EventArgs e)
{
fileAccess db = new fileAccess();
byte[] fileBinary = File.ReadAllBytes(#"C:\Users\username\Desktop\test.docx");
db.uploadNewFile(fileBinary, "test.docx");
string message = "New file succesfully uploaded. File ID: " + Convert.ToString(db.newFileId);
string caption = "Success.";
MessageBox.Show(message, caption, MessageBoxButtons.OK);
}

best solution for multiple insert update solution

Struggle with understanding C# & Npgsql as a beginner. Following code examples:
// Insert some data
using (var cmd = new NpgsqlCommand())
{ cmd.Connection = conn;
cmd.CommandText = "INSERT INTO data (some_field) VALUES (#p)";
cmd.Parameters.AddWithValue("p", "Hello world");
cmd.ExecuteNonQuery();
}
The syntax for more than one insert & update statement like this is clear so far:
cmd.CommandText = "INSERT INTO data (some_field) VALUES (#p);INSERT INTO data1...;INSERT into data2... and so on";
But what is the right solution for a loop which should handle one statement within.
This works not:
// Insert some data
using (var cmd = new NpgsqlCommand())
{
foreach(s in SomeStringCollectionOrWhatever)
{
cmd.Connection = conn;
cmd.CommandText = "INSERT INTO data (some_field) VALUES (#p)";
cmd.Parameters.AddWithValue("p", s);
cmd.ExecuteNonQuery();
}
}
It seems the values will be "concatenated" or remembered. I cannot see any possibility to "clear" the existing cmd-object.
My second solution would be to wrap the whole "using" block into the loop. But every cycle would create a new object. That seems ugly to me.
So what is the best solution for my problem?
To insert lots of rows efficiently, take a look at Npgsql's bulk copy feature - the API is more suitable (and more efficient) for inserting large numbers of rows than concatenating INSERT statements into a batch like you're trying to do.
If you want to rerun the same SQL with changing parameter values, you can do the following:
using (var cmd = new NpgsqlCommand("INSERT INTO data (some_field) VALUES (#p)", conn))
{
var p = new NpgsqlParameter("p", DbType.String); // Adjust DbType according to type
cmd.Parameters.Add(p);
cmd.Prepare(); // This is optional but will optimize the statement for repeated use
foreach(var s in SomeStringCollectionOrWhatever)
{
p.Value = s;
cmd.ExecuteNonQuery();
}
}
If you need lots of rows and performance is key then i would recommend Npgsql's bulk copy capability as #Shay mentioned. But if you are looking for quick way to do this without the bulk copy i would recommend to use Dapper.
Consider the example below.
Lets say you have a class called Event and a list of events to add.
List<Event> eventsToInsert = new List<Event>
{
new Event() { EventId = 1, EventName = "Bday1" },
new Event() { EventId = 2, EventName = "Bday2" },
new Event() { EventId = 3, EventName = "Bday3" }
};
The snippet that would add the list to the DB shown below.
var sqlInsert = "Insert into events( eventid, eventname ) values (#EventId, #EventName)";
using (IDbConnection conn = new NpgsqlConnection(cs))
{
conn.Open();
// Execute is an extension method supplied by Dapper
// This code will add all the entries in the eventsToInsert List and match up the values based on property name. Only caveat is that the property names of the POCO should match the placeholder names in the SQL Statement.
conn.Execute(sqlInsert, eventsToInsert);
// If we want to retrieve the data back into the list
List<Event> eventsAdded;
// This Dapper extension will return an Ienumerable, so i cast it to a List.
eventsAdded = conn.Query<Event>("Select * from events").ToList();
foreach( var row in eventsAdded)
{
Console.WriteLine($"{row.EventId} {row.EventName} was added");
}
}
-HTH

How to call stored procedure from EntityFramework 6 with 'hierarchyid' parameter

I am developing a service using WebApi2 and EntityFramework6.
I have a legacy SQLServer DB that my service must work with.
That DB is using heavily the 'hierarchyid' data type and this type is used internally in DB's stored procedures.
Seems like EF6 is not supporting 'hierarchyid' data type, so i used this fork that adds support for 'hierarchyid'.
While the retrieval from the DB is working great with the 'hierarchyid' type, my problem is with the Stored Procedures that need a 'hierarchyid' as a parameter.
The stored procedure looks like this:
CREATE PROCEDURE [dbo].[GetSomethingByNodeId]
(
#startingRoot HIERARCHYID
,#return HIERARCHYID OUTPUT
)
My client code for invoking this stored procedure looks like this:
var param1 = new SqlParameter("#startingRoot", new HierarchyId("/"));
var param2 = new SqlParameter{ ParameterName = "#return", Value = 0, Direction = ParameterDirection.Output };
var obj = context.Database.SqlQuery<HierarchyId>("GetSomethingByNodeId" #startingRoot, #return out", param1, param2).ToList();
But unfortunately calling this query throws an exception that says:
An unhandled exception of type 'System.ArgumentException' occurred in EntityFramework.SqlServer.dll
Additional information: No mapping exists from object type System.Data.Entity.Hierarchy.HierarchyId to a known managed provider native type.
Any ideas on how i can make this work?
Unfortunately, MetaType.GetMetaTypeFromValue does not allow to add types (all supported types are hardcoded).
I think you can accomplish your goal with nvarchar parameters and conversions.
In your C# code:
var param1 = new SqlParameter("#startingRoot", "/1/");
var param2 = new SqlParameter { ParameterName = "#return", Value = "", Size = 1000, Direction = ParameterDirection.Output };
var ids = context.Database.SqlQuery<HierarchyId>("GetSomethingByNodeId #startingRoot, #return out", param1, param2).ToList();
var returnedId = new HierarchyId(param2.Value.ToString());
In your procedure (I wrote some test code inside):
CREATE PROCEDURE [dbo].[GetSomethingByNodeId]
(
#startingRoot nvarchar(max), #return nvarchar(max) OUTPUT
)
as
declare #hid hierarchyid = hierarchyid::Parse('/1/')
select #return = #hid.ToString()
declare #root hierarchyid = hierarchyid::Parse(#startingRoot)
select #root as field
Also, you can try to use Microsoft.SqlServer.Types and SqlHierarchyId type like this:
var sqlHierarchyId = SqlHierarchyId.Parse("/");
var param1 = new SqlParameter("#startingRoot", sqlHierarchyId) { UdtTypeName = "HierarchyId" };
But, I think, this is wrong direction.
Oleg's answer is correct, hierarchyid is still not integrated to the EF very well, and you should operate with strings in .net. Here is one more approach which was used from the first days of HierarchyId datatype:
Stored Procedure:
CREATE PROCEDURE GetSomethingByNodeId
#startingRoot hierarchyid, -- you don't need to use nvarchar here. String which will come from the application will be converted to hierarchyId implicitly
#return nvarchar(500) OUTPUT
AS
BEGIN
SELECT #return = #startingRoot.GetAncestor(1).ToString();
END
In an application you are adding a partial class for your EF data context with the SP call using plain old ADO.NET. Probably you will write this other way or use Dapper instead, but the main idea here is passing parameter as string to SQL Server, and it will convert to the HierarchyId implicitly:
public partial class TestEntities
{
public string GetSomethingByNodeId(string startingRoot)
{
using (var connection = new SqlConnection(this.Database.Connection.ConnectionString))
{
var command = new SqlCommand("GetSomethingByNodeId", connection);
command.CommandType = CommandType.StoredProcedure;
command.Parameters.AddWithValue("#startingRoot", startingRoot);
var outParameter = new SqlParameter("#return", SqlDbType.NVarChar, 500);
outParameter.Direction = ParameterDirection.Output;
command.Parameters.Add(outParameter);
connection.Open();
command.ExecuteNonQuery();
return outParameter.Value.ToString();
}
}
}
Then call this method as any other stored procedure using your EF context:
using (var context = new TestEntities())
{
var s = context.GetSomethingByNodeId("/1/1.3/");
}
UPD: here is how the extension method for legacy HierarchyId procedure call will look like with Dapper (as for me it looks much better than plain ADO.NET):
public string GetSomethingByNodeId(string startingRoot)
{
using (var connection = new SqlConnection(this.Database.Connection.ConnectionString))
{
var parameters = new DynamicParameters();
parameters.Add("startingRoot", startingRoot);
parameters.Add("return", null, DbType.String, ParameterDirection.Output, 500);
connection.Open();
connection.Execute("GetSomethingByNodeId", parameters, commandType: CommandType.StoredProcedure);
return parameters.Get<string>("return");
}
}

Dapper calls sp_executesql when I have parameters, is there a way around that?

When I call
connection.Execute(sql);
Dapper executes and everything is fine. When I call
connection.Execute(sql, new { UserId = _userId });
it executes with sp_executesql.
The issue is when it uses sp_executesql it's in its own scope. If it creates a temporary table, it's not accessible to subsequent queries that use the same connection. I could get around it by using global temporary tables, but I don't want to risk having two processes interfere with each other.
Does anybody know a way around that?
Update: I have the same problem when I use SqlCommand objects without Dapper. I wrote a unit test that illustrates the problem I'm having. WorksWithParameters fails with System.Data.SqlClient.SqlException : Invalid object name '#TEMP_OBJECTLIST'.
[TestFixture]
public class DapperTest
{
private const string TestObjectType = "S";
private const string ConnectionString = "XXXXXXXXX";
private static void CreateTempTableWithoutParameters(SqlConnection connection)
{
const string sql = "SELECT TOP 10 * INTO #TEMP_OBJECTLIST FROM sys.objects WHERE TYPE = 'S'";
connection.Execute(sql);
}
private static void UseTempTableWithoutParameters(SqlConnection connection)
{
const int expectedCount = 10;
const string sql = "SELECT COUNT(*) FROM #TEMP_OBJECTLIST WHERE TYPE = 'S'";
var count = connection.Query<int>(sql).First();
Assert.AreEqual(expectedCount, count);
}
private static void CreateTempTableWithParameters(SqlConnection connection)
{
const string sql = "SELECT TOP 10 * INTO #TEMP_OBJECTLIST FROM sys.objects WHERE TYPE = #OBJECT_TYPE";
connection.Execute(sql, new {OBJECT_TYPE = TestObjectType});
}
private static void UseTempTableWithParameters(SqlConnection connection)
{
const int expectedCount = 10;
const string sql = "SELECT COUNT(*) FROM #TEMP_OBJECTLIST WHERE TYPE = #OBJECT_TYPE";
var param = new {OBJECT_TYPE = TestObjectType};
var count = connection.Query<int>(sql, param).First();
Assert.AreEqual(expectedCount, count);
}
[Test]
public void WorksWithParameters()
{
using (var connection = new SqlConnection(ConnectionString))
{
connection.Open();
CreateTempTableWithParameters(connection);
UseTempTableWithParameters(connection);
}
}
[Test]
public void WorksWithoutParameters()
{
using (var connection = new SqlConnection(ConnectionString))
{
connection.Open();
CreateTempTableWithoutParameters(connection);
UseTempTableWithoutParameters(connection);
}
}
}
One way around the temp table scope problem is to create the temp table with one dummy column in the outer scope, then use alter table statements to add all the desired columns and use it.
Additionally, How to share data between procedures by Erland Sommarskog may be useful to you or another person looking for different options for sharing data.
I ran into the same problem with Dapper, but it's not Dapper's fault. sp_executesql is called by ADO.NET and this switches the "scope" so temp tables become invisible.
As a workaround:
//no parameters, so it runs without sp_executesql
conn.Execute("CREATE TABLE #temp BLAHBLAH");
//do your thing
conn.Execute("INSERT INTO #temp BLAHBLAH", parameters);
//cleanup (no parameters again)
conn.Execute("DROP TABLE #temp");

SQL server refusing to cache plan for a fixed length parameterized IN clause

Using .NET 4.0, I have defined the following sqlcommand. When I execute the sqlcommand multiple times consecutively without making any changes, SQL Server refuses to cache the query plan.
string[] colors = new string[] { "red", "blue", "yellow", "green" };
string cmdText = "SELECT * FROM ColoredProducts WHERE Color IN ({0})";
string[] paramNames = tags.Select(
(s, i) => "#color" + i.ToString()
).ToArray();
string inClause = string.Join(",", paramNames);
using (SqlCommand cmd = new SqlCommand(string.Format(cmdText, inClause))) {
for(int i = 0; i < paramNames.Length; i++) {
cmd.Parameters.AddWithValue(paramNames[i], tags[i]);
}
//Execute query here
}
I know it's refusing the cache the plan because the following query was running at a fraction of the time after consecutive runs:
string[] colors = new string[] { "red", "blue", "yellow", "green" };
string cmdText = "SELECT * FROM ColoredProducts WHERE Color IN ({0})";
string inClause = string.Join(",", colors);
using (SqlCommand cmd = new SqlCommand(string.Format(cmdText, inClause))) {
//Execute query here
}
In my actual test case the param list is fixed at a size of exactly 2000. The scenario I am attempting to optimize is selecting a specific set of 2000 records from a very large table. I would like for the query to be as fast as possible so I really want it to cached.
Sleepy post Edit:
The question is, why wouldn't this plan get cached? And yes, I have confirmed that the query is not in the cache using sys.dm_exec_cached_plans and sys.dm_exec_sql_test.
Here is an idea using a table-valued parameter. Please let us know if this approach performs better than your huge string. There are other ideas too, but this is the closest to treating your set of colors as an array.
In SQL Server:
CREATE TYPE dbo.Colors AS TABLE
(
Color VARCHAR(32) -- be precise here! Match ColoredProducts.Color
PRIMARY KEY
);
GO
CREATE PROCEDURE dbo.MatchColors
#colors AS dbo.Colors READONLY
AS
BEGIN
SET NOCOUNT ON;
SELECT cp.* -- use actual column names please!
FROM dbo.ColoredProducts AS cp -- always use schema prefix
INNER JOIN #colors AS c
ON cp.Color = c.Color;
END
GO
Now in C#:
DataTable tvp = new DataTable();
tvp.Columns.Add(new DataColumn("Color"));
tvp.Rows.Add("red");
tvp.Rows.Add("blue");
tvp.Rows.Add("yellow");
tvp.Rows.Add("green");
// ...
using (connectionObject)
{
SqlCommand cmd = new SqlCommand("dbo.MatchColors", connectionObject);
cmd.CommandType = CommandType.StoredProcedure;
SqlParameter tvparam = cmd.Parameters.AddWithValue("#colors", tvp);
tvparam.SqlDbType = SqlDbType.Structured;
// execute query here
}
I can almost guarantee this will perform better than an IN list with a large number of parameters, regardless of the length of the actual string in your C# code.

Resources