SQL Data Reader - manipulação de valores de coluna nulos

298

Estou usando um SQLdatareader para criar POCOs a partir de um banco de dados. O código funciona, exceto quando encontra um valor nulo no banco de dados. Por exemplo, se a coluna Nome no banco de dados contiver um valor nulo, uma exceção será lançada.

employee.FirstName = sqlreader.GetString(indexFirstName);

Qual é a melhor maneira de lidar com valores nulos nessa situação?

DenaliHardtail
fonte

Respostas:

471

Você precisa verificar IsDBNull:

if(!SqlReader.IsDBNull(indexFirstName))
{
  employee.FirstName = sqlreader.GetString(indexFirstName);
}

Essa é sua única maneira confiável de detectar e lidar com essa situação.

Eu agrupei essas coisas em métodos de extensão e tendem a retornar um valor padrão se a coluna for realmente null:

public static string SafeGetString(this SqlDataReader reader, int colIndex)
{
   if(!reader.IsDBNull(colIndex))
       return reader.GetString(colIndex);
   return string.Empty;
}

Agora você pode chamar assim:

employee.FirstName = SqlReader.SafeGetString(indexFirstName);

e você nunca precisará se preocupar com uma exceção ou um nullvalor novamente.

marc_s
fonte
65
Se alguém precisar do nome da coluna em vez do índice, você poderá: int colIndex = reader.GetOrdinal(fieldname);e sobrecarregar facilmente a SafeGetStringfunção de @ marc_s .
ilans
Não posso acreditar que estou aqui em 2019, em VB não menos .......... Graças embora, grande ajuda
JimmyB
Também pode ser feito assim: int ordinal = reader.GetOrdinal ("col_name"); uint? val = reader.IsDBNull (ordinal)? (uint?) null: reader.GetUInt32 (ordinal);
ed22
Ola pessoal! Tentei copiar e colar no formulário e retornei com um erro. "O método de extensão deve ser definido em uma classe estática não genérica."
Jansen Malaggay
Se você estiver usando o reader.GetOrindal dentro do SafeGetString e quiser usar o SafeGetString dentro de um loop, como isso pode ser alcançado sem o desempenho atingido?
AspUser7724
223

Você deve usar o asoperador combinado com o ??operador para valores padrão. Os tipos de valor precisarão ser lidos como anuláveis ​​e receber um padrão.

employee.FirstName = sqlreader[indexFirstName] as string;
employee.Age = sqlreader[indexAge] as int? ?? default(int);

O asoperador lida com a transmissão, incluindo a verificação de DBNull.

stevehipwell
fonte
6
Se alguém alterar a coluna Idade de int para bigint SQL (c # long), seu código falhará silenciosamente retornando 0. A resposta do ZXX é o IMO mais confiável.
Martin Ørding-Thomsen
Gostaria de saber se você pode substituir o padrão (int) para ser -1 em vez de 0
Chris
5
@ Chris - Você poderá substituir apenas o padrão (int) por -1.
Stevehipwell 17/05
@ Stevo3000 Você está correto! Tentei fazer isso e funcionou como você disse logo depois que eu postei, mas eu esqueci de voltar a esta página :)
Chris
5
Esteja ciente de que usar "como" aqui pode ocultar erros de índice. Se você usar acidentalmente sqlreader[indexAge] as string ?? "", sempre receberá "". Considere se você realmente deseja (int?)sqlreader[indexAge] ?? defaultValue, portanto, se o seu SQL mudar, você obterá exceções em vez de valores ruins. @ Stevo3000: o padrão (int) é 0, não -1. @ Chris: Verifique se você está usando o que você realmente deseja.
ME22
30

Para uma string, você pode simplesmente converter a versão do objeto (acessada usando o operador array) e terminar com uma string nula para valores nulos:

employee.FirstName = (string)sqlreader[indexFirstName];

ou

employee.FirstName = sqlreader[indexFirstName] as string;

Para números inteiros, se você converter para um int nulo, poderá usar GetValueOrDefault ()

employee.Age = (sqlreader[indexAge] as int?).GetValueOrDefault();

ou o operador de coalescência nula ( ??).

