Automatizar lectura de un SqlDataReader

No soy muy bueno escribiendo, pero en esta ocasión, traigo algo que a más de uno le puede interesar.

Se trata de una clase c# que nos va a ayudar a ejecutar procedimientos almacenados Sql, sobre todo, procedimientos que nos devuelvan conjuntos de filas y mapearlas a objetos de clases definidas previamente.

Por ejemplo, tengo un procedimiento que tiene la siguiente instrucción


SELECT clave, descripcion, fechaCreacion, clienteID,
fechaEntrega, folioInicial, importe, estatus, esPermanente
FROM remision
WHERE fechaCreacion BETWEEN @fechaInicio AND @fechaFinal

El codigo puesto arriba es completamente ficticio, lo pongo sólo para ilustrar el ejemplo.

Continuamos, deseo tener una lista de objetos mapeado al resultado de la consulta anterior. Bien, la definición de mi clase sería más o menos así:


public class Remision
{
public string Clave { get; set; }
public string Descripcion { get; set; }
public DateTime FechaCreacion { get; set; }
public int ClienteID { get; set; }
public DateTime FechaEntrega { get; set; }
public int FolioInicial { get; set; }
public Decimal Importe { get; set; }
public string Estatus { get; set; }
public bool EsPermanente { get; set; }
}

Y el código para obtener mi lista de objetos sería más o menos así (No voy a poner bloques try-catch porque es sólo ilustrativo. Es media noche 😉 ):


SqlConnection cnn = new SqlConnection(cadenaConexion);
cnn.Open();
SqlCommand comando = cnn.CreateCommand();
comando.CommandType = CommandType.StoredProcedure;
comando.CommandText = "procedimiento_a_ejecutar";
comando.Parameters.AddWithValue("@fechaInicio", valorFechaInicio);
comando.Parameters.AddWithValue("@fechaFinal", valorFechaFinal);
SqlDataReader dr = comando.ExecuteReader();

// Esta es mi lista de objetos final
List<Remision> remisiones = new List<Remision>();

while (consulta.Read())
{
// Se hace el fetch de cada campo
Remision r = new Remision();

r.Clave = dr["clave"].ToString();
r.Descripcion = dr["descripcion"].ToString();
r.FechaCreacion = dr.GetDateTime("fechaCreacion");
r.ClienteID = dr.GetInt32("clienteID");
r.FechaEntrega = dr.GetDateTime("fechaEntrega");
r.FolioInicial = dr.GetInt32("folioInicial");
r.Importe = dr.GetDecimal("importe");
r.Estatus = dr.GetString("estatus");
r.EsPermanente = dr.GetBoolean("espermanente");

// Se agrega el objeto a la lista
remisiones.Add(r);
}

consulta.Close();

Uuuf, menos mal que sólo fueron esos campos. Al código anterior súmenle validaciones y demás cosas.

Lo anterior es muy simple, recorrer el SqlDataReader y llenar cada uno de los objetos y ponerlos en la lista.

Las cosas se complican cuando regresamos muchos más campos en la consulta del procedimiento almacenado.

Bueno, al objetivo de esta entrada.

El objetivo es reemplazar el código anterior, por un código mucho más simple. A continuación pongo la llamada final:


SqlConnection cnn = new SqlConnection(conexionString);
cnn.Open();
SqlHelper helper = new SqlHelper(cnn, null);

var lista = helper.ExecuteSqlProc<Remision>("procedimiento_a_ejecutar",
new SqlParameter("@fechaInicio", valorFechaInicio),
new SqlParameter("@fechaFinal", valorFechaFinal));

// En la variable "lista", tenemos la lista de objeto Remision.
return ll;

Hemos reducido muchísimo el código utilizado para recorrer un SqlDataReader y crear una lista de objetos a partir de este.

Ahora, como logramos esto, bien, pues en seguido pongo el código de nuestra clase SqlHelper.


