Ten cuidado aquí. Debe siempre tener una instrucción using en cualquier objeto local que implemente IDisposable. Eso incluye no solo conexiones y lectores, sino también el comando. Pero puede ser difícil a veces exactamente donde va que usa la instrucción. Si no tiene cuidado, puede causar problemas. Por ejemplo, en el código que sigue a la instrucción using cerrará su lector antes de que usted llegue a usarlo:
DataReader MyQuery()
{
string sql="some query";
using (var cn = new SqlConnection("connection string"))
using (var cmd = new SqlCommand(sql, cn))
{
cn.Open();
using (var rdr = cmd.ExecuteReader())
{
return rdr;
}
}
}
En su lugar, tienen cuatro opciones. Una de ellas es que esperar para crear el bloque usando hasta que llame a la función:
DataReader MyQuery()
{
string sql="some query";
using (var cn = new SqlConnection("connection string"))
using (var cmd = new SqlCommand(sql, cn))
{
cn.Open();
return cmd.ExecuteReader();
}
}
using (var rdr = MyQuery())
{
while (rdr.Read())
{
//...
}
}
Por supuesto, usted todavía tiene que cuidado con su conexión allí y significa recordar a escribir un bloque usando todas partes se utiliza la función.
La opción dos solo procesa los resultados de la consulta en el método en sí, pero eso rompe la separación de su capa de datos del resto del programa. Una tercera opción es que su función MyQuery() acepte un argumento de tipo Acción que puede llamar dentro del ciclo while (rdr.Read()), pero eso es incómodo.
por lo general prefieren la opción de cuatro: gire el lector de datos en un IEnumerable, así:
IEnumerable<IDataRecord> MyQuery()
{
string sql="some query";
using (var cn = new SqlConnection("connection string"))
using (var cmd = new SqlCommand(sql, cn))
{
cn.Open();
using (var rdr = cmd.ExecuteReader())
{
while (rdr.Read())
yield return rdr;
}
}
}
Ahora todo se cerrará correctamente y el código que se encarga de todo está en un solo lugar. También obtienes una buena bonificación: los resultados de tu consulta funcionarán bien con cualquiera de los operadores de linq.
Por último, algo nuevo que estoy jugando con la próxima vez que llego a construir un proyecto completamente nuevo que combina el IEnumerable con el paso en un argumento delegado:
//part of the data layer
private static IEnumerable<IDataRecord> Retrieve(string sql, Action<SqlParameterCollection> addParameters)
{
//DL.ConnectionString is a private static property in the data layer
// depending on the project needs, it can be implementing to read from a config file or elsewhere
using (var cn = new SqlConnection(DL.ConnectionString))
using (var cmd = new SqlCommand(sql, cn))
{
addParameters(cmd.Parameters);
cn.Open();
using (var rdr = cmd.ExecuteReader())
{
while (rdr.Read())
yield return rdr;
}
}
}
Y luego lo usaré dentro de la capa de datos de esta manera:
public IEnumerable<IDataRecord> GetFooChildrenByParentID(int ParentID)
{
//I could easily use a stored procedure name instead, and provide overloads for commandtypes.
return Retrieve(
"SELECT c.*
FROM [ParentTable] p
INNER JOIN [ChildTable] c ON c.ParentID = f.ID
WHERE f.ID= @ParentID", p =>
{
p.Add("@ParentID", SqlDbType.Int).Value = ParentID;
}
);
}
No debe _siempre_ usar una instrucción 'using' - un caso donde pueden surgir problemas es cuando se llama' Command.ExecuteReader'. Este método puede arrojar excepciones, y si lo hace, no se manejarán correctamente y su software fallará. Es mejor manejar las posibles excepciones y llamar manualmente a 'Dispose()' o su equivalente. –
Conrad