employee.Age = (sqlreader[indexAge] as int?) ?? 0;
Gone Coding
fonte
2
A conversão explícita, como no seu primeiro exemplo, não funciona.
Emite
@musefan: Seu campo é realmente uma string? Caso contrário, você receberá um erro diferente. Isso funciona e não há diferença real entre os exemplos 1 e 2 (além da sintaxe).
Gone Coding
1
@GoneCoding: Sim, é uma string anulável, e é definitivamente um caso em que o primeiro causa problemas quando o segundo funciona. Eu imagino que o problema é causado por como os valores nulos são tratados. Como em, eles não são, nullmas sim um objeto DBNull. A diferença entre as duas instruções é que a primeira falhará se não for uma sequência, enquanto a segunda retornará nulo se não for uma sequência.
Musefan 4/08
23

IsDbNull(int)geralmente é muito mais lento do que usar métodos como GetSqlDateTimee comparar DBNull.Value. Experimente estes métodos de extensão para SqlDataReader.

public static T Def<T>(this SqlDataReader r, int ord)
{
    var t = r.GetSqlValue(ord);
    if (t == DBNull.Value) return default(T);
    return ((INullable)t).IsNull ? default(T) : (T)t;
}

public static T? Val<T>(this SqlDataReader r, int ord) where T:struct
{
    var t = r.GetSqlValue(ord);
    if (t == DBNull.Value) return null;
    return ((INullable)t).IsNull ? (T?)null : (T)t;
}

public static T Ref<T>(this SqlDataReader r, int ord) where T : class
{
    var t = r.GetSqlValue(ord);
    if (t == DBNull.Value) return null;
    return ((INullable)t).IsNull ? null : (T)t;
}

Use-os assim:

var dd = r.Val<DateTime>(ords[4]);
var ii = r.Def<int>(ords[0]);
int nn = r.Def<int>(ords[0]);
ZXX
fonte
5
Eu estou achando que os operadores explícitos sobre os tipos System.Data.SqlTypes está jogando erros em todos os lugares tentando usar este código ...
Tetsujin no Oni
Consulte stackoverflow.com/a/21024873/1508467 para obter uma explicação de por que isso às vezes falha (tente usar Val <int> para ler uma coluna int SQL).
Rhys Jones
12

Uma maneira de fazer isso é procurar nulos db:

employee.FirstName = (sqlreader.IsDBNull(indexFirstName) 
    ? ""
    : sqlreader.GetString(indexFirstName));
Michael Todd
fonte
12

reader.IsDbNull(ColumnIndex) funciona como muitas respostas diz.

E quero mencionar que, se você trabalha com nomes de colunas, apenas comparar tipos pode ser mais confortável.