public class SqlHelper
{
private SqlConnection _cn = null;
private SqlTransaction _tran = null;
public SqlHelper(SqlConnection cnn, SqlTransaction tran)
{
this._cn = cnn;
this._tran = tran;
}

public List<T> ExecuteList<T>(string procName, params SqlParameter[] parametros)
{
SqlDataReader dr = null;
try
{
SqlCommand comando = this._cn.CreateCommand();
if (this._tran != null)
comando.Transaction = this._tran;

comando.CommandType = CommandType.StoredProcedure;
comando.CommandText = procName;

if (parametros != null)
{
foreach (SqlParameter p in parametros)
comando.Parameters.Add(p);
}

dr = comando.ExecuteReader();

// Se invoca el método para obtener la lista de propiedades del tipo de objeto indicado
// enviándole como parámetro una instancia del tipo genérico
object objecto = Activator.CreateInstance<T>();

// Obtiene una lista de campos que han hecho match
List<string> camposMatch = GetPropertyMatch(objecto, dr);

// Se itera el darareader
// y se leen los campos que han hecho match
List<T> listaARegresar = new List<T>();
while (dr.Read())
{
// Se crea una nueva instanacia en cada iteración
objecto = Activator.CreateInstance<T>();
foreach (string s in camposMatch)
{
PropertyInfo p = objecto.GetType().GetProperty(s);
if (!dr.IsDBNull(dr.GetOrdinal(s)))
{
switch (p.PropertyType.Name.ToLower())
{
case "boolean":
p.SetValue(objecto, dr.GetBoolean(dr.GetOrdinal(s)), null);
break;
case "byte":
p.SetValue(objecto, dr.GetByte(dr.GetOrdinal(s)), null);
break;
case "char":
p.SetValue(objecto, dr.GetString(dr.GetOrdinal(s)), null);
break;
case "decimal":
p.SetValue(objecto, dr.GetDecimal(dr.GetOrdinal(s)), null);
break;
case "double":
p.SetValue(objecto, dr.GetDouble(dr.GetOrdinal(s)), null);
break;
case "single": // float
p.SetValue(objecto, dr.GetFloat(dr.GetOrdinal(s)), null);
break;
case "int32":
p.SetValue(objecto, dr.GetInt32(dr.GetOrdinal(s)), null);
break;
case "int64":
p.SetValue(objecto, dr.GetInt64(dr.GetOrdinal(s)), null);
break;
case "sbyte":
p.SetValue(objecto, dr.GetByte(dr.GetOrdinal(s)), null);
break;
case "int16":
p.SetValue(objecto, dr.GetInt16(dr.GetOrdinal(s)), null);
break;
case "uint32":
p.SetValue(objecto, dr.GetInt32(dr.GetOrdinal(s)), null);
break;
case "uint64":
p.SetValue(objecto, dr.GetInt64(dr.GetOrdinal(s)), null);
break;
case "uint16":
p.SetValue(objecto, dr.GetInt16(dr.GetOrdinal(s)), null);
break;
case "string":
p.SetValue(objecto, dr[s].ToString(), null);
break;
case "datetime":
p.SetValue(objecto, dr.GetDateTime(dr.GetOrdinal(s)), null);
break;
case "timespan":
p.SetValue(objecto, dr.GetDateTime(dr.GetOrdinal(s)), null);
break;
}
//if (p.PropertyType.Name.ToLower() == "string")
//    p.SetValue(objecto, dr.GetValue(dr.GetOrdinal(s)).ToString(), null);
//else
//    p.SetValue(objecto, dr.GetValue(dr.GetOrdinal(s)), null);
}
}
listaARegresar.Add((T)objecto);
}
dr.Close();

return listaARegresar;
}
catch (Exception ex)
{
throw;
}
finally
{
if (dr != null && !dr.IsClosed)
dr.Close();

dr = null;
}
}

public object ExecuteScalar(string procName, params SqlParameter[] parametros)
{
try
{
SqlCommand comando = this._cn.CreateCommand();
if (this._tran != null)
comando.Transaction = this._tran;
comando.CommandType = CommandType.StoredProcedure;
comando.CommandText = procName;
if (parametros != null)
{
foreach (SqlParameter p in parametros)
comando.Parameters.Add(p);
}

object resp = comando.ExecuteScalar();

return resp;
}
catch (Exception ex)
{
throw;
}
finally
{
}
}

/// <summary>
/// Regresa el número de filas afectadas en la ejecución
/// </summary>
/// <param name="procName"></param>
/// <param name="parametros"></param>
/// <returns></returns>
public int ExecuteNonQuery(string procName, params SqlParameter[] parametros)
{
try
{
SqlCommand comando = this._cn.CreateCommand();
if (this._tran != null)
comando.Transaction = this._tran;
comando.CommandType = CommandType.StoredProcedure;
comando.CommandText = procName;
if (parametros != null)
{
foreach (SqlParameter p in parametros)
comando.Parameters.Add(p);
}

int resp = comando.ExecuteNonQuery();

return resp;
}
catch (Exception ex)
{
throw;
}
finally
{
}
}

#region Metodos privados

private List<string> GetPropertyMatch(object objecto, SqlDataReader dr)
{
List<string> lProp = GetGenericPropertyList(objecto);

// Obtener una lista de nombres de campos del datareader
List<string> nombrescamposdr = new List<string>();
for (int i = 0; i < dr.FieldCount; i++)
{
nombrescamposdr.Add(dr.GetName(i));
}

// En este punto, ya tenemos las 2 listas, la lista de propiedades del objeto Genérico
// y la lista de campos que ha devuelto la ejecución de la consuta
// Ahora, hay que hacer un match, para saber cuáles serán leídas del datareader
List<string> camposMatch = new List<string>();
foreach (string s in lProp)
{
foreach (string s1 in nombrescamposdr)
{
if (s.ToLower() == s1.ToLower())
{
camposMatch.Add(s);
break;
}
}
}
return camposMatch;
}

private List<string> GetGenericPropertyList(object target)
{
List<string> result = new List<string>();

foreach (MemberInfo mi in target.GetType().GetMembers())
{
if (mi.MemberType == MemberTypes.Property)
{
PropertyInfo pi = mi as PropertyInfo;
if (pi != null)
{
result.Add(pi.Name);
}
}
}
return result;
}

#endregion
}

No voy a explicar el código porque las partes más importantes están comentadas.

La clase tienes 2 métodos auxiliares más, uno para ejecutar procesimientos escalares y uno más para ejecutar procedimientos que no devuelvan finlas.

Si tienen alguna duda del funcionamiento, sólo escriban y trataré de responderla.

Espero que este código sea de mucha ayuda, ya que a mí me ha ayudado bastante.

Bueno, que estén bien y hasta la próxima.

Anuncios
  1. Aún no hay comentarios.
  1. No trackbacks yet.

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión /  Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión /  Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión /  Cambiar )

Conectando a %s

A %d blogueros les gusta esto: