O procedimento espera um parâmetro que não foi fornecido

106

Estou recebendo o erro ao acessar um procedimento armazenado no SQL Server

Server Error in '/' Application.
Procedure or function 'ColumnSeek' expects parameter '@template', which was not supplied. 

Isso está acontecendo quando eu chamo uma Stored Procedure com um parâmetro por meio da conexão de dados .net para sql (System.data.SqlClient), embora eu esteja fornecendo o parâmetro. Aqui está meu código.

SqlConnection sqlConn = new SqlConnection(connPath);
sqlConn.Open();

//METADATA RETRIEVAL
string sqlCommString = "QCApp.dbo.ColumnSeek";
SqlCommand metaDataComm = new SqlCommand(sqlCommString, sqlConn);
metaDataComm.CommandType = CommandType.StoredProcedure;
SqlParameter sp = metaDataComm.Parameters.Add("@template",SqlDbType.VarChar,50);
sp.Value = Template;

SqlDataReader metadr = metaDataComm.ExecuteReader();

E meu procedimento armazenado é:

   USE [QCApp]
   GO
   SET ANSI_NULLS ON
   GO
   SET QUOTED_IDENTIFIER ON
   GO

   ALTER PROCEDURE [dbo].[ColumnSeek] 
       @template varchar(50)
   AS
   EXEC('SELECT Column_Name, Data_Type 
   FROM [QCApp].[INFORMATION_SCHEMA].[COLUMNS] 
   WHERE TABLE_NAME = ' + @template);

Estou tentando descobrir o que estou fazendo de errado aqui.

Edit: Acontece que o template era nulo porque eu estava obtendo seu valor de um parâmetro passado pelo URL e eu estraguei a passagem do parâmetro url (eu estava usando @para e em vez de &)

Tony Peterson
fonte
Questão muito antiga, mas encontrei o mesmo problema e, no meu caso, não vi que adicionei um espaço extra em um dos parâmetros @. Uma hora de depuração.
Léon Pelletier de
Consulte stackoverflow.com/a/26374810/1860652 para executar procedimentos armazenados e obter este erro
AlexFoxGill
Isso acabou na primeira página de hoje por qualquer motivo. Mas parece que isso é vulnerável à injeção de SQL se o valor "template" vier da URL do cliente! No mínimo, eu QUOTENAME(@template)
sugeriria o

Respostas:

85

Gostaria de verificar o código do meu aplicativo e ver qual valor você está definindo como @template. Suspeito que seja nulo e aí está o problema.

HLGEM
fonte
Sim, o modelo era nulo, esqueci de defini-lo antes.
Tony Peterson
35
Posso apenas acrescentar que DbNull é o ÚNICO "recurso" mais inútil do C #
thaBadDawg
295

Além das outras respostas aqui, se você se esqueceu de colocar:

cmd.CommandType = CommandType.StoredProcedure;

Em seguida, você também receberá esse erro.

Brian
fonte
Se você estiver depurando-o no Visual Studio: Na guia de dados do relatório [ao lado das guias de layout e Visualização], ao lado do nome do conjunto de dados selecionado, há outro controle suspenso que permite alterar o CommandType. Aproveitar!
SarjanWebDev
2
sim, o SqlException é estranho - ele diz a você que o conhece como um procedimento, mas então você tem que definir sua propriedade CommandType para dizer que é um procedimento!
Tahir Hassan
@Tahir, acho que é mais porque o erro está usando "procedimento" como um termo genérico (como sugerido pela adição de "ou função"), em vez de implicar que está ciente de que a intenção é um procedimento armazenado de banco de dados SQL.
Brian,
3
essa é a solução para 99% das pessoas que vêm aqui, imagino
Jonesopolis
Droga, por que a solução foi tão fácil. Obrigado. Eu sabia que o parâmetro existia e não era nulo e isso foi o suficiente.
BornToDoStuff
27

Esse problema é geralmente causado pela definição de um valor de parâmetro como nulo, conforme HLGEM mencionado acima. Pensei em elaborar algumas soluções para este problema que achei úteis para o benefício de pessoas novas neste problema.

A solução que eu prefiro é padronizar os parâmetros do procedimento armazenado para NULL (ou qualquer valor que você quiser), o que foi mencionado por sangram acima, mas pode ser perdido porque a resposta é muito prolixa. Algo na linha de:

CREATE PROCEDURE GetEmployeeDetails
    @DateOfBirth    DATETIME = NULL,
    @Surname        VARCHAR(20),
    @GenderCode     INT = NULL,
AS

Isso significa que se o parâmetro acabar sendo definido no código como nulo em algumas condições, o .NET não definirá o parâmetro e o procedimento armazenado usará o valor padrão definido. Outra solução, se você realmente deseja resolver o problema no código, seria usar um método de extensão que lida com o problema para você, algo como:

public static SqlParameter AddParameter<T>(this SqlParameterCollection parameters, string parameterName, T value) where T : class
{
    return value == null ? parameters.AddWithValue(parameterName, DBNull.Value) : parameters.AddWithValue(parameterName, value);
}

Matt Hamilton tem uma boa postagem aqui que lista mais alguns métodos de extensão excelentes para lidar com essa área.

Xcalibur
fonte
12

Tive um problema em que recebia o erro quando fornecia 0 para um parâmetro inteiro. E descobri que:

cmd.Parameters.AddWithValue("@Status", 0);

funciona, mas isso não:

cmd.Parameters.Add(new SqlParameter("@Status", 0));
Anders Rune Jensen
fonte
6
A razão pela qual o segundo não funciona é porque o compilador pensa que você está chamando a sobrecarga (string, SqlDbType) do construtor SqlParameter. Veja as observações aqui .
Keith de
1
Se você quiser usar a Addsintaxe ou se estiver usando um inicializador de objeto para o seu comando, poderá usar um parâmetro nomeado:cmd.Parameters.Add(new SqlParameter("@Status", value: 0));
user888734
7

No meu caso, tive que passar DBNULL.Value(usando a condição if else) do código para o parâmetro de procedimentos armazenados que não estão definidos, nullmas o valor está null.

Rafoo
fonte
5

Encontro um problema semelhante ao chamar um procedimento armazenado

CREATE PROCEDURE UserPreference_Search
    @UserPreferencesId int,
    @SpecialOfferMails char(1),
    @NewsLetters char(1),
    @UserLoginId int,
    @Currency varchar(50)
AS
DECLARE @QueryString nvarchar(4000)

SET @QueryString = 'SELECT UserPreferencesId,SpecialOfferMails,NewsLetters,UserLoginId,Currency FROM UserPreference'
IF(@UserPreferencesId IS NOT NULL)
BEGIN
SET @QueryString = @QueryString + ' WHERE UserPreferencesId = @DummyUserPreferencesId';
END

IF(@SpecialOfferMails IS NOT NULL)
BEGIN
SET @QueryString = @QueryString + ' WHERE SpecialOfferMails = @DummySpecialOfferMails';
END

IF(@NewsLetters IS NOT NULL)
BEGIN
SET @QueryString = @QueryString + ' WHERE NewsLetters = @DummyNewsLetters';
END

IF(@UserLoginId IS NOT NULL)
BEGIN
SET @QueryString = @QueryString + ' WHERE UserLoginId = @DummyUserLoginId';
END

IF(@Currency IS NOT NULL)
BEGIN
SET @QueryString = @QueryString + ' WHERE Currency = @DummyCurrency';
END

EXECUTE SP_EXECUTESQL @QueryString
                     ,N'@DummyUserPreferencesId int, @DummySpecialOfferMails char(1), @DummyNewsLetters char(1), @DummyUserLoginId int, @DummyCurrency varchar(50)'
                     ,@DummyUserPreferencesId=@UserPreferencesId
                     ,@DummySpecialOfferMails=@SpecialOfferMails
                     ,@DummyNewsLetters=@NewsLetters
                     ,@DummyUserLoginId=@UserLoginId
                     ,@DummyCurrency=@Currency;

Que construindo dinamicamente a consulta para pesquisa que eu estava chamando acima de uma por:

public DataSet Search(int? AccessRightId, int? RoleId, int? ModuleId, char? CanAdd, char? CanEdit, char? CanDelete, DateTime? CreatedDatetime, DateTime? LastAccessDatetime, char? Deleted)
    {
        dbManager.ConnectionString = ConfigurationManager.ConnectionStrings["MSSQL"].ToString();
        DataSet ds = new DataSet();
        try
        {
            dbManager.Open();
            dbManager.CreateParameters(9);
            dbManager.AddParameters(0, "@AccessRightId", AccessRightId, ParameterDirection.Input);
            dbManager.AddParameters(1, "@RoleId", RoleId, ParameterDirection.Input);
            dbManager.AddParameters(2, "@ModuleId", ModuleId, ParameterDirection.Input);
            dbManager.AddParameters(3, "@CanAdd", CanAdd, ParameterDirection.Input);
            dbManager.AddParameters(4, "@CanEdit", CanEdit, ParameterDirection.Input);
            dbManager.AddParameters(5, "@CanDelete", CanDelete, ParameterDirection.Input);
            dbManager.AddParameters(6, "@CreatedDatetime", CreatedDatetime, ParameterDirection.Input);
            dbManager.AddParameters(7, "@LastAccessDatetime", LastAccessDatetime, ParameterDirection.Input);
            dbManager.AddParameters(8, "@Deleted", Deleted, ParameterDirection.Input);
            ds = dbManager.ExecuteDataSet(CommandType.StoredProcedure, "AccessRight_Search");
            return ds;
        }
        catch (Exception ex)
        {
        }
        finally
        {
            dbManager.Dispose();
        }
        return ds;
    }

Então, depois de muito coçar a cabeça, modifiquei o procedimento armazenado para:

ALTER PROCEDURE [dbo].[AccessRight_Search]
    @AccessRightId int=null,
    @RoleId int=null,
    @ModuleId int=null,
    @CanAdd char(1)=null,
    @CanEdit char(1)=null,
    @CanDelete char(1)=null,
    @CreatedDatetime datetime=null,
    @LastAccessDatetime datetime=null,
    @Deleted char(1)=null
AS
DECLARE @QueryString nvarchar(4000)
DECLARE @HasWhere bit
SET @HasWhere=0

SET @QueryString = 'SELECT a.AccessRightId, a.RoleId,a.ModuleId, a.CanAdd, a.CanEdit, a.CanDelete, a.CreatedDatetime, a.LastAccessDatetime, a.Deleted, b.RoleName, c.ModuleName FROM AccessRight a, Role b, Module c WHERE a.RoleId = b.RoleId AND a.ModuleId = c.ModuleId'

SET @HasWhere=1;

IF(@AccessRightId IS NOT NULL)
    BEGIN
        IF(@HasWhere=0) 
            BEGIN
                SET @QueryString = @QueryString + ' WHERE a.AccessRightId = @DummyAccessRightId';
                SET @HasWhere=1;
            END
        ELSE                SET @QueryString = @QueryString + ' AND a.AccessRightId = @DummyAccessRightId';
    END

IF(@RoleId IS NOT NULL)
    BEGIN
        IF(@HasWhere=0)
            BEGIN   
                SET @QueryString = @QueryString + ' WHERE a.RoleId = @DummyRoleId';
                SET @HasWhere=1;
            END
        ELSE            SET @QueryString = @QueryString + ' AND a.RoleId = @DummyRoleId';
    END

IF(@ModuleId IS NOT NULL)
BEGIN
    IF(@HasWhere=0) 
            BEGIN   
                SET @QueryString = @QueryString + ' WHERE a.ModuleId = @DummyModuleId';
                SET @HasWhere=1;
            END
    ELSE SET @QueryString = @QueryString + ' AND a.ModuleId = @DummyModuleId';
END

IF(@CanAdd IS NOT NULL)
BEGIN
    IF(@HasWhere=0) 
            BEGIN       
                SET @QueryString = @QueryString + ' WHERE a.CanAdd = @DummyCanAdd';
                SET @HasWhere=1;
            END
    ELSE SET @QueryString = @QueryString + ' AND a.CanAdd = @DummyCanAdd';
END

IF(@CanEdit IS NOT NULL)
BEGIN
    IF(@HasWhere=0) 
        BEGIN
            SET @QueryString = @QueryString + ' WHERE a.CanEdit = @DummyCanEdit';
            SET @HasWhere=1;
        END
    ELSE SET @QueryString = @QueryString + ' AND a.CanEdit = @DummyCanEdit';
END

IF(@CanDelete IS NOT NULL)
BEGIN
    IF(@HasWhere=0) 
        BEGIN
            SET @QueryString = @QueryString + ' WHERE a.CanDelete = @DummyCanDelete';
            SET @HasWhere=1;
        END
    ELSE SET @QueryString = @QueryString + ' AND a.CanDelete = @DummyCanDelete';
END

IF(@CreatedDatetime IS NOT NULL)
BEGIN
    IF(@HasWhere=0) 
    BEGIN
        SET @QueryString = @QueryString + ' WHERE a.CreatedDatetime = @DummyCreatedDatetime';
        SET @HasWhere=1;
    END
    ELSE SET @QueryString = @QueryString + ' AND a.CreatedDatetime = @DummyCreatedDatetime';
END

IF(@LastAccessDatetime IS NOT NULL)
BEGIN
    IF(@HasWhere=0) 
        BEGIN
            SET @QueryString = @QueryString + ' WHERE a.LastAccessDatetime = @DummyLastAccessDatetime';
            SET @HasWhere=1;
        END
    ELSE SET @QueryString = @QueryString + ' AND a.LastAccessDatetime = @DummyLastAccessDatetime';
END

IF(@Deleted IS NOT NULL)
BEGIN
  IF(@HasWhere=0)   
    BEGIN
        SET @QueryString = @QueryString + ' WHERE a.Deleted = @DummyDeleted';
        SET @HasWhere=1;
    END
  ELSE SET @QueryString = @QueryString + ' AND a.Deleted = @DummyDeleted';
END

PRINT @QueryString

EXECUTE SP_EXECUTESQL @QueryString
                      ,N'@DummyAccessRightId int, @DummyRoleId int, @DummyModuleId int, @DummyCanAdd char(1), @DummyCanEdit char(1), @DummyCanDelete char(1), @DummyCreatedDatetime datetime, @DummyLastAccessDatetime datetime, @DummyDeleted char(1)'
                      ,@DummyAccessRightId=@AccessRightId
                      ,@DummyRoleId=@RoleId
                      ,@DummyModuleId=@ModuleId
                      ,@DummyCanAdd=@CanAdd
                      ,@DummyCanEdit=@CanEdit
                      ,@DummyCanDelete=@CanDelete
                      ,@DummyCreatedDatetime=@CreatedDatetime
                      ,@DummyLastAccessDatetime=@LastAccessDatetime
                      ,@DummyDeleted=@Deleted;

AQUI estou inicializando os parâmetros de entrada do procedimento armazenado para nulo conforme segue

    @AccessRightId int=null,
@RoleId int=null,
@ModuleId int=null,
@CanAdd char(1)=null,
@CanEdit char(1)=null,
@CanDelete char(1)=null,
@CreatedDatetime datetime=null,
@LastAccessDatetime datetime=null,
@Deleted char(1)=null

isso funcionou para mim.

Espero que isso seja útil para alguém que caiu na armadilha semelhante.

Sangram
fonte
3

Se o modelo não estiver definido (ou seja, == nulo), esse erro também será gerado.

Mais comentários:

Se você souber o valor do parâmetro no momento em que adiciona parâmetros, também pode usar AddWithValue

O EXEC não é necessário. Você pode fazer referência ao parâmetro @template diretamente no SELECT.

Devio
fonte
0

Primeiro - por que isso é um EXEC? Isso não deveria ser apenas

AS
SELECT Column_Name, ...
FROM ...
WHERE TABLE_NAME = @template

O atual SP não faz sentido? Em particular, isso procuraria por uma coluna correspondente a @template, não o valor varchar de @template. ou seja, se @template for 'Column_Name', ele pesquisará WHERE TABLE_NAME = Column_Name, o que é muito raro (ter tabela e coluna com o mesmo nome).

Além disso, se você não tem que usar SQL dinâmico, você deve usar EXEC sp_ExecuteSQL(mantendo os valores como parâmetros) para impedir ataques de injeção (em vez de concatenação de entrada). Mas não é necessário neste caso.

Re o problema real - parece OK à primeira vista; tem certeza de que não tem uma cópia diferente do SP por aí? Este é um erro comum ...

Marc Gravell
fonte
Ainda não funciona com essa mudança. Tive o exec porque estava trabalhando anteriormente com um proc em que a cláusula from foi fornecida por um parâmetro, então comecei a pensar errado sobre este. Mas ainda recebo o erro apenas com o select
Tony Peterson
muito curioso; talvez verificação de erros de digitação?
Marc Gravell
0

Encontrei este erro hoje, quando valores nulos foram passados ​​para os parâmetros do meu procedimento armazenado. Consegui corrigir facilmente alterando o procedimento armazenado adicionando valor padrão = nulo.

user4249282
fonte
0

Eu tive o mesmo problema, para resolvê-lo, basta adicionar exatamente o mesmo nome de parâmetro em sua coleção de parâmetros como em seus procedimentos armazenados.

Exemplo

Digamos que você crie um procedimento armazenado:

create procedure up_select_employe_by_ID 
     (@ID int) 
as
    select * 
    from employe_t 
    where employeID = @ID

Portanto, certifique-se de nomear seu parâmetro exatamente como está em seu procedimento armazenado.

cmd.parameter.add("@ID", sqltype,size).value = @ID

se tu vais

cmd.parameter.add("@employeID", sqltype,size).value = @employeid 

então o erro acontece.

Programador haitiano
fonte
0

É necessário informar que um Stored Proc está sendo chamado:

comm.CommandType = CommandType.StoredProcedure;
pp
fonte