if(reader["TeacherImage"].GetType() == typeof(DBNull)) { //logic }
PJ3
fonte
Isso também funciona em versões antigas do System.Data e .NET FW
RaSor 7/17/17
11

Acho que não há um valor de coluna NULL , quando as linhas são retornadas em um datareader usando o nome da coluna.

Se você fizer datareader["columnName"].ToString();isso, sempre fornecerá um valor que pode ser uma sequência vazia ( String.Emptyse você precisar comparar).

Eu usaria o seguinte e não me preocuparia muito:

employee.FirstName = sqlreader["columnNameForFirstName"].ToString();
el bayames
fonte
4
Você pode fazer um leitor [FieldName] == DBNull.Value, para verificar se há NULL's
Ralph Willgoss
11

Esta solução é menos dependente do fornecedor e funciona com um SQL, OleDB e MySQL Reader:

public static string GetStringSafe(this IDataReader reader, int colIndex)
{
    return GetStringSafe(reader, colIndex, string.Empty);
}

public static string GetStringSafe(this IDataReader reader, int colIndex, string defaultValue)
{
    if (!reader.IsDBNull(colIndex))
        return reader.GetString(colIndex);
    else
        return defaultValue;
}

public static string GetStringSafe(this IDataReader reader, string indexName)
{
    return GetStringSafe(reader, reader.GetOrdinal(indexName));
}

public static string GetStringSafe(this IDataReader reader, string indexName, string defaultValue)
{
    return GetStringSafe(reader, reader.GetOrdinal(indexName), defaultValue);
}
Horário de verão
fonte
1
Copiando e personalizando esse código diretamente em uma classe de extensões no momento.
Qxotk
8

O que costumo fazer é substituir os valores nulos na instrução SELECT por algo apropriado.

SELECT ISNULL(firstname, '') FROM people

Aqui substituo todo nulo por uma sequência em branco. Seu código não irá gerar erros nesse caso.

alex
fonte
Se possível, use isso para evitar nulos. Caso contrário, gosto da resposta dos métodos auxiliares de Sonny Boy.
Sem reembolsos Sem devoluções
3
Por que um método auxiliar estático e separado? Um método de extensão no SqlDataReader não parece mais atraente e mais intuitivo?
21480 Marc
7

Você pode escrever uma função genérica para verificar nulo e incluir o valor padrão quando for nulo. Chame isso ao ler Datareader

public T CheckNull<T>(object obj)
        {
            return (obj == DBNull.Value ? default(T) : (T)obj);
        }

Ao ler o Datareader, use

                        while (dr.Read())
                        {
                            tblBPN_InTrRecon Bpn = new tblBPN_InTrRecon();
                            Bpn.BPN_Date = CheckNull<DateTime?>(dr["BPN_Date"]);
                            Bpn.Cust_Backorder_Qty = CheckNull<int?>(dr["Cust_Backorder_Qty"]);
                            Bpn.Cust_Min = CheckNull<int?>(dr["Cust_Min"]);
                         }
Vijai
fonte
6

Verifique sqlreader.IsDBNull(indexFirstName)antes de tentar lê-lo.

CesarGon
fonte
5

Ao influenciar na resposta de getpsyched , criei um método genérico que verifica o valor da coluna pelo nome

public static T SafeGet<T>(this System.Data.SqlClient.SqlDataReader reader, string nameOfColumn)
{
  var indexOfColumn = reader.GetOrdinal(nameOfColumn);
  return reader.IsDBNull(indexOfColumn) ? default(T) : reader.GetFieldValue<T>(indexOfColumn);
}

Uso:

var myVariable = SafeGet<string>(reader, "NameOfColumn")
Mayhem projeto
fonte
Não sei quem você é, mas te abençoe. Esta função economizou mais de três horas de trabalho.
Ramneek Singh
4

sobre como criar métodos auxiliares

For String

private static string MyStringConverter(object o)
    {
        if (o == DBNull.Value || o == null)
            return "";

        return o.ToString();
    }

Uso

MyStringConverter(read["indexStringValue"])

Para Int

 private static int MyIntonverter(object o)
    {
        if (o == DBNull.Value || o == null)
            return 0;

        return Convert.ToInt32(o);
    }

Uso

MyIntonverter(read["indexIntValue"])

Para data

private static DateTime? MyDateConverter(object o)
    {
        return (o == DBNull.Value || o == null) ? (DateTime?)null : Convert.ToDateTime(o);
    }

Uso

MyDateConverter(read["indexDateValue"])

Nota: para DateTime, declare varialbe como

DateTime? variable;
Usman Ali
fonte
4

Como complemento à resposta de marc_s, você pode usar um método de extensão mais genérico para obter valores do SqlDataReader:

public static T SafeGet<T>(this SqlDataReader reader, int col)
    {
        return reader.IsDBNull(col) ? default(T) : reader.GetFieldValue<T>(col);
    }
empolgado
fonte
Eu não chamaria esse método de "SafeGet" porque, se T for uma estrutura, ele converterá nulos no padrão não nulo para T - não é realmente seguro. Talvez "GetValueOrDefault".
Rhys Jones
@RhysJones Por que você teria T como uma estrutura nesse caso? Mesmo se você argumentar, o padrão não nulo na estrutura é o comportamento esperado.
getpsyched
@RhysJones Mas eu concordo que esse método pode não ser seguro, ele precisaria lidar com exceções como InvalidCastException do SqlDataReader.
precisa
3

Eu acho que você gostaria de usar:

SqlReader.IsDBNull(indexFirstName)
bytebender
fonte
2

Usamos uma série de métodos estáticos para extrair todos os valores de nossos leitores de dados. Então, neste caso, estaríamos chamandoDBUtils.GetString(sqlreader(indexFirstName)) O benefício de criar métodos estáticos / compartilhados é que você não precisa fazer as mesmas verificações repetidas vezes ...

O (s) método (s) estático (s) conteria código para verificar se há nulos (consulte outras respostas nesta página).

Sonny Boy
fonte
2

Você pode usar o operador condicional:

employee.FirstName = sqlreader["indexFirstName"] != DBNull.Value ? sqlreader[indexFirstName].ToString() : "";
Panayot Minkov
fonte
O mesmo que uma das respostas abaixo, mas com 8 anos de atraso!
BeerChol22 /
2

Há muitas respostas aqui com informações úteis (e algumas informações erradas) espalhadas, eu gostaria de reunir tudo.

A resposta curta para a pergunta é verificar o DBNull - quase todo mundo concorda com esse bit :)

