Обнуляемый табличный параметр в хранимой процедуре

У меня есть эта процедура

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

которые используют тип пользователя в качестве параметра

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

и я вызываю процедуру из проекта C # ASP.NET MVC 4

    // 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                
}

ошибка, возникающая при вызове хранимой процедуры,

Параметр типа таблицы «p1» должен иметь допустимое имя типа.

Как передать пустое табличное значение?

Передача списка вместо datatable вызывает следующую ошибку

var emptyIntDataTable = new List<int>;

Не существует сопоставления типа объекта System.Collections.Generic.List`1 [[System.Int32, mscorlib, Version = 4.0.0.0, Culture = нейтральный, PublicKeyToken = b77a5c561934e089]] с собственным типом известного управляемого поставщика.


person Muflix    schedule 31.08.2016    source источник
comment
пустой список? , который в коде создаст пустую таблицу данных, но в следующем методе, который создает список параметров для передачи в сохраненную процедуру, если таблица данных пуста, просто опустите этот параметр.   -  person Charles Bretana    schedule 31.08.2016
comment
Параметр Table Valued не может быть необязательным, поэтому я должен передать пустой DataTable, который, к сожалению, не работает. Не могли бы вы предоставить пример кода? Я попробую передать List ‹int› вместо DataTable и дам вам знать   -  person Muflix    schedule 31.08.2016
comment
Попробуйте, я знаю, что он говорит, что это не обязательно, но если вы вызываете Stored proc без него, он принимает его и работает нормально. В диспетчере SQL Enterprise он даже отображается красным цветом (что указывает на ошибку), но если вы нажмете «Выполнить», он работает.   -  person Charles Bretana    schedule 31.08.2016
comment
О, теперь я понимаю вашу точку зрения, но если хранимая процедура имеет параметр, указанный в первом параметре SqlQuery, пропуск параметра приведет к ошибке, что @ p1 не указан. Я также попробовал Список ‹int› и задаю вопрос об ошибке.   -  person Muflix    schedule 31.08.2016
comment
Нет, это не вызывает ошибки ... В вашем коде для создания SQLCommand просто не добавляйте этот параметр, если список пуст. Но с другой стороны, почему бы вам просто не закодировать SP для обработки пустой таблицы данных, если параметр отсутствует, SQL Server все равно создает для вас пустой список. (См. Мой пример - я его не передаю, но (Select * From @aIDs) все еще работает.   -  person Charles Bretana    schedule 31.08.2016
comment
В этой строке кода @"spProcedure1 @p1", указан параметр, поэтому я не могу пропустить этот параметр во втором параметре new SqlParameter( метода SqlQuery. Ответ на вопрос why don't you just code the SP to handle an empty datatable, if the parameter is missing заключается в том, что я уже делаю это с помощью оператора ??, но пустая таблица данных не работает.   -  person Muflix    schedule 31.08.2016
comment
Вы не знакомы с утверждением if? У C # есть такая возможность. Где бы ни была эта строка кода, поместите ДВЕ из них: одну, если в списке есть что-то (как есть), и одну БЕЗ параметра @ p1.   -  person Charles Bretana    schedule 31.08.2016
comment
Какое несовпадение нумерации параметров?   -  person Charles Bretana    schedule 31.08.2016


Ответы (2)


В вашем коде:

где сказано:

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

Измените его на следующее:

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

пример, чтобы показать, как не передавать параметр таблицы

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

Но даже несмотря на то, что я НЕ передаю параметр @aids, он по-прежнему работает, а подзапрос (select * from @aIds) по-прежнему функционирует, и, поскольку это пустая таблица данных, SP возвращает пустое сообщение «Никакие вспомогательные средства не переданы».

С другой стороны, если вы передадите параметр

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

он выводит содержимое параметра datatable

Пример кода C # ...

 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> { }
person Charles Bretana    schedule 31.08.2016
comment
Чарльз, спасибо, но я вызываю хранимую процедуру из C #, и проблема в том, что я не могу опустить этот параметр, но мне нужно передать неявное значение - person Muflix; 31.08.2016

Альтернативное решение

подготовить метод преобразования List<int> в dbo.IntArray тип

public static DataTable IntArrayToDataTable(IEnumerable<int> ids)
    {
        if (ids == null)
            return null;

        DataTable table = new DataTable();

        // datatable columns has to have same name as database type !
        table.Columns.Add("IntValue", typeof(int));
        foreach (int id in ids)
        {
            table.Rows.Add(id);
        }
        return table;
    }

запустить хранимую процедуру sql

var sqlParameters = new List<object>();

    var parameter1 = Utils.IntArrayToDataTable(m.IntArray);    
    if (parameter1 != null) 

        sqlParameters.Add(new SqlParameter("intArray", parameter1)
        // these variables are the key, without them it is not working
        { 
            SqlDbType = SqlDbType.Structured,
            TypeName = "dbo.IntArray"
        });
else // parameter cannot be omitted !! even if all parameters are named !! otherwise parameter mismatch happens (in case of multiple parameters)
 sqlParameters.Add(new SqlParameter("intArray", SqlDbType.Structured) { TypeName = "dbo.IntArray" });


var sqlQuery = "spProcedure1 @InArray";

return Database.SqlQuery<int>(sqlQuery, sqlParameters.ToArray()).ToList();
person Muflix    schedule 01.09.2016