2009-09-09 4 views
5

Ma question porte sur l'état de connexion SQL, charge, etc., selon le code suivant:Y a-t-il des pièges à l'utilisation d'un type de retour IEnumerable <T> pour les données SQL?

public IEnumberable<MyType> GetMyTypeObjects() 
{ 
    string cmdTxt = "select * from MyObjectTable"; 

    using(SqlConnection conn = new SqlConnection(connString)) 
    { 
    using(SqlCommand cmd = new SqlCommand(cmdTxt, conn)) 
    { 
     conn.Open(); 
     using(SqlDataReader reader = cmd.ExecuteReader()) 
     { 
     while(reader.Read()) 
     { 
      yield return Mapper.MapTo<MyType>(reader); 
     } 
     } 
    } 
    } 
    yield break; 
} 

je peux voir ce qui est peut-être un problème s'il y a beaucoup de processus en cours d'exécution du code similaire lorsque les temps d'exécution entre les itérations de l'objet IEnumerable, car les connexions seront ouvertes plus longtemps, etc. Cependant, il semble également plausible que cela réduise l'utilisation du processeur sur le serveur SQL car il ne renvoie des données que lorsque l'objet IEnumerable est utilisé. Cela réduit également l'utilisation de la mémoire sur le client car le client doit seulement charger une instance de MyType pendant qu'il fonctionne plutôt que de charger toutes les occurrences de MyType (en itérant tout le DataReader et en retournant une liste ou quelque chose).

  • Y a-t-il des cas, vous pouvez penser où vous ne voudriez pas utiliser IEnumerable de cette manière, ou tout cas, vous pensez qu'il correspond parfaitement?

  • Quel type de charge cela met-il sur le serveur SQL?

  • Est-ce quelque chose que vous utiliseriez dans votre propre code (sauf mention de NHibernate, Subsonic, etc.)?

  • -

Répondre

2

Je ne l'utiliserais pas, car il cache ce qui se passe, et il pourrait éventuellement quitter les connexions de la base de données sans une élimination appropriée.

L'objet de connexion sera fermé lorsque vous aurez lu le dernier enregistrement, et si vous arrêtez de lire avant cela, l'objet de connexion ne sera pas éliminé. Si vous savez par exemple que vous avez toujours dix enregistrements dans un résultat et que vous avez juste une boucle qui lit ces dix enregistrements de l'énumérateur sans faire le onzième appel Read qui lit au-delà du dernier élément, la connexion n'est pas fermée correctement. De plus, si vous ne voulez utiliser qu'une partie du résultat, vous n'avez aucun moyen de fermer la connexion sans lire le reste des enregistrements.

Même les extensions internes pour les agents recenseurs peuvent provoquer ce même si vous les utilisez correctement seamlingy:

foreach (MyType item in GetMyTypeObjects().Take(10)) { 
    ... 
} 
+0

C'est un bon point, ne l'avait pas considéré non plus. Je suis tellement aveuglé par la façon dont je l'utiliserais! – scottm

+1

@Guffa: L'utilisation de méthodes d'extension telles que 'Take' ne poserait pas de problème: La méthode' GetMyTypeObjects' n'est que du sucre syntaxique qui crée un objet itérateur 'IDisposable'. 'Take' appellera la méthode' Dispose' de l'itérateur quand elle aura fini, et donc disposera de la connexion, de la commande, du lecteur etc. – LukeH

+0

@Guffa: Et il en va de même pour toutes les autres lectures partielles du résultat: Tant que vous appelez 'Dispose' lorsque vous avez terminé (ou préférez simplement tout emballer dans un bloc' using '), puis la connexion, la commande, le lecteur, etc. seront également éliminés. – LukeH

2

Je recommande contre la pré-optimisation. Dans de nombreuses situations, les connexions seront regroupées.

Je n'attends pas non plus de différence de charge sur SQL Server - la requête aura déjà été compilée, et sera en cours d'exécution.

+0

Je ne suis pas sûr ce que vous entendez par pré-optimisation (je n'essaie pas de). Je pensais que la charge serait affectée car le serveur maintiendrait la position du curseur sur plusieurs connexions à la fois. – scottm

+0

En pré-optimisant, je veux dire que vous vous inquiétez des problèmes de performances avant que votre code fonctionne. Vous pensez à des problèmes qui n'existent peut-être pas. –

+0

Je comprends. Je ne veux pas non plus me mettre dans l'embarras, avoir à revoir mes méthodes d'accès aux données et les méthodes qui les utilisent à l'avenir. – scottm

6

Ce n'est pas un modèle que je voudrais suivre. Je ne m'inquiéterais pas autant de la charge sur le serveur que des verrous. En suivant ce modèle, vous intégrez le processus de récupération des données dans votre flux logique métier, ce qui semble être une recette complète pour résoudre les problèmes. vous n'avez aucune idée de ce qui se passe du côté de l'itération, et vous vous y insérez. Récupérez vos données en une seule fois, puis autorisez le code client à les énumérer une fois que vous avez fermé le lecteur.

+0

Verrouille les objets SQL? Je n'ai pas considéré cela. Que pensez-vous de donner un indice nolock (qui ne serait viable que dans certaines circonstances)? – scottm

+1

Cela éliminerait (évidemment) le problème de verrouillage, mais vous gardez toujours le lecteur ouvert pendant une période indéfinie (éventuellement pour toujours si le code consommateur oublie de se débarrasser de l'énumérateur). Cela semble juste un risque relativement élevé de gain faible. S'il y a un besoin particulier pour cette pratique - la consommation de mémoire, je suppose, pourrait se qualifier - alors ce n'est pas tout à fait méchant, mais je n'en ferais définitivement pas une habitude et je chercherais des alternatives. –

+0

Et en aparté, vous n'êtes en aucun cas garanti "une instance de MyType". En effet, même si le code consommateur ne traite que d'une instance à la fois, le GC ne va pas les nettoyer immédiatement. Oui, vous les laisserez hors de la portée et serez admissibles à la collecte plus tôt, mais cela ne sera pas une empreinte mémoire constante. –

1

Mon inquiétude serait que vous vous mettre complètement à la merci du code client.

Vous avez déjà mentionné que le code appelant peut maintenir la connexion ouverte plus longtemps que nécessaire, mais il est également possible qu'il n'autorise jamais les objets à être fermés/éliminés.

Tant que le code client utilise foreach, using etc ou appelle explicitement la méthode Dispose du recenseur alors vous êtes bien, mais il n'y a rien pour l'arrêter de faire quelque chose comme ceci:

var e = GetMyTypeObjects().GetEnumerator(); 
e.MoveNext(); // open the connection etc 

// forget about the enumerator and go away and do something else 
// now the reader, command and connection won't be closed/disposed 
// until the GC kicks in and calls their finalisers 
Questions connexes