Banco de Dados e Null values

É comum na modelagem de banco de dados termos colunas com valores nulos. Para tipos como string, que são “reference type”, valor null é extremamente comum, mas e quanto a valores primitivos como: int, float, double, DateTime (struct no C#) e entre outros?

Na versão 2.0 do .NET, sim numa versão já adotada no mercado (.NET 1.1 é bem pouco, espero) apresentou o conceito de Nullable Types que permite você inferir valores nulos à built-in types (ou tipos primitos). Não irei entrar em detalhes muito profundos, pois pretendo ser breve nest post.

A sintaxe é bem simples:

   1: int? a = null;
   2:  
   3: // dois properties para Nullable Types, HasValue e Value
   4: if (a.HasValue) Console.WriteLine(a.Value);

Assim podemos atribuir valores para nulos e não mais valores default para estes tipos de dados.

Ok, isso é bonito mas o IDataReader não possui um método para tratar quando valores são nulos, o que fazemos? Realizamos uma checagem com o método IsDbNull, e se for podemos retornar nulo ou o valor caso OK. Veja abaixo:

   1: int? coluna = reader.IsDBNull(i) ? (int?)null : (int?)reader.GetInt32(i);

Isso resolveria o nosso problema, mas e se tivermos 10 colunas na tabela, esse código polui um pouco, não? Pena que o GetInt32(int i) não lida com campos nulos.

Como podemos melhorar isso?

Podemos usar Extension Methods!

Num post anterior eu cheguei a falar bem básicamente sobre extension methods, veja como resolveríamos este problema:

   1: static class DataReaderTypeHelper
   2: {
   3:     public static int? GetNullOrInt32(this IDataReader reader, int index)
   4:     {
   5:         return reader.IsDBNull(index) ? (int?)null : (int?)reader.GetInt32(index);
   6:     }
   7:  
   8:     public static string GetNullOrString(this IDataReader reader, int index)
   9:     {
  10:         return reader.IsDBNull(index) ? null : reader.GetString(index);
  11:     }
  12:  
  13:     public static DateTime? GetNullOrDateTime(this IDataReader reader, int index)
  14:     {
  15:         return reader.IsDBNull(index) ? (DateTime?)null : (DateTime?)reader.GetDateTime(index);
  16:     }
  17: }

Agora podemos este método e tornar a coisa mais bonita e direta:

   1: int? coluna = reader.GetNullOrInt32(index);

Muito mais simples, fácil e direto de usar. Podemos fazer também para inserção, remoção ou atualização onde valores nulos encrencam no banco de dados também. Normalmente teríamos que fazer:

   1: cmd.Parameters.AddWithValue("@param1", param1);
   2: cmd.Parameters.AddWithValue("@param2", param2);

Se usassemos o código acima, se @param2 pudesse ser nulo no banco de dados, este código geraria uma SqlException caso o valor de param2 fosse nulo, pois o null do c# é diferente do null para o banco de dados, então teríamos que verificar o valor e assim retornar DBNull.Value.

Com Nullable Types, isso ficaria extremamente simples, nada de if‘s:

   1: cmd.Parameters.AddWithValue("@param2", (object)param2 ?? DBNull.Value);

Observe que temos de fazer um cast para object em param2, pois para o compilador o operador ?? deve garantir que param2 e DBNull.Value sejam do mesmo tipo, por que Nullable usa Generics para inferir tipos, embora mascare isso.

Usando novamente Extension Methods podemos fazer uma classe que nos ajude nisso:

   1: static class DbHelper
   2: {
   3:     public static void AddWithValueOrNull(this SqlParameterCollection collection, string param, object value)
   4:     {
   5:         collection.AddWithValue(param, (object)value ?? DBNull.Value);
   6:     }
   7: }

Agora temos um método AddWithValueOrNull para o Parameters que é do tipo SqlParametersCollection. Assim encapsulamos tudo numa sintaxe mais limpa:

   1: cmd.Parameters.AddWithValueOrNull("@param2", param2);

Nota usamos este method apenas para valores que possam ser nulos e não para todos, no caso de chaves primárias devemos usar o AddWithValue mesmo por que devemos reforçar a necessidade de valores não nulos.

This entry was posted in .NET, C#3, dica. Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>