¿Existe la columna con DataReader?

En mi empresa desde un principio hemos utilizado una herramienta propia para generar la capa de lógica de negocios, adaptada  al 100% a nuestras necesidades  y que ha ido creciendo con el tiempo. Eso nos ha beneficiado en tener controlado en todo momento nuestros objetos de negocio y nos ha evitado tener que hacer un salto obligatorio para utilizar alguno de los ORM que existen en el mercado, como puede ser: Entity Framework, NHibernate, etc.

Nuestra capa de acceso a datos “DAL” encapsula toda la lógica de conexiones a datos independientemente del proveedor y hay una de las funciones que retorna un lector de datos para poder personalizar la lectura de los datos desde la capa de lógica de Negocios.

 /// <summary>
 /// Ejecuta el comando creado y retorna el lector.
 /// </summary>
 /// <returns>El resultado de la consulta.</returns>
 public DbDataReader EjecutarConsulta(string cmd, CommandType tipo)
 {
     this.comando.CommandText = cmd;
     this.comando.CommandType = tipo;
     this.Conectar();
     return this.comando.ExecuteReader();
 }

Eso facilita la carga del objeto de negocio y optimiza la lectura.

DbDataReader reader = null;
Using (Dal dal = new Dal())
{
  string command = "select id, nombre, descripcion from productos";
  reader = dal.EjecutarConsulta(command, CommandType.Text);
  while (reader.Read())
  {
    ...

Pero uno de los problemas habituales que nos encontraremos con el objeto DataReader es que si no nos gusta leer los datos por posición, como es mi caso y preferimos controlar la columna que deseamos leer. Nos podemos encontrar con el horrible error que no se encuentra la columna que estamos intentando leer.

Ya podemos intentar validar si el objeto es null, pero no funciona:

capas

O si el dato está vacio:

capas2

Para poder validar si la columna que estamos consultando existe podemos tener una función en nuestro DAL que valide esta situación utilizando el propio esquena de la tabla:

public bool ValidarColumna( DbDataReader reader, string nombreColumna)
{
   reader.GetSchemaTable().DefaultView.RowFilter = 
     "ColumnName= '"  + nombreColumna + "'";
   return (reader.GetSchemaTable().DefaultView.Count > 0);
}

Finalmente cuando queramos hacer la consulta de los datos utilizaremos el validador para evitar errores no controlados en el acceso a datos.

 using (Dal dal = new Dal())
 {
    string command = @"select id, nombre, descripcion 
";
        from productos
    reader = dal.EjecutarConsulta(command, CommandType.Text);
    while (reader.Read())
    {
      ProductoData produc = new ProductoData();
      produc.ID = (dal.ValidarColumna(reader, "ID") 
        && reader["ID"] != DBNull.Value)?(int)reader["ID"]:0;
      produc.Nombre = (dal.ValidarColumna(reader, "Nombre")
        && reader["Nombre"] != DBNull.Value)
        ?reader["Nombre"].ToString():string.Empty;
      listaProductos.Add(produc);
    }
}

Es una forma un poco rebuscada, pero no conozco ninguna otra forma de verificar esta información.

Espero que os sea útil.

CrossPosting de http://lonetcamp.com

9 comentarios en “¿Existe la columna con DataReader?”

  1. Hola Marc, interesante artículo, en mi opinión el error parte de la escritura de la sentencia Sql, si escribes directamente el nombre de los campos, esto dara lugar a muchos errores como los que espones, en nuestro caso nunca generamos las sentencias de esta forma sino que utilizamos los campos de la entidad para escribirlas por ejemplo utilizamos

    Entities.Compras.Albaranes_cabecera.Fields.Modo.ToString()

    Esto nos devolveria del campo Modo de la tabla Albaranes_cabecera del esquema Compras a partir de la entidad, con lo cual nuestras sentencias Select, update e insert se generan siempre utilizando este modelo, evitando estos errores, si en algun momento cambiamos el nombre de un campo de la tabla y refactorizamos la entidad, el programa hara los ajustes correspondientes y las sentencias seguiran funcionando.

    Esto se consigue añadiendo a la entidad un enumerador

    #region Fields enum

    public enum Fields
    {
    Codigo, Espanol, Idioma, Referencia, Tipo, Traduccion
    }

    En nuestro caso construimos un generador de entidades utilizando Codedom asi que resulta de lo mas sencillo poder añadir esta funcionalidad.

    Un saludo.

  2. Algún ejemplo de open source de generador de entidades con Codedom ? no hay nada en codeplex ??

    .GetSchemaTable().DefaultView.RowFilter sería lento,no?

    Linq to sql evitaría esto, no?

    Salu2&grz

  3. Optimización si vas a iterar por muchas filas:

    – Extrae el índice de la columna antes del bucle: Por nombre sacas el índice.

    – Extrae las columnas por ese índice.
    ……………………………………………
    int nameColIndex = dr.GetOrdinal(“Name”);
    int idColIndex = dr.GetOrdinal(“ID”);

    while (dr.Read())
    {
    string name = dr.GetString(nameColIndex);
    int id = dr.GetInt32(idColIndex);

    //……

    }
    ……………………………………………….

    Con ésto mantienes las ventajas de buscar la columna por índice( “más rápido” ) y la legibilidad y robustez frente al cambio de órden de las columnas en la BD.

    Aunque claro, como siempre tendrán que ser unas cuantas filas para que se note 🙂

  4. Hola Marc y Juan

    Precisamente estoy ahora con temas de estos. Había pensado en la utilidad de Marc, pero eso de obtener el esquema para cada consulta me parece poco optimizado, sería mejor optimizar esto haciendo una obtención al comienzo, inmediatamente despues del execute reader y guardar los valores de las columnas dentro de la clase en una colección, luego en cada acceso recurrir a la colección en lugar de solicitar el esquema por cada acceso a cada columna.

    Juan tu ejemplo me es muy inetersante . A mi se me había ocurrido esto también de usar un enum, pero definiéndolo lo más cerca posible de la BBDD, es decir en otra entidad que reuna todo lo relativo al diseño (def de campos, reglas de validación de campo, etc…) de manera que al modificar el diseño de tabla para agregar nuevas columnas este todo muy junto y que el programador lo haga de un tirón, si se os ocurre algo agradeceré vuestra opinión.

    Un saludo

  5. @Juan En mi opinión el problema es el mismo, porque puede pasar que la estructura de la base de datos no corresponda con la de la entidad si la actualización no se ha realizado correctamente.

    Nosotros en este caso utilizamos Storeds procedures para realizar las consultas a un SQL, de esta manera podemos modificar la consulta para solucionar algún problema sin tener que recompilar la aplicación y siempre la respuesta es algo más rápida.

    Con esta solución simplemente evitamos que alguien modifique la estructura de la tabla o que accidentalmente la entidad espere un registro que no existe y no lance una excepción no controlada, si no existe no lo carga. 😉

    Pero como he comentado es una solución adoptada para seguir con la misma metodología que utilizamos antes de que surgieran los actuales ORM’s.

    @preguntoncojonero El DataReader es la opción más rápida de lectura de datos y mientras el lector se encuentre abierto no hay problema para consultar el esquema de la fila recuperada.

  6. @Marc, no estoy de acuerdo, mientras que tu, cuando haces un cambio debes buscar en todas las sentencias escritas en transact sql que puedan afectar a tu entidad y todas las filas en las que utilizas cadenas de tipo reader[“ID”]. En mi caso asumimos que cualquier cambio en la base de datos supone una actualización de nuestro modelo de datos de forma similar a EF, tan solo tengo que regenerar la entidad con un proceso automático y el sistema seguirá funcionando. En el caso de los store procedures pasa lo mismo, si cambias el nombre de un campo, agregas uno o lo eliminas, tu aplicación fallara, esto solo puede detectarse a través de pruebas unitarias de BD.

    En cualquier caso, la validación que realizas puede suponer un coste muy alto ya que debes realizar un filtrado por cada columna del datareader, si este tiene muchas filas el rendimiento se vera afectado considerablemente y me pregunto que sucedera si la columna a cambiado de nombre tipo o tamaño, fallara la sentencia sql, pero solo lo detectaras en tiempo de ejecución, el mantenimiento sera mayor y la detección de errores tambien, pues hay que revisar todas las referencias que se puedan tener en la aplicación.

    Imagina que sucede si cambias el nombre del campo ID y le llamas PersonID, tendras que buscar en tu aplicación todos los campos ID en una cadena, ¿Y si tienes tablas que tengan tambien el mismo nombre?, te puedes volver loco para encontrar las referencias.

    Un saludo.

  7. @Julio, la gran ventaja de hacerlo en la propia entidad, es que no tienes instanciar otra para disponer de la estructura completa.

    Maldivas.Entities.Products.Fields.ProductId,
    Description
    Price
    Etc…

    de la misma forma si quieres acceder al valor de un campo de la entidad:

    Maldivas.Entities.Products.ProductID
    Maldivas.Entities.Products.Description
    Maldivas.Entities.Products.Price

    En el momento que escribes fields el intelisense te muestra la lista de todos los campos ordenados, con lo que es muy sencillo utilizarlos para conformar sentencias sql, filtros y otros aspectos.

    No te creara ningún problema el implementarlo en la propia clase, pues el enumerador no aparece en los grids y otros controles cuando se visualizan los datos. Con otra ventaja, no tienes que instanciar la entidad para poder acceder al enumerador se comporta como una propiedad estática.

    Un saludo.

  8. @Pablo Alarcón, Lamentablemente GetOrdinal() tiene el mismo problema. Si no existe la columna en la sentencia, lanzara un error IndexOutOfRangeException y estamos en las mismas 😉

    @Juan NO es exactamente así, este truco precisamente es para evitar fallos si la sentencia del stored varía. Si la columna no existe agregará el valor por defecto a la propiedad de la entidad y como tu ejemplo, es la misma entidad la que se encarga de inicializar sus valores y solo ella, un solo punto para toda la aplicación.

    Pero bueno como he comentado anteriormente el uso de esta estructura es por motivos internos de la empresa, eso no quiere decir que mañana utilicemos un ORM u otro modelo de objetos mas sofitficado como el tuyo 😉

    Realmente este es un tema que se le puede sacar mucho jugo.

  9. Hola Marc, asi es el tema es muy interesante, en cualquier caso si lo que buscas es controlar los cambios que puedan producirse en la base de datos, te recomiendo que leas el artículo que escribi sobre pruebas unitarias en base de datos, en este post escribo un test para blindar un SP, frente a cualquier cambio que se pueda producir, cambio de nombres de los parámetros, longitud, eliminación de parametros, etc. De estas forma tienes la seguridad de que si el test falla debes variar necesariamente el código que utilizan los sp, además tienes la ventaja de que lo puedes aplicar en cualquier arquitectura.

    http://geeks.ms/blogs/jirigoyen/archive/2009/10/25/database-professionals-2010-pruebas-unitarias-de-bases-de-datos-y-materiales-de-la-charla.aspx

    Un saludo.

Deja un comentario

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