2009-09-09 16 views
5

Mi pregunta tiene que ver el estado de conexión de SQL, carga, etc. basado en el siguiente código:¿Hay algún inconveniente al utilizar un tipo de retorno IEnumerable <T> para datos 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; 
} 

puedo ver esta siendo posiblemente un problema si hay muchos procesos que se ejecutan código similar con largos tiempos de ejecución entre las iteraciones de el objeto IEnumerable, porque las conexiones estarán abiertas durante más tiempo, etc. Sin embargo, también parece plausible que esto reduzca el uso de CPU en el servidor SQL porque solo devuelve datos cuando se utiliza el objeto IEnumerable. También reduce el uso de memoria en el cliente porque el cliente solo tiene que cargar una instancia de MyType mientras funciona en lugar de cargar todas las apariciones de MyType (iterando a través de todo el DataReader y devolviendo una lista o algo así).

  • ¿Hay casos en que se pueda imaginar en el que no se desea utilizar IEnumerable de este modo, o cualquier instancia que creo que encaja perfectamente?

  • ¿Qué tipo de carga no someten quizás en el servidor SQL?

  • ¿Es esto algo que usaría en su propio código (salvo mención alguna de NHibernate, subsónico, etc)?

  • -

Respuesta

2

No lo usaría, porque oculta lo que está sucediendo, y posiblemente podría dejar las conexiones de la base de datos sin su eliminación adecuada.

El objeto de conexión se cerrará cuando haya leído más allá del último registro, y si deja de leer antes, el objeto de conexión no se eliminará. Si, por ejemplo, sabe que siempre tiene diez registros en un resultado y solo tiene un bucle que lee esos diez registros del enumerador sin realizar la undécima llamada de Lectura que se lee más allá del último elemento, la conexión no se cierra correctamente. Además, si solo desea usar parte del resultado, no tiene forma de cerrar la conexión sin leer el resto de los registros.

Incluso el construido en extensiones para los empadronadores pueden causar esto incluso si los usa correctamente seamlingy:

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

Ese es un buen punto, tampoco lo había considerado. ¡Estoy tan cegado por la forma en que lo usaría! – scottm

+1

@Guffa: Usar métodos de extensión como 'Take' no sería un problema: el método' GetMyTypeObjects' es simplemente azúcar sintáctico que crea un objeto iterador 'IDisposable'. 'Take' llamará al método' Dispose' del iterador cuando esté listo, y que luego eliminará la conexión, el comando, el lector, etc. – LukeH

+0

@Guffa: Y lo mismo se aplica a cualquier otra lectura parcial del conjunto de resultados: siempre y cuando usted llame a 'Dispose' cuando haya terminado (o preferiblemente solo envuelva todo en un bloque 'using'), luego la conexión, el comando, el lector, etc. también serán eliminados. – LukeH

2

me recomiendan en contra de pre-optimización. En muchas situaciones, las conexiones se agruparán.

también no esperan ninguna diferencia en la carga en SQL Server - ya habrá sido compilado la consulta, y va a correr.

+0

No estoy seguro lo que quieres decir con optimización previa (no estoy tratando de hacerlo). Estaba pensando que la carga se vería afectada porque el servidor mantendría la posición del cursor en más conexiones a la vez. – scottm

+0

Al optimizar previamente, me refiero a que está preocupado por problemas de rendimiento antes de que funcione su código. Estás pensando en problemas que pueden no existir. –

+0

Entiendo. Tampoco quiero codificarme en una esquina, tener que rediseñar mis métodos de acceso a los datos y los métodos que los utilizan en el futuro. – scottm

6

Este no es un patrón que seguiría. No me preocuparía tanto por la carga en el servidor como lo haría con los bloqueos. Seguir este patrón integra el proceso de recuperación de datos en el flujo lógico de su negocio, y eso parece una receta completa para problemas; no tienes idea de lo que sucede en el lado de la iteración, y te estás insertando en él. Recupere sus datos de una sola vez, luego permita que el código del cliente lo enumere una vez que haya cerrado el lector.

+0

¿Bloqueos en los objetos SQL? No lo consideré. ¿Qué piensas sobre dar una pista de nolock (que solo sería viable bajo ciertas circunstancias)? – scottm

+1

Eso eliminaría (obviamente) el problema de bloqueo, pero aún mantiene el lector abierto durante un período de tiempo indefinido (posiblemente para siempre si el código de consumo olvida deshacerse del enumerador). Simplemente parece un riesgo relativamente alto de baja ganancia. Si hay una necesidad particular para esta práctica, el consumo de memoria, supongo, podría calificar, entonces no es completamente malvado, pero definitivamente no lo convertiría en un hábito y buscaría alternativas. –

+0

Y, aparte, de ninguna manera se garantiza "una instancia de MyType". De hecho, incluso si el código de consumo solo trata con una instancia a la vez, el GC no los va a limpiar inmediatamente. Sí, los mantendrá fuera del alcance y será elegible para la recolección antes, pero no será una huella de memoria constante. –

1

Mi preocupación sería que se está poniendo a merced del código del cliente.

Ya ha mencionado que el código de llamada puede mantener la conexión abierta más de lo estrictamente necesario, pero también existe la posibilidad de que nunca permita que los objetos se cierren/desechen.

En tanto que el código de cliente utiliza foreach, using etc o explícitamente llama al método del empadronador Dispose entonces está bien, pero no hay nada para detenerlo haciendo algo como esto:

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 
Cuestiones relacionadas