ChatGPT解决这个技术问题 Extra ChatGPT

How to pass table value parameters to stored procedure from .net code

I have a SQL Server 2005 database. In a few procedures I have table parameters that I pass to a stored proc as an nvarchar (separated by commas) and internally divide into single values. I add it to the SQL command parameters list like this:

cmd.Parameters.Add("@Logins", SqlDbType.NVarchar).Value = "jim18,jenny1975,cosmo";

I have to migrate the database to SQL Server 2008. I know that there are table value parameters, and I know how to use them in stored procedures. But I don't know how to pass one to the parameters list in an SQL command.

Does anyone know correct syntax of the Parameters.Add procedure? Or is there another way to pass this parameter?

Check out this solution: Stored Procedure with Table-Valued Parameter in EF. code.msdn.microsoft.com/Stored-Procedure-with-6c194514
In a case like this one, I usually concatenate strings and split them on the server side or pass even an xml if I have multiple columns. Sql it's very fast when processing xml. You can try all the methods and check the processing time and after that choose the best method. An XML would look like .... The process on Sql Server is also simple. Using this method, you can always add a new attribute to if you need more information.
@NițuAlexandru, "Sql it's very fast when processing xml.". Not even close.

K
Kjartan

DataTable, DbDataReader, or IEnumerable<SqlDataRecord> objects can be used to populate a table-valued parameter per the MSDN article Table-Valued Parameters in SQL Server 2008 (ADO.NET).

The following example illustrates using either a DataTable or an IEnumerable<SqlDataRecord>:

SQL Code:

CREATE TABLE dbo.PageView
(
    PageViewID BIGINT NOT NULL CONSTRAINT pkPageView PRIMARY KEY CLUSTERED,
    PageViewCount BIGINT NOT NULL
);
CREATE TYPE dbo.PageViewTableType AS TABLE
(
    PageViewID BIGINT NOT NULL
);
CREATE PROCEDURE dbo.procMergePageView
    @Display dbo.PageViewTableType READONLY
AS
BEGIN
    MERGE INTO dbo.PageView AS T
    USING @Display AS S
    ON T.PageViewID = S.PageViewID
    WHEN MATCHED THEN UPDATE SET T.PageViewCount = T.PageViewCount + 1
    WHEN NOT MATCHED THEN INSERT VALUES(S.PageViewID, 1);
END

C# Code:

private static void ExecuteProcedure(bool useDataTable, 
                                     string connectionString, 
                                     IEnumerable<long> ids) 
{
    using (SqlConnection connection = new SqlConnection(connectionString)) 
    {
        connection.Open();
        using (SqlCommand command = connection.CreateCommand()) 
        {
            command.CommandText = "dbo.procMergePageView";
            command.CommandType = CommandType.StoredProcedure;

            SqlParameter parameter;
            if (useDataTable) {
                parameter = command.Parameters
                              .AddWithValue("@Display", CreateDataTable(ids));
            }
            else 
            {
                parameter = command.Parameters
                              .AddWithValue("@Display", CreateSqlDataRecords(ids));
            }
            parameter.SqlDbType = SqlDbType.Structured;
            parameter.TypeName = "dbo.PageViewTableType";

            command.ExecuteNonQuery();
        }
    }
}

private static DataTable CreateDataTable(IEnumerable<long> ids) 
{
    DataTable table = new DataTable();
    table.Columns.Add("ID", typeof(long));
    foreach (long id in ids) 
    {
        table.Rows.Add(id);
    }
    return table;
}

private static IEnumerable<SqlDataRecord> CreateSqlDataRecords(IEnumerable<long> ids) 
{
    SqlMetaData[] metaData = new SqlMetaData[1];
    metaData[0] = new SqlMetaData("ID", SqlDbType.BigInt);
    SqlDataRecord record = new SqlDataRecord(metaData);
    foreach (long id in ids) 
    {
        record.SetInt64(0, id);
        yield return record;
    }
}