Em vez de usar um método auxiliar para ler valores anuláveis ​​por tipo de dados SQL, um método genérico nos permite abordar isso com muito menos código. No entanto, você não pode ter um único método genérico para os tipos de valor anulável e de referência. Isso é discutido detalhadamente no tipo Nullable como um parâmetro genérico possível. e restrição de tipo genérico C # para tudo que pode ser anulado .

Portanto, seguindo as respostas de @ZXX e @getpsyched, terminamos com dois métodos para obter valores anuláveis ​​e adicionei um terceiro para valores não nulos (ele completa o conjunto com base na nomeação de métodos).

public static T? GetNullableValueType<T>(this SqlDataReader sqlDataReader, string columnName) where T : struct
{
    int columnOrdinal = sqlDataReader.GetOrdinal(columnName);
    return sqlDataReader.IsDBNull(columnOrdinal) ? (T?)null : sqlDataReader.GetFieldValue<T>(columnOrdinal);
}

public static T GetNullableReferenceType<T>(this SqlDataReader sqlDataReader, string columnName) where T : class
{
    int columnOrdinal = sqlDataReader.GetOrdinal(columnName);
    return sqlDataReader.IsDBNull(columnOrdinal) ? null : sqlDataReader.GetFieldValue<T>(columnOrdinal);
}

public static T GetNonNullValue<T>(this SqlDataReader sqlDataReader, string columnName)
{
    int columnOrdinal = sqlDataReader.GetOrdinal(columnName);
    return sqlDataReader.GetFieldValue<T>(columnOrdinal);
}

Eu geralmente uso nomes de colunas, altere-os se você usar índices de colunas. Com base nesses nomes de método, posso dizer se estou esperando que os dados sejam anuláveis ​​ou não, o que é bastante útil quando se olha o código escrito há muito tempo.

Dicas;

  • Não ter colunas anuláveis ​​no banco de dados evita esse problema. Se você tiver controle sobre o banco de dados, as colunas deverão ser não nulas por padrão e somente nulas quando necessário.
  • Não converta valores de banco de dados com o operador C # 'as' porque, se a conversão estiver incorreta, ela retornará silenciosamente nulo.
  • O uso de uma expressão de valor padrão alterará os valores nulos do banco de dados para valores não nulos para tipos de valores como int, datetime, bit etc.

Por fim, ao testar os métodos acima em todos os tipos de dados do SQL Server, descobri que você não pode obter diretamente um char [] de um SqlDataReader; se você quiser um char [], precisará obter uma string e usar ToCharArray ().

Rhys Jones
fonte
1

Estou usando o código listado abaixo para manipular células nulas em uma planilha do Excel que é lida em uma tabela de dados.

if (!reader.IsDBNull(2))
{
   row["Oracle"] = (string)reader[2];
}
Tequila
fonte
1
private static void Render(IList<ListData> list, IDataReader reader)
        {
            while (reader.Read())
            {

                listData.DownUrl = (reader.GetSchemaTable().Columns["DownUrl"] != null) ? Convert.ToString(reader["DownUrl"]) : null;
                //没有这一列时,让其等于null
                list.Add(listData);
            }
            reader.Close();
        }
xux
fonte
1

e / ou usar operador ternário com atribuição:

employee.FirstName = rdr.IsDBNull(indexFirstName))? 
                     String.Empty: rdr.GetString(indexFirstName);

substitua o valor padrão (quando nulo) conforme apropriado para cada tipo de propriedade ...

