Pass list of values in sqlparameter, structured perhaps [duplicate] - c#

This question already has answers here:
How to pass an array into a SQL Server stored procedure
(13 answers)
Closed 3 years ago.
Is it possible to create something like this in C# code using SqlDbCommand and SqlParameter?
DECLARE #Users TABLE (ID INT)
INSERT INTO #Users
VALUES (10),(20),(30)
SELECT COUNT(*) FROM #Users
I tried something like this:
1) DataTable creator:
private static DataTable CreateDataTable(IEnumerable<int> ids)
{
DataTable table = new DataTable();
table.Columns.Add("ID", typeof(int));
foreach (int id in ids)
{
table.Rows.Add(id);
}
return table;
2) Add SqlParameter:
sqlParameters.Add(new SqlParameter()
{
ParameterName = $"#paramname",
SqlDbType = SqlDbType.Structured,
Value = table
});
3) Execute command (command is SELECT COUNT(*) FROM #Users), parameters is list of parameters from step 2:
using (SqlCommand cmd = new SqlCommand(command, connection))
{
if (parameters != null)
cmd.Parameters.AddRange(parameters.ToArray());
SqlDataReader reader = cmd.ExecuteReader();
What I get is:
The table type parameter '#Users' must have a valid type name.
And I don't really have a real table so no type available, just want it to be:
DECLARE #Users TABLE (ID INT)
Is that doable? What I want to achieve is just pass list of values, in this case list of ints, obviously.
IN REGARD TO MARKED AS DUPLICATE:
Provided link doesn't solve the problem since it's not lack of typename problem but rather lack of typename to use. The problem is that I can't create any table and can't use any existing one to pass TypeName in SqlParameter.
ONE WORKING SOLUTION:
CREATE TYPE [dbo].[IntList] AS TABLE(
[Value] [int] NOT NULL
)
and then SqlParameter:
sqlParameters.Add(new SqlParameter()
{
ParameterName = $"#paramname",
SqlDbType = SqlDbType.Structured,
Value = table,
TypeName = "dbo.IntList"
});
Nevertheless, another step would be to use built-in type like GarethD suggested. I'm not sure if they are available in SQL Server 2016.

You need to add TypeName in sqlParameter , the same name with you created your table type in DB.
sqlParameters.Add(new SqlParameter()
{
ParameterName = $"#paramname",
SqlDbType = SqlDbType.Structured,
Value = table,
TypeName = "dbo.MyType";
});
If you do not have table type in database then first you need to create it in SQL
CREATE TYPE [dbo].[IntegerList] AS TABLE(
[Data] [int] NOT NULL,
)
GO
And then give that name in your code. That will work and you do need to create table in DB for that.

You need to use table type parameter. first you need to create table type in SQL. then pass parameter from C#
Take Datatable as SP Pararmeter
#dt customdatatable READONLY
Write Following C# Code
DataTable dt = new DataTable();
dt.Columns.Add("customcolumnname");
DataRow dr = dt.NewRow();
dr["customcolumnname"] = "columnvalue";
dt.Rows.Add(dr);
SqlParameter[] parCollection = new SqlParameter[1];
parCollection[0] = new SqlParameter("#yourdt", dt);

Related

c# Oracle Table Output Parameter with returning statement

It looks like it's possible to have an Oracle command in C# which has an output parameter, however if it is I'm not sure how to wire it up.
The command:
declare
type new_rows is table of Table1%rowtype;
newRows new_rows;
type newKeys_rec is record (col1 number, col2 number);
type newKeys_type is table of newKeys_rec;
newKeys newKeys_type;
begin
select *
bulk collect into newRows
from Table2;
forall idx in 1..newRows.count()
insert into Table1
values newRows(idx)
returning Table1.col1, Table1.col2 bulk collect into newKeys;
end;
The command parameter in sql:
Parameters.Add(new OracleParameter
{
ParameterName = "newKeys",
ObjectTypeName = "newKeys_type",
OracleDbType = OracleDbType.Object,
Direction = ParameterDirection.Output
});
The error:
OCI-22303: type ""."NEWKEYS_TYPE" not found
UPDATE: Following upon the answers below:
1) Declare the type on the schema:
Create type Schema.newKeys_object as object (col1 number, Col2 number)
Create type Schema.newKeys_type as table of Schema.type1_object
2) In the OracleParameter:
Parameters.Add(new OracleParameter
{
ParameterName = "newKeys",
ObjectTypeName = "newKeys_type",
OracleDbType = OracleDbType.Table,
Direction = ParameterDirection.ReturnValue
});
In order for the PL/SQL types to be accessible from C# you need to define them as database types using the CREATE TYPE statement. See this Web page for more details on that DDL statement. Note also that a database type belongs to a schema and has access permissions just like a database table has, so when accessing the database type from C# code, you may need to prepend the schema name to the type name, as in...
SCOTT.NEWKEYS_TYPE

