Soyez prudent. Vous devriez toujours une instruction à l'aide sur tout objet local qui implémente IDisposable. Cela inclut non seulement les connexions et les lecteurs, mais aussi la commande. Mais il peut être difficile parfois exactement où que l'utilisation de l'instruction va. Si vous ne faites pas attention, cela peut causer des problèmes. Par exemple, dans le code qui suit l'instruction à l'aide fermerons votre lecteur avant de vous jamais l'utiliser:
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;
}
}
}
Au lieu de cela, vous disposez de quatre options. L'un est d'attendre pour créer le bloc à l'aide jusqu'à ce que vous appelez la fonction:
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())
{
//...
}
}
Bien sûr, vous avez toujours prudent avec votre connexion et il cela signifie se rappeler d'écrire un bloc à l'aide partout où vous utilisez la fonction.
Option deux est tout simplement de traiter les résultats de la requête dans la méthode elle-même, mais qui brise la séparation de votre couche de données du reste du programme. Une troisième option consiste pour votre fonction MyQuery() à accepter un argument de type Action que vous pouvez appeler dans la boucle while (rdr.Read()), mais c'est juste gênant.
Je préfère généralement l'option quatre: tourner le lecteur de données dans un IEnumerable, comme ceci:
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;
}
}
}
Maintenant, tout sera fermé correctement, et le code qui le gère est en un seul endroit. Vous obtenez également un bon bonus: les résultats de votre requête fonctionneront bien avec tous les opérateurs linq.
Enfin, quelque chose de nouveau que je joue avec pour la prochaine fois que je reçois de construire un tout nouveau projet qui combine le IEnumerable avec passage dans un argument délégué:
//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;
}
}
}
Et puis je vais l'utiliser dans la couche de données comme ceci:
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;
}
);
}
Vous ne devriez pas utiliser _toujours_ une déclaration 'using' - un cas où des problèmes pourraient survenir lors de l'appel est' Command.ExecuteReader'. Cette méthode peut générer des exceptions, et si c'est le cas, elles ne seront pas gérées correctement et votre logiciel échouera. Mieux vaut gérer les exceptions possibles et appeler manuellement 'Dispose()' ou son équivalent. –
Conrad