Exceção quando o parâmetro AddWithValue é NULL

92

Eu tenho o seguinte código para especificar parâmetros para consulta SQL. Estou recebendo a seguinte exceção quando uso Code 1; mas funciona bem quando eu uso Code 2. Em Code 2temos uma verificação de nulo e, portanto, um if..elsebloco.

Exceção:

A consulta parametrizada '(@application_ex_id nvarchar (4000)) SELECT E.application_ex_id A' espera o parâmetro '@application_ex_id', que não foi fornecido.

Código 1 :

command.Parameters.AddWithValue("@application_ex_id", logSearch.LogID);

Código 2 :

if (logSearch.LogID != null)
{
         command.Parameters.AddWithValue("@application_ex_id", logSearch.LogID);
}
else
{
        command.Parameters.AddWithValue("@application_ex_id", DBNull.Value );
}

QUESTÃO

  1. Você pode explicar por que ele não pode receber NULL do valor logSearch.LogID no Código 1 (mas pode aceitar DBNull)?

  2. Existe um código melhor para lidar com isso?

Referência :

  1. Atribuir nulo a um SqlParameter
  2. O tipo de dados retornado varia de acordo com os dados da tabela
  3. Erro de conversão de smallint de banco de dados em int anulável C #
  4. Qual é o objetivo do DBNull?

CÓDIGO

    public Collection<Log> GetLogs(LogSearch logSearch)
    {
        Collection<Log> logs = new Collection<Log>();

        using (SqlConnection connection = new SqlConnection(connectionString))
        {
            connection.Open();

            string commandText = @"SELECT  *
                FROM Application_Ex E 
                WHERE  (E.application_ex_id = @application_ex_id OR @application_ex_id IS NULL)";

            using (SqlCommand command = new SqlCommand(commandText, connection))
            {
                command.CommandType = System.Data.CommandType.Text;

                //Parameter value setting
                //command.Parameters.AddWithValue("@application_ex_id", logSearch.LogID);
                if (logSearch.LogID != null)
                {
                    command.Parameters.AddWithValue("@application_ex_id", logSearch.LogID);
                }
                else
                {
                    command.Parameters.AddWithValue("@application_ex_id", DBNull.Value );
                }

                using (SqlDataReader reader = command.ExecuteReader())
                {
                    if (reader.HasRows)
                    {
                        Collection<Object> entityList = new Collection<Object>();
                        entityList.Add(new Log());

                        ArrayList records = EntityDataMappingHelper.SelectRecords(entityList, reader);

                        for (int i = 0; i < records.Count; i++)
                        {
                            Log log = new Log();
                            Dictionary<string, object> currentRecord = (Dictionary<string, object>)records[i];
                            EntityDataMappingHelper.FillEntityFromRecord(log, currentRecord);
                            logs.Add(log);
                        }
                    }

                    //reader.Close();
                }
            }
        }

        return logs;
    }
LCJ
fonte
3
O que você quer dizer com melhor? O código 2 é a maneira correta de enviar um valor nulo a um banco de dados.
Phil Gan

Respostas:

152

Irritante, não é?

Você pode usar:

command.Parameters.AddWithValue("@application_ex_id",
       ((object)logSearch.LogID) ?? DBNull.Value);

Ou, alternativamente, use uma ferramenta como "dapper", que fará toda essa bagunça para você.

Por exemplo:

var data = conn.Query<SomeType>(commandText,
      new { application_ex_id = logSearch.LogID }).ToList();

Estou tentado a adicionar um método para melhorar a IDataReader... ainda não tenho certeza se é uma boa ideia.

Marc Gravell
fonte
1
Eu estava pensando em uma extensão da Parameterspropriedade - isso é um Object?
Phil Gan
6
@Phil hmmm, sim é, e entendo o que você quer dizer ... talvezAddWithValueAndTreatNullTheRightDamnedWay(...)
Marc Gravell
1
@MarcGravell Você pode explicar por que ele não consegue tirar NULL do valor logSearch.LogID no Código 1 (mas consegue aceitar DBNull)?
LCJ
18
@Lijo porque nullem um valor de parâmetro significa "não envie este parâmetro". Suspeito que tenha sido uma má decisão que simplesmente foi incorporada. Na verdade, acho que a maior parte DBNullfoi uma decisão fundamentalmente ruim que foi incorporada: stackoverflow.com/a/9632050/23354
Marc Gravell
1
@tylerH por causa das regras de fundição de carvão nulo - que podem estar enfraquecendo em C # 9
Marc Gravell
53

Acho mais fácil apenas escrever um método de extensão para o SqlParameterCollectionque lida com valores nulos:

public static SqlParameter AddWithNullableValue(
    this SqlParameterCollection collection,
    string parameterName,
    object value)
{
    if(value == null)
        return collection.AddWithValue(parameterName, DBNull.Value);
    else
        return collection.AddWithValue(parameterName, value);
}

Então você apenas chama assim:

sqlCommand.Parameters.AddWithNullableValue(key, value);
AxiomaticNexus
fonte
o valor pode ser int ou int ?, string, bool ou bool ?, DateTime ou Datetime? , etc?
Kiquenet
3
Eu li a resposta de Marc e pensei "Acho que prefiro apenas escrever um método de extensão para a coleção de parâmetros", então rolei um fio de cabelo ... (o bom sobre um método de extensão é que posso fazer um único localizar / substituir depois que todas as minhas atualizações de código estiverem concluídas)
jleach
1
Ótima solução ... Os métodos de extensão devem ser definidos em uma classe estática. Como: Implementar e chamar um método de extensão personalizado
Chris Catignani
2
Talvez eu esteja enganado (meio que um novato em C #), mas você não poderia fazer isso de forma mais concisa assim:return collection.AddWithValue(parameterName, value ?? DBNull.Value);
Tobias Feil
1
@TobiasFeil Sim, você também poderia fazer isso. É só uma questão de gosto.
AxiomaticNexus
4

Caso você esteja fazendo isso ao chamar um procedimento armazenado: Acho que é mais fácil de ler se você declarar um valor padrão no parâmetro e adicioná-lo apenas quando necessário.

SQL:

DECLARE PROCEDURE myprocedure
    @myparameter [int] = NULL
AS BEGIN

C #:

z00l
fonte
0

algum problema, permitido com necessariamente definido SQLDbType

command.Parameters.Add("@Name", SqlDbType.NVarChar);
command.Parameters.Value=DBNull.Value

onde SqlDbType.NVarChar você digita. Defina necessariamente o tipo de SQL.

user1599225
fonte