T-SQL table type, specified type not registered

I have a query written as a string in C# that I'm attempting to call on a T-sql database (not a stored procedure). One of the parameters I have to pass in is a list of GUIDs. I'm attempting to use a table-valued parameter for this. Given that part of the query requires dynamic table names a stored procedure is considered somewhat clunky for debugging. The final part of the query joins the ID on the table to the list of Guids I'm passing in.
Adding the parameter to my command in
var ListOfIDs = new DataTable();
ListOfIDs.Columns.Add("ID", typeof (Guid));
selectedIDs.ForEach(x=> ListOfIDs.Rows.Add(x));
cmd.Parameters.Add(new SqlParameter("#ListOfIDs", SqlDbType.Udt)
{
Value = ListOfIDs, UdtTypeName = "ListOfGuids"
});
My table
CREATE TYPE [dbo].[ListOfGuids] AS TABLE(
[ID] [uniqueidentifier] NULL
)
When I run the line:
var reader = cmd.ExecuteReader();
I get the error:
Specified type is not registered on the target server.System.Data.DataTable, System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089.
I have been unable to locate what I need to do to get this working.
You should be using SqlDbType.Structured. .Udt is not a table-valued parameter or table type, that's a CLR User-Defined Type IIRC. A TVP / table-valued parameter is an alias type not a user-defined type. You also don't need to tell it the .UdtTypeName because, again, this has nothing to do with TVPs, and your code shouldn't be mentioning Udt in any shape or form.
If your procedure looks like this:
CREATE PROCEDURE dbo.MyProcedure
#ListOfGUIDs dbo.ListOfGuids READONLY
AS
BEGIN
...
END
Then your C# code can be:
SqlCommand cmd = new SqlCommand("dbo.MyProcedure", conn);
cmd.CommandType = CommandType.StoredProcedure;
SqlParameter tvparam = cmd.Parameters.AddWithValue("#ListOfGUIDs", ListOfIDs);
tvparam.SqlDbType = SqlDbType.Structured;
Or, if you just have a statement, like SELECT * FROM #ListOfGUIDs then:
SqlCommand cmd = new SqlCommand("SELECT * FROM #ListOfGUIDs", conn);
cmd.CommandType = CommandType.Text;
SqlParameter tvparam = cmd.Parameters.AddWithValue("#ListOfGUIDs", ListOfIDs);
tvparam.SqlDbType = SqlDbType.Structured;

how to pass DataTable with less columns to stored procedure when user defined table type in SQL server has default values of all columns set to null

I have a stored proc which accepts user defined table type and default values for all the columns in the user defined data type is set to null.
Now i am passing a dataTable with less columns to stored procedure from c# code expecting that the values for remaining columns will be set to null.
But i am getting this error:
Trying to pass a table-valued parameter with 21 column(s) where the corresponding user-defined table type requires 77 column(s).
This is the code
SqlConnection conn = new SqlConnection("server=****; database=***;integrated security=SSPI");
DataSet dataset=new DataSet();
conn.Open();
SqlCommand cmd = new SqlCommand("Insert");
cmd.CommandType = CommandType.StoredProcedure;
cmd.Connection = conn;
SqlParameter para = new SqlParameter();
para.ParameterName = "#TableVar";
para.SqlDbType = SqlDbType.Structured;
//SqlParameter para=cmd.Parameters.AddWithValue("#TableVar",table);
para.Value = table;
cmd.Parameters.Add(para);
cmd.ExecuteNonQuery();
You can use a SqlDataAdapter to create a DataTable matching the table type schema:
DataTable table = new DataTable();
// Declare a variable of the desired table type to produce a result set with it's schema.
SqlDataAdapter adapter = new SqlDataAdapter("DECLARE #tableType dbo.UserDefindTableType
SELECT * FROM #tableType", ConnectionString);
// Sets the DataTable schema to match dbo.UserDefindTableType .
adapter.FillSchema(table, SchemaType.Source);
You can then create DataRows with the all the default column values and just set the columns you know about:
DataRow row = table.NewRow();
// Set know columns...
row["ColumnName"] = new object();
// or check column exists, is expected type etc first
if (table.Columns.Contains("ColumnName")
&& table.Columns["ColumnName"].DataType == typeof(string)) {
row["ColumnName"] = "String";
}
table.Rows.Add(row);
I'm having the same issue and the solution I'm working on is to run an extra SQL query to get the column definition and then fill up the DataTable with the missing columns, here is the SQL statement for a column definition on your table type:
select c.name, t.name as type, c.max_length as length from sys.table_types tt
inner join sys.columns c on c.object_id = tt.type_table_object_id
inner join sys.types t on t.system_type_id = c.system_type_id
where tt.name = #tabletypename
order by c.column_id
The C# code will be a bit more messy as you have to parse the return type (ie VARCHAR, INT, etc) into a SqlDbType enum if you want the solution to work for all table valued parameter defintions..
I would have thought stuff like this could have been better solved by Microsoft inside the SQL Server engine as its the next best way to import CSV files if you do not have write access to the local filesystem, but I want a single UDTT to cater for all CSV files not having to create a new table type every time I deal with a new file format. Anyways.. rant over.

Pass RowID from application to a SQL Server stored procedure (or alternative)

I have a method in my application which is passing list<listitem> into a stored procedure. I have created a table type data type to pass list<listitem>. Now I need to loop through in stored procedure to insert into another table. For that purpose I created row id column in the table type which is generated automatically.
Since the table type has 2 columns, it expects 2 parameters to pass from outside but I am generating it by identity column. Is there any way to avoid so that I don't pass value form outside?
public void test(List<string> listItem) {
var table = new DataTable();
table.Columns.Add("col1", typeof(string));
foreach (string col1 in listItem) { table.Rows.Add(col1); }
SqlCommand cmd1 = new SqlCommand("TableTypeUpdateAnswers", conn);
cmd1.CommandType = CommandType.StoredProcedure;
SqlParameter sqlParam = cmd1.Parameters.AddWithValue("#tvpUpdateAnswers",table);
sqlParam.SqlDbType = SqlDbType.Structured ;
sqlParam.TypeName = "dbo.AnswerTableType";
conn.Open();
try
{ }
catch (Exception e)
{ }
}
Here is the SQL to create table type :
CREATE TYPE [dbo].[AnswerTableType] AS TABLE(
RowID int not null primary key identity(1,1),
[col1] [nvarchar](50) NULL
)
And here is the stored procedure:
ALTER PROCEDURE [dbo].[TestTableType]
#TVPUPDATEANSWERS DBO.ANSWERTABLETYPE READONLY
AS
DECLARE
#CURRENTROW INT,
#VANSID int ,
#ROWSTOPROCESS INT
BEGIN
SET #CURRENTROW=0
SELECT L.col1 FROM #TVPUPDATEANSWERS L;
SET #ROWSTOPROCESS=##ROWCOUNT
WHILE #CURRENTROW<#ROWSTOPROCESS
BEGIN
SET #CURRENTROW=#CURRENTROW+1
(SELECT #VANSID = col1
FROM #TVPUPDATEANSWERS
WHERE ROWID=#CURRENTROW);
//do something with table type datatype
INSERT INTO DBO.table1(col3,col4)
VALUES(#VANSID ,#col4);
END
END
Seems like you may benefit from using this kind of approach - check it out and let me know... http://ilovedevelopment.blogspot.co.uk/2012/01/manage-multiple-updates-to-data-using-c.html

How to pass sqlparameter to IN()? [duplicate]

This question already has answers here:
Pass Array Parameter in SqlCommand
(11 answers)
Closed 6 years ago.
For some reason the Sqlparameter for my IN() clause is not working. The code compiles fine, and the query works if I substitute the parameter with the actual values
StringBuilder sb = new StringBuilder();
foreach (User user in UserList)
{
sb.Append(user.UserId + ",");
}
string userIds = sb.ToString();
userIds = userIds.TrimEnd(new char[] { ',' });
SELECT userId, username
FROM Users
WHERE userId IN (#UserIds)
You have to create one parameter for each value that you want in the IN clause.
The SQL needs to look like this:
SELECT userId, username
FROM Users
WHERE userId IN (#UserId1, #UserId2, #UserId3, ...)
So you need to create the parameters and the IN clause in the foreach loop.
Something like this (out of my head, untested):
StringBuilder sb = new StringBuilder();
int i = 1;
foreach (User user in UserList)
{
// IN clause
sb.Append("#UserId" + i.ToString() + ",");
// parameter
YourCommand.Parameters.AddWithValue("#UserId" + i.ToString(), user.UserId);
i++;
}
Possible "cleaner" version:
StringBuilder B = new StringBuilder();
for (int i = 0; i < UserList.Count; i++)
YourCommand.Parameters.AddWithValue($"#UserId{i}", UserList[i].UserId);
B.Append(String.Join(",", YourCommand.Parameters.Select(x => x.Name)));
If you are using SQL 2008, you can create a stored procedure which accepts a Table Valued Parameter (TVP) and use ADO.net to execute the stored procedure and pass a datatable to it:
First, you need to create the Type in SQL server:
CREATE TYPE [dbo].[udt_UserId] AS TABLE(
[UserId] [int] NULL
)
Then, you need to write a stored procedure which accepts this type as a parameter:
CREATE PROCEDURE [dbo].[usp_DoSomethingWithTableTypedParameter]
(
#UserIdList udt_UserId READONLY
)
AS
BEGIN
SELECT userId, username
FROM Users
WHERE userId IN (SELECT UserId FROM #UserIDList)
END
Now from .net, you cannot use LINQ since it does not support Table Valued Parameters yet; so you have to write a function which does plain old ADO.net, takes a DataTable, and passes it to the stored procedure: I've written a generic function I use which can do this for any stored procedure as long as it takes just the one table-typed parameter, regardless of what it is;
public static int ExecStoredProcWithTVP(DbConnection connection, string storedProcedureName, string tableName, string tableTypeName, DataTable dt)
{
using (SqlConnection conn = new SqlConnection(connection.ConnectionString))
{
SqlCommand cmd = new SqlCommand(storedProcedureName, conn);
cmd.CommandType = CommandType.StoredProcedure;
SqlParameter p = cmd.Parameters.AddWithValue(tableName, dt);
p.SqlDbType = SqlDbType.Structured;
p.TypeName = tableTypeName;
conn.Open();
int rowsAffected = cmd.ExecuteNonQuery(); // or could execute reader and pass a Func<T> to perform action on the datareader;
conn.Close();
return rowsAffected;
}
}
Then you can write DAL functions which use this utility function with actual names of stored procedures; to build on the example in your question, here is what the code would look like:
public int usp_DoSomethingWithTableTypedParameter(List<UserID> userIdList)
{
DataTable dt = new DataTable();
dt.Columns.Add("UserId", typeof(int));
foreach (var userId in updateList)
{
dt.Rows.Add(new object[] { userId });
}
int rowsAffected = ExecStoredProcWithTVP(Connection, "usp_DoSomethingWithTableTypedParameter", "#UserIdList", "udt_UserId", dt);
return rowsAffected;
}
Note the "connection" parameter above - I actually use this type of function in a partial DataContext class to extend LINQ DataContext with my TVP functionality, and still use the (using var context = new MyDataContext()) syntax with these methods.
This will only work if you are using SQL Server 2008 - hopefully you are and if not, this could be a great reason to upgrade! Of course in most cases and large production environments this is not that easy, but FWIW I think this is the best way of doing this if you have the technology available.
SQL Server sees your IN clause as:
IN ('a,b,c')
What it needs to look like is:
IN ('a','b','c')
There is a better way to do what you're trying to do.
If the user id's are in the DB, then the IN clause should be changed to a subquery, like so:
IN (SELECT UserID FROM someTable WHERE someConditions)
This is a hack -- it doesn't work well with indexes, and you have to be careful it works right with your data, but I've used it successfully in the past:
#UserIDs LIKE '%,' + UserID + ',%' -- also requires #UserID to begin and end with a comma

Categories