Null. La Historia interminable 2/3

Si recordamos el anterior Post, una de las frase que comente fue que desde el 2005 yo estaba libre de null y claro no hay que decirlo sino realmente demostrarlo y es por eso el motivo de esta entrada.

Lo primero que vamos a hacer es definir una interface que herede de IDataRecord y en ella definir unos métodos que nos permitan leer Nullables.

   1: public interface IDataRecordNullable : IDataRecord

   2: {

   3:     bool? GetNullBoolean(int i);

   4:     byte? GetNullByte(int i);

   5:     char? GetNullChar(int i);

   6:     DateTime? GetNullDateTime(int i);

   7:     Decimal? GetNullDecimal(int i);

   8:     double? GetNullDouble(int i);

   9:     float? GetNullFloat(int i);

  10:     Guid? GetNullGuid(int i);

  11:     Int16? GetNullInt16(int i);

  12:     Int32? GetNullInt32(int i);

  13:     Int64? GetNullInt64(int i);

  14:     string GetNullString(int i);

  15: }

Una vez definida  nos toca implementarla para ello voy a definir una clase a la cual en el constructor le voy a pasar como parametro un IDataRecord.

Solamente voy a implementar una par de métodos y eso sí os dejo la tarea del resto por si alguien quiere vivir un poco más feliz.

   1: public class MyDataRecord : IDataRecordNullable

   2: {

   3:     private readonly IDataRecord DataRecord;

   4:     public MyDataRecord(IDataRecord DataRecord)

   5:     {

   6:         if (DataRecord == null)

   7:         {

   8:             throw new ArgumentNullException();

   9:         }

  10:         this.DataRecord = DataRecord;

  11:     }

  12:     public int GetInt32(int i)

  13:     {

  14:         return DataRecord.GetInt32(i);

  15:     }

  16:     public int? GetNullInt32(int i)

  17:     {

  18:         if (IsDBNull(i))

  19:             return null;

  20:         return GetInt32(i);

  21:     }

  22: }

A partir de este momento la cosa se hace más agradable simplemente lo que tengo que hacer es utilizar esa clase para leer mis datos tal y como os muestro.

   1: using (SqlConnection cn = new SqlConnection(@"Tu conexion"))

   2: {

   3:     cn.Open();

   4:     string sql = @"SELECT CAST(NULL AS INT) NUMERO,CAST(NULL AS VARCHAR(50)) 

   5:                 CADENA UNION ALL SELECT CAST(1 AS INT),CAST('HOLA' AS VARCHAR(50)) ";

   6:     SqlCommand cmd = new SqlCommand(sql, cn);

   7:     SqlDataReader rd = cmd.ExecuteReader();

   8:                 

   9:     MyDataRecord reader = new MyDataRecord(rd);

  10:  

  11:     while (rd.Read())

  12:     {

  13:         int? numero = reader.GetNullInt32(0);

  14:         string Cadena = reader.GetNullString(1);

  15:     }

  16: }

Como podéis observar sencillo y por fin me puedo librar de utilizar este código una y otra vez.

   1: int? numero = rd.IsDBNull(0) ? null : rd.GetInt32(i);

El ejemplo es útil para cualquier versión del framework a partir de la 2.0, pero claro os voy a dar una idea, para los más modernos, entre los que me incluyo. Porque no utilizamos Extension Methods y podemos hacer lo mismo sin tener que crear un nuevo objeto cuando voy a leer.

Vamos con ello.

   1: public static class ExtensionDbDataReader

   2: {

   3:     public static int? GetNullInt32(this DbDataReader reader,int i)

   4:     {

   5:         if (reader.IsDBNull(i))

   6:             return null;

   7:         return reader.GetInt32(i);

   8:     }

   9: }

Como veis ya no tengo que ir instanciando mi clase sino simplemente al utilizar un Extension Methods me encuentro con un método extensor en cualquiera de las clases que heredan  DbDataReader, jo sin quererlo como que me sirve para cualquier motor de bb.dd.

Bueno os dejo una tarea, que es implementar 11 métodos para cada uno de los tipos primitivos y uno para el maldito “string”. Y ahora voy con ese, que se quedo en el tintero en el anterior post.

Si analizamos nuestras aplicaciones un porcentaje muy alto de nuestras propiedades son string concretamente en una app de aproximadamente unas 500 tablas  y 3800 columnas 2500, son varchar o sus derivados, es decir el 66%. Todas ellas no tienen otra representación en el CLR que no sea un string y claro a este se le puede asignar null y no dice nada, hasta que llega a la bb.dd que si la columna no permite null esta si va a decir y además cosas feas.

Pues hasta el momento solamente nos queda una solución y es bastante tediosa, algo parecido a implementar en nuestras clases INotifyPropertyChanged o bien decorarlas con atributos de System.ComponentModel.DataAnnotations dependiendo de la tecnología que utilicemos en nuestras vistas, ambas de verdad para mí feas, pero feas.

Vamos a solucionar el problema de todos los string que no permiten nulos o por lo menos como yo lo he hecho hasta el momento.

   1: public class Entidad

   2: {

   3:     public int Id { get; set; }

   4:     string _Nombre;

   5:     public string Nombre

   6:     {

   7:         set

   8:         {

   9:             _Nombre = value;

  10:         }

  11:         get

  12:         {

  13:             return _Nombre ?? string.Empty;

  14:         }

  15:     }

  16: }

Como no solamente de leer vive el hombre y a veces nos toca escribir, cuando queremos guardar un valor null en la bb.dd, también tenemos que tener en cuenta alguna que otra cosa y es que no podemos enviar null sino System.DbNull.Value, esto es más sencillo y un poco más corto, simplemente vamos a crear un Extension method para SqlParameterCollection tal y como os muestro.

   1: public static class ExtensionDbParameterCollection

   2: {

   3:     public static SqlParameter AddValueWithNull(this SqlParameterCollection Parameters,string ParameterName,SqlDbType Type,int Size, object Value )

   4:     {     

   5:         var Parametro =  Parameters.Add(ParameterName, Type, Size);

   6:         Parametro.Value = (Value == null) ? System.DBNull.Value : Value;

   7:         return Parametro;

   8:     }

   9: }

Como podéis observar lo he implementado para SqlServer el resto de servidores os lo dejo a vosotros y una cosa importante no utilizar AddWithValue, en esta entrada del foro de c# podéis ver los motivos.

SqlParameterCollection.AddWithValue Method.

Para terminar y como se que alguno va a decir ¿porque no utilizas generic y te ahorras esos 11 métodos y el maldito? Pues sencillo porque me obliga a hacer cast en cada una de las lecturas y menos óptimo puede ser cualquier cosa y más si leemos muchos registros, de todas formas os dejo ese método con generic. Pero ya os he advertido de las consecuencias Guiño.

   1: public static class ExtensionDbDataReader

   2: {

   3:     public static Nullable<T> GetNullValue<T>(this DbDataReader Reader, int i) where T:struct

   4:     {

   5:         if (Reader.IsDBNull(i))

   6:             return null;

   7:         return (T)Reader.GetValue(i);

   8:         

   9:     }

  10: }

Saludos y a implementarlo.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *