Search code examples
sql-server-2008asp.net-mvc-4stored-procedurestable-valued-parameters

Nullable Table Valued Parameter in Stored Procedure


I have this procedure

CREATE PROCEDURE dbo.spProcedure1
    @intArray as dbo.intArray READONLY
AS
BEGIN
-- ...
END

which use user type as a parameter

CREATE TYPE dbo.IntArray AS TABLE (IntValue int NULL)

and I am calling the procedure from the C# ASP.NET MVC 4 project

    // creating empty SQL @IntArray parameter

        var emptyIntDataTable = new DataTable();
        emptyIntDataTable.Columns.Add("IntValue");

    // calling stored procedure

        return Database.SqlQuery<int>(
            @"spProcedure1 @p1",
            new SqlParameter("p1", (object)Utils.ToDataTable(m.IntArray) ?? emptyIntDataTable)
        ).ToList();

    // ToDataTable method which is returning null

        public static DataTable ToDataTable<T>(this IList<T> data)
                {
                    if (data == null)
                        return null;

                    ... // code omitted because it is not working yet                
}

the error which is throwed when calling stored procedure is

The table type parameter 'p1' must have a valid type name.

How to pass an empty table value?

Passing the list instead of datatable throw following error

var emptyIntDataTable = new List<int>;

No mapping exists from object type System.Collections.Generic.List`1[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]] to a known managed provider native type.


Solution

  • In your code:

    where it says:

    return Database.SqlQuery<int>(
        @"spProcedure1 @p1", new SqlParameter("p1", 
            (object)Utils.ToDataTable(m.IntArray) ?? emptyIntDataTable)
    ).ToList();
    

    Change it to read:

     return m.IntArray.Length > 0? 
            Database.SqlQuery<int>(@"spProcedure1 @p1",
                new SqlParameter("p1", 
                   (object)Utils.ToDataTable(m.IntArray))).ToList():
            Database.SqlQuery<int>(@"spProcedure1")).ToList();
    

    sample to show how to not pass table parameter

    CREATE TYPE dbo.KeyIds]
    AS TABLE(pkId int NOT NULL,
    PRIMARY KEY CLUSTERED (pkId ASC)
    WITH (IGNORE_DUP_KEY = OFF))
    Go
    -- ------------------------------
    Create procedure testProc 
        @aIds dbo.keyIds readonly
    as 
    Set NoCount On
        if exists (select * from @aIds) 
            Select * from @aIds
        else
            Select 'No Aids passed in'
    Go
    -- ------------------------------
    
    Exec dbo.testProc -- <--- Here I am NOT passing the @aids parameter
    

    But, even though I am NOT passing the @aids parameter it still works, and the subquery (select * from @aIds) still functions, and since it is an empty datatable the SP returns the empty message 'No Aids passed in'.

    On the other hand, if you pass the parameter

    Declare @MyIds dbo.keyIds
    Insert @MyIds Values(1)
    Insert @MyIds Values(2)
    Insert @MyIds Values(3)
    Insert @MyIds Values(4)
    Insert @MyIds Values(5)
    Exec dbo.testProc @MyIds -- <--- Here I AM passing the @aids parameter
    

    it outputs the contents of the datatable parameter

    C# code example...

     public DataTable GetAccountTransactions(IEnumerable<int> accountIds)
        {
            const string procName = "FetchAccountTransactionData";
    
            var acctIds = accountIds == null ? 
                  new List<int>() : accountIds.ToList();
            // -------------------------------------------------
            var parms = DbParamList.Make(); 
                // DbParamList is a List<IDbDataParameter>
            // See here, ONLY ADD PARAMETER if list is NOT empty!
            if (acctIds.Count > 0) 
                parms.AddSQLTableParm("aIds", acctIds);
    
            try
            { // following constructs command obkect and calls SP
                return Utilities.GetDataTable(schemaNm + "." + 
                                procName, parms, copConn);
            }
            catch (SqlException dbX)
            { 
               // Exception stuff
            }
        }
    
    
        public class DbParamSortedList : SortedList<string,IDbDataParameter> { }