2011-03-29 21 views
5

Estoy llamando a un procedimiento almacenado con varios conjuntos de resultados (siempre 2) y escribiendo los resultados para separar los archivos (en formato delimitado por tuberías). No puedo dividir los conjuntos de resultados en procesos almacenados separados. Estoy usando IDataReader e IEnumerable para mantener tan poca memoria en el proceso.Mejor forma de consumir un IEnumerable <IEnumerable <string>>

¿Hay alguna manera más clara de consumir mi IEnumerable<IEnumerable<string>> que usar GetEnumerator/MoveNext/Current para obtener el IEnumerable<string> interno para pasar a File.AppendAllLines?

public void Execute() 
    { 
     var reader = GetLines(); 

     using (var enumerator = reader.GetEnumerator()) 
     { 
      enumerator.MoveNext(); 

      File.AppendAllLines("file1.dat", enumerator.Current); 
      enumerator.MoveNext(); 

      File.AppendAllLines("file2.dat", enumerator.Current); 
     } 
    } 

    public IEnumerable<IEnumerable<string>> GetLines() 
    { 
     Database db = DatabaseFactory.CreateDatabase("connectionStringKey"); 
     using (var command = db.GetStoredProcCommand("getdata_sp")) 
     { 
      var reader = db.ExecuteReader(command); 
      yield return GetInnerEnumerable(reader); 
      reader.NextResult(); 
      yield return GetInnerEnumerable(reader); 
     } 
    } 

    private IEnumerable<string> GetInnerEnumerable(IDataReader reader) 
    { 
     while (reader.Read()) 
     { 
      object[] rowValues = new object[reader.FieldCount]; 
      reader.GetValues(rowValues); 
      yield return String.Join("|", rowValues); 
     } 
    } 
+0

Quizás no estaba claro al nombrar el método GetLines() - devuelve IEnumerable >. Debería haberlo llamado GetReaders() – foson

Respuesta

5

¿Por qué no un foreach loop? Eso es lo más básico.

+0

Necesito pasar los nombres de los archivos – foson

+0

Mire más de cerca el código. Tendría que escribir dos foreachs con uno de ellos teniendo todos los recuentos desiguales y el otro el recuento. – skarmats

4

Personalmente, sólo tiene que utilizar un bucle foreach con una variable independiente para el seguimiento de qué archivo para escribir en, algo así como:

public void Execute() 
{ 
    var reader = GetLines(); 

    int i = 0; 
    foreach (var inner in reader) 
    { 
     if (i % 2 == 0) 
      File.AppendAllLines("file1.dat", inner); 
     else 
      File.AppendAllLines("file2.dat", inner); 
     ++i; 
    } 
} 
+0

Gracias.Sé que esto es subjetivo, pero no veo un foreach y un contador para ser mucho más limpio/más comprensible que el método GetEnumerator/MoveNext/Current. Creo que esperaba un método de extensión "Pop"/"Siguiente" (básicamente un Enumerador), tal vez algo nuevo de Rx. – foson

2

Tal vez gire el resultado de GetLines() en una matriz y acceder a ella por el índice (ya que dijiste que siempre habrá 2 conjuntos de resultados)?

public void Execute() 
{ 
    IEnumerable<string>[] rows = GetLines().ToArray(); 

    File.AppendAllLines("file1.dat", rows[0]); 
    File.AppendAllLines("file2.dat", rows[1]); 
} 
+0

Buena idea, pero cuando la ejecuté, no funciona como esperaba. Como se llama a NextResult cuando se llama a .ToArray(), cuando se itera el primer Enumerable (filas [0]), obtenemos los resultados de las filas [1] – foson

+0

@foson: Ah, no lo consideré (lo olvidé) sobre 'NextResult()' que aparece entre los rendimientos de rendimiento). – BoltClock

4

Usted podría utilizar SelectMany() para aplanar la enumeración, ya que sólo está interesado en los propios valores.

Editar:

Según comentario SelectMany() es inadecuada dado el caso de uso, así que lo mejor es utilizar un bucle foreach:

var reader = GetLines(); 
int index = 0; 
foreach(var lines in reader) 
    File.AppendAllLines(string.Format("file{0}.dat", index++%2 + 1), lines); 
+0

líneas van a archivos separados, así que no creo que pueda aplanar – foson

+0

@foson: tiene razón, actualicé mi respuesta – BrokenGlass

1

foreach es apoyado implícitamente por IEnumerable. Por lo tanto:

public void Execute() 
{ 
    var reader = GetLines(); 

    using (var enumerator = reader.GetEnumerator()) 
    { 
     enumerator.MoveNext(); 

     File.AppendAllLines("file1.dat", enumerator.Current); 
     enumerator.MoveNext(); 

     File.AppendAllLines("file2.dat", enumerator.Current); 
    } 
} 

se convierte en:

public void Execute() 
{ 
    var reader = GetLines(); 

    int index = 0; 

    foreach (string line in reader) 
    { 
     if ((index % 2) == 0) 
      File.AppendAllLines("file1.dat", line); 

     else 
      File.AppendAllLines("file2.dat", line); 

     index++; 
    } 
} 

O:

public void Execute() 
{ 
    var reader = GetLines(); 

    var evenLines = reader.Where((str, i) => i % 2 == 0); 
    var oddLines = reader.Where((str, i) => i % 2 != 0); 

    foreach (string line in evenLines) 
     File.AppendAllLines("file1.dat", line); 

    foreach (string line in oddLines) 
     File.AppendAllLines("file2.dat", line); 
} 
+0

los 2 foreach causan que la sp se ejecute dos veces – foson

2

me acaba de cambiar mi método GetLines a la siguiente

public IEnumerable<string> GetLines() 
{ 
    Database db = DatabaseFactory.CreateDatabase("connectionStringKey"); 
    using (var command = db.GetStoredProcCommand("getdata_sp")) 
    { 
     var reader = db.ExecuteReader(command); 
     for (var i = 0; i < 2; i++) 
     { 
      foreach(var cur in GetInnerEnumerable(reader)) 
      { 
      yield return cur; 
      } 
      reader.NextResult(); 
     } 
    } 
} 

Tener que devolver un IEnumerable<IEnumerable<string>> w producir una carga innecesaria para los consumidores de la API. Supongo que todos preferirán ver esto como IEnumerable<string>.

+0

Esto es solo un código interno de la aplicación, no se consume por cualquier otro código. ¿Cómo sabría que mi primer lector ha terminado y debería comenzar a escribir los resultados en el segundo archivo? – foson

+0

@foson tu pregunta específicamente dijo que solo había 2 – JaredPar

+0

Correcto, pero ¿cómo sabe el método Execute qué valores de GetLines() corresponden al primer lector que se guarda en el primer archivo y cuáles son del segundo lector? – foson

0

Puede comprimir sus resultados en un Tuple<> con los nombres de archivo de esta manera:

using System.Linq; 
using FileZip = System.Tuple< 
    System.String, 
    System.Collections.Generic.IEnumerable< 
     System.String>>; 

public void Execute() 
{ 
    var files = new string[] { "file1.dat", "file2.dat" }; 
    var results = GetLines(); 

    foreach (var file in files.Zip(results, (f, r) => new FileZip(f, r))) 
    { 
     File.AppendAllLines(file.Item1, file.Item2); 
    } 
} 

Por supuesto, estoy bastante seguro de que esto va a lanzar tan pronto como regrese un número diferente de filas, pero hará lo que estás buscando.

Cuestiones relacionadas