46

I am trying to pass a table-value parameter to a stored procedure, but I keep getting an exception (see below).

SqlCommand c = new SqlCommand("getPermittedUsers", myConn) { CommandType = CommandType.StoredProcedure };

c.Parameters.AddWithValue("@intNotifyingUserId", notifyingUserId);
c.Parameters.AddWithValue("@tSelectedPdfIds", sharedPdfs).SqlDbType = SqlDbType.Structured;

SqlDataReader dr = c.ExecuteReader();

The type is defined on the server like this:

CREATE TYPE [dbo].[IdList] AS TABLE(
    [Id] [int] NOT NULL
)

I have tried passing sharedPdfs as a List<int>, and IQueryable<int>, but keep getting the following exception:

Object must implement IConvertible.

Anyone know what I am doing wrong? The documentation implies that I should be able to pass a list as a TVP but doesn't give any examples.

Thank you.

4
  • How is getPermittedUsers declared? Commented Aug 8, 2011 at 22:56
  • This is a long-shot, but have you tried passing an int[]? Commented Aug 8, 2011 at 23:00
  • Thanks, it worked with a DataTable. Annoying to have to convert my IQueryable into a DataTable, though. Commented Aug 8, 2011 at 23:30
  • 4
    You can also implement IEnumerable<SqlDataRecord>. lennilobel.wordpress.com/2009/07/29/… Commented Aug 9, 2011 at 2:01

3 Answers 3

57

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;
    }
}
Sign up to request clarification or add additional context in comments.

6 Comments

was trying to get tvp working from what i found on sqlperformance.com/2012/08/t-sql-queries/… , but kept getting dbnull values. Your example ended up working for me, might be the parameter.typename. Thanks!
NB: For those who don't wish to create a custom type for this, a workaround is to pass data as an xml type, then query against that.
This is not working in .Net CORE 3.1. Always gives an error of type "Failed to convert parameter value from a <CreateSqlDataRecords> to a IEnumerable`1."
I had some trouble with type conversion in .Net Core 3.1 as well. It worked when I used command.Parameters.Add("@Display",SqlDbType.Structured)) {TypeName="dbo.PageViewTableType", Value=CreateSqlDataRecords(ids)}
In the interests of a cleaner SQL database, shouldn't the dbo.PageViewTableType be dropped after using it? Gets ugly IMO when there are multiple Types in the database mixed with real data types like INT, CHAR etc
|
21

You can pass the parameter as a DataTable, IEnumerable<SqlDataRecord>, or DbDataReader.

4 Comments

Yes, those are the supported types for a Table-Value Parameter in SqlCommand. The full documentation is here: msdn.microsoft.com/en-us/library/bb675163.aspx . See 1/3 of the way down: "System.Data.SqlClient supports populating table-valued parameters from DataTable, DbDataReader or System.Collections.Generic.IEnumerable<SqlDataRecord> ([T:System.Collections.Generic.IEnumerable`1)] objects."
is this also possible in Oracle?
Table-Valued Parameters is a SQL Server feature. Oracle may or may not have its own similar feature, I do not know.
@Mr. No but check out UDTs and ntypes. A UDT is analogous to a class, an ntype to a collection/list. I've passed very complex objects between .net and the database using this mechanism. You need to be using ODP.net, and you need to be aware of which version also as support for UDTs was only added relatively recently.
5

Adding a new answer with updated links.

According to the documentation (learn.microsoft.com), you can use one of the following parameter types:

SqlClient supports populating table-valued parameters from DataTable, DbDataReader or IEnumerable \ SqlDataRecord objects. You must specify a type name for the table-valued parameter by using the TypeName property of a SqlParameter. The TypeName must match the name of a compatible type previously created on the server.

Not included in the docs but important for high-performance apps, a sample using IEnumerable<SqlDataRecord> (.NET Core 3.1, async):

  using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(30));
  using SqlConnection connection = this.GetConnection();
  await connection.OpenAsync(timeout.Token);

  using SqlCommand command = connection.CreateCommand();
  command.CommandType = CommandType.StoredProcedure;
  command.CommandText = "Mycommand";

  IEnumerable<SqlDataRecord> records = // ...

  SqlParameter parameter = command.Parameters.Add("@MyObjects", SqlDbType.Structured);
  parameter.TypeName = "MyCustomTableType";
  parameter.Value = records;

  await command.ExecuteNonQueryAsync(timeout.Token);

Example using a DataTable:

  // Create a DataTable with the modified rows.  
  DataTable addedCategories = CategoriesDataTable.GetChanges(DataRowState.Added);  

  // Configure the SqlCommand and SqlParameter.  
  SqlCommand insertCommand = new SqlCommand("usp_InsertCategories", connection);  
  insertCommand.CommandType = CommandType.StoredProcedure;  
  SqlParameter tvpParam = insertCommand.Parameters.AddWithValue("@tvpNewCategories", addedCategories);  
  tvpParam.SqlDbType = SqlDbType.Structured;  

  // Execute the command.  
  insertCommand.ExecuteNonQuery(); 

Example using DbDataReader:

 // Assumes connection is an open SqlConnection.  
 // Retrieve data from Oracle.  
 OracleCommand selectCommand = new OracleCommand(  
     "Select CategoryID, CategoryName FROM Categories;",  
     oracleConnection);  
 OracleDataReader oracleReader = selectCommand.ExecuteReader(  
     CommandBehavior.CloseConnection);  

  // Configure the SqlCommand and table-valued parameter.  
  SqlCommand insertCommand = new SqlCommand(  
      "usp_InsertCategories", connection);  
  insertCommand.CommandType = CommandType.StoredProcedure;  
  SqlParameter tvpParam =  
      insertCommand.Parameters.AddWithValue(  
      "@tvpNewCategories", oracleReader);  
  tvpParam.SqlDbType = SqlDbType.Structured;  

  // Execute the command.  
  insertCommand.ExecuteNonQuery(); 

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.