Charles Bretana
fonte
1

Esse método depende do indexFirstName, que deve ser a coluna ordinal com base em zero.

if(!sqlReader.IsDBNull(indexFirstName))
{
  employee.FirstName = sqlreader.GetString(indexFirstName);
}

Se você não conhece o índice da coluna, mas não deseja verificar um nome, pode usar este método de extensão:

public static class DataRecordExtensions
{
    public static bool HasColumn(this IDataRecord dr, string columnName)
    {
        for (int i=0; i < dr.FieldCount; i++)
        {
            if (dr.GetName(i).Equals(columnName, StringComparison.InvariantCultureIgnoreCase))
                return true;
        }
        return false;
    }
}

E use o método como este:

if(sqlReader.HasColumn("FirstName"))
{
  employee.FirstName = sqlreader["FirstName"];
}
Ogglas
fonte
1

Pergunta antiga, mas talvez alguém ainda precise de uma resposta

na verdade, eu trabalhei em torno desta questão assim

Para int:

public static object GatDataInt(string Query, string Column)
    {
        SqlConnection DBConn = new SqlConnection(ConnectionString);
        if (DBConn.State == ConnectionState.Closed)
            DBConn.Open();
        SqlCommand CMD = new SqlCommand(Query, DBConn);
        SqlDataReader RDR = CMD.ExecuteReader();
        if (RDR.Read())
        {
            var Result = RDR[Column];
            RDR.Close();
            DBConn.Close();
            return Result;
        }
        return 0;
    }

o mesmo para a string, basta retornar "" em vez de 0, pois "" é uma string vazia

então você pode usá-lo como

int TotalPoints = GatDataInt(QueryToGetTotalPoints, TotalPointColumn) as int?;

e

string Email = GatDatastring(QueryToGetEmail, EmailColumn) as string;

muito flexível para que você possa inserir qualquer consulta para ler qualquer coluna e ela nunca retornará com erro

Ahmed Kamal
fonte
0

Aqui está a classe auxiliar que outras pessoas podem usar se precisarem, com base na resposta @marc_s:

public static class SQLDataReaderExtensions
    {
        public static int SafeGetInt(this SqlDataReader dataReader, string fieldName)
        {
            int fieldIndex = dataReader.GetOrdinal(fieldName);
            return dataReader.IsDBNull(fieldIndex) ? 0 : dataReader.GetInt32(fieldIndex);
        }

        public static int? SafeGetNullableInt(this SqlDataReader dataReader, string fieldName)
        {
            int fieldIndex = dataReader.GetOrdinal(fieldName);
            return dataReader.GetValue(fieldIndex) as int?;
        }

        public static string SafeGetString(this SqlDataReader dataReader, string fieldName)
        {
            int fieldIndex = dataReader.GetOrdinal(fieldName);
            return dataReader.IsDBNull(fieldIndex) ? string.Empty : dataReader.GetString(fieldIndex);
        }

        public static DateTime? SafeGetNullableDateTime(this SqlDataReader dataReader, string fieldName)
        {
            int fieldIndex = dataReader.GetOrdinal(fieldName);
            return dataReader.GetValue(fieldIndex) as DateTime?;
        }

        public static bool SafeGetBoolean(this SqlDataReader dataReader, string fieldName)
        {
            return SafeGetBoolean(dataReader, fieldName, false);
        }

        public static bool SafeGetBoolean(this SqlDataReader dataReader, string fieldName, bool defaultValue)
        {
            int fieldIndex = dataReader.GetOrdinal(fieldName);
            return dataReader.IsDBNull(fieldIndex) ? defaultValue : dataReader.GetBoolean(fieldIndex);
        }
    }
Raghav
fonte
0

O Convert lida com DbNull de maneira sensata.

employee.FirstName = Convert.ToString(sqlreader.GetValue(indexFirstName));
Frank Hagenson
fonte
Observe que DBNull é convertido em uma cadeia vazia, não em um valor nulo.
Rhys Jones
-2

você já pode verificar isso também

if(null !=x && x.HasRows)
{ ....}
pateta
fonte
-1 Este não é o ponto: estamos a tratar do caso de um valor de coluna nulo, e não a de um nulo ou vazioSqlDataReader
azulado