+1 Excellent example. Takeaways are: send a DataTable as the parameter value, set SqlDbType to Structured and TypeName to the database UDT name.
If you are going to reuse an instance of a reference type in a loop (SqlDataRecord in your example), please please add a comment on why it is safe to do so in this particular instance.
This code is wrong: empty table valued parameters should have their value set to null. CreateSqlDataRecords will never return null if given an empty ids parameter.
@Crono: DataTable(or DataSet) only implement it because they have to suppiort drag&drop capabilities in Visual-Studio, so they implement IComponent which implements IDisposable. If you don't use the designer but create it manually there's no reason to dispose it (or to use the using-statement). So this is one of the exceptions of the golden rule "dispose everything that implements IDisposable".
Future Users, please note this: DataTable approach consumes the hell of a memory, whereas the IEnumerable one simply does not.
K
KyleMit

Further to Ryan's answer you will also need to set the DataColumn's Ordinal property if you are dealing with a table-valued parameter with multiple columns whose ordinals are not in alphabetical order.

As an example, if you have the following table value that is used as a parameter in SQL:

CREATE TYPE NodeFilter AS TABLE (
  ID int not null
  Code nvarchar(10) not null,
);

You would need to order your columns as such in C#:

table.Columns["ID"].SetOrdinal(0);
// this also bumps Code to ordinal of 1
// if you have more than 2 cols then you would need to set more ordinals

If you fail to do this you will get a parse error, failed to convert nvarchar to int.


M
Martea

Generic

   public static DataTable ToTableValuedParameter<T, TProperty>(this IEnumerable<T> list, Func<T, TProperty> selector)
    {
        var tbl = new DataTable();
        tbl.Columns.Add("Id", typeof(T));

        foreach (var item in list)
        {
            tbl.Rows.Add(selector.Invoke(item));

        }

        return tbl;

    }

Would you please let me know that what do I pass as parameter? Func selector? Can't it be simply tbl.Rows.Add(item) and no need of that parameter.
the selector.Invoke(item) selects the property on the item most cases its a int, but it also allows you to select a string property
can you please provide an example of how do I put selector over there?? I have a List to pass to stored proc...
guidList.ToTabledValuedParameter(x=>x), since x is the guid in your case, the return will be a DataTable with one column(id) with a list of guids,
S
Shahzad Qureshi

The cleanest way to work with it. Assuming your table is a list of integers called "dbo.tvp_Int" (Customize for your own table type)

Create this extension method...

public static void AddWithValue_Tvp_Int(this SqlParameterCollection paramCollection, string parameterName, List<int> data)
{
   if(paramCollection != null)
   {
       var p = paramCollection.Add(parameterName, SqlDbType.Structured);
       p.TypeName = "dbo.tvp_Int";
       DataTable _dt = new DataTable() {Columns = {"Value"}};
       data.ForEach(value => _dt.Rows.Add(value));
       p.Value = _dt;
   }
}

Now you can add a table valued parameter in one line anywhere simply by doing this:

cmd.Parameters.AddWithValueFor_Tvp_Int("@IDValues", listOfIds);

what if the paramCollection is NULL ? How to pass empty type ?
@Muflix Obscurely, extension methods actually work against null instances. So adding a simple if(paramCollection != null) check at the top of the method will be fine
Updated answer with initial -if- check
Maybe a bit pedantic, but I'd use IEnumerable instead of List in the signature, that way you can pass anything that is IEnumerable, not just lists, Since you're not using any function specific to List, I don't really see a reason not to us IEnumerable
Using List allows you to use the shorthand data.ForEach(), otherwise you'd have to actually write a foreach loop. Which could work also, but I like writing things as short as possible.
b
bside

Use this code to create suitable parameter from your type:

private SqlParameter GenerateTypedParameter(string name, object typedParameter)
{
    DataTable dt = new DataTable();

    var properties = typedParameter.GetType().GetProperties().ToList();
    properties.ForEach(p =>
    {
        dt.Columns.Add(p.Name, Nullable.GetUnderlyingType(p.PropertyType) ?? p.PropertyType);
    });
    var row = dt.NewRow();
    properties.ForEach(p => { row[p.Name] = (p.GetValue(typedParameter) ?? DBNull.Value); });
    dt.Rows.Add(row);

    return new SqlParameter
    {
        Direction = ParameterDirection.Input,
        ParameterName = name,
        Value = dt,
        SqlDbType = SqlDbType.Structured
    };
}