Лучший способ использовать IEnumerable ‹IEnumerable ‹string››

Я вызываю хранимую процедуру с несколькими наборами результатов (всегда 2) и записываю результаты в отдельные файлы (в формате с разделителями каналов). Я не могу разбить наборы результатов на отдельные хранимые процедуры. Я использую IDataReader и IEnumerable, чтобы в процессе было как можно меньше памяти.

Есть ли более чистый способ использования моего IEnumerable<IEnumerable<string>>, чем использование GetEnumerator / MoveNext / Current для перехода к внутреннему IEnumerable<string> для перехода к 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);
        }
    }

person foson    schedule 29.03.2011    source источник
comment
Возможно, я не понял, назвав метод GetLines () - он возвращает IEnumerable ‹IEnumerable ‹string››. Надо было назвать это GetReaders ()   -  person foson    schedule 30.03.2011


Ответы (7)


Почему не foreach цикл? Это самое основное.

person Daniel A. White    schedule 29.03.2011
comment
Мне нужно передать имена файлов - person foson; 30.03.2011
comment
Присмотритесь к коду поближе. Ему придется написать два foreach, один из которых будет принимать все нечетные подсчеты, а другой - четные. - person skarmats; 30.03.2011

Лично я бы просто использовал цикл foreach с отдельной переменной для отслеживания, в какой файл писать, например:

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;
    }
}
person Reed Copsey    schedule 29.03.2011
comment
Спасибо. Я знаю, что это субъективно, но я не вижу, чтобы foreach и счетчик были намного чище / понятнее, чем метод GetEnumerator / MoveNext / Current. Думаю, я надеялся на метод расширения Pop / Next (в основном Enumerator), возможно, что-то новое от Rx. - person foson; 30.03.2011

Вы можете использовать SelectMany() для сглаживания перечисления, поскольку вас интересуют только сами значения.

Изменить:

Согласно комментарию SelectMany() не подходит для данного варианта использования, поэтому лучше всего использовать цикл foreach:

var reader = GetLines();
int index = 0;
foreach(var lines in reader)
    File.AppendAllLines(string.Format("file{0}.dat", index++%2 + 1), lines);
person BrokenGlass    schedule 29.03.2011
comment
строки идут в отдельные файлы, поэтому я не думаю, что могу сгладить - person foson; 30.03.2011

Может быть, превратить результат GetLines() в массив и получить к нему доступ по индексу (поскольку вы сказали, что всегда будет 2 набора результатов)?

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

    File.AppendAllLines("file1.dat", rows[0]);
    File.AppendAllLines("file2.dat", rows[1]);
}
person BoltClock    schedule 29.03.2011
comment
Хорошая идея, но когда я ее запустил, она работает не так, как ожидалось. Поскольку NextResult вызывается при вызове .ToArray (), при итерации первого Enumerable (rows [0]) мы получаем результаты из rows [1] - person foson; 30.03.2011
comment
@foson: Ах, я не учел этого (я забыл о появлении NextResult() между доходами). - person BoltClock; 30.03.2011

Я бы просто изменил свой метод GetLines на следующий

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();
        }
    }
}

Если он вернет IEnumerable<IEnumerable<string>>, это создаст ненужную нагрузку для потребителей API. Я предполагаю, что все они просто предпочтут видеть это как IEnumerable<string>.

person JaredPar    schedule 29.03.2011
comment
Это просто некоторый внутренний код приложения, не используемый никаким другим кодом. Как я узнаю, что мой первый ридер закончился и я должен начать записывать результаты во второй файл? - person foson; 30.03.2011
comment
@foson в вашем вопросе конкретно сказано, что их всего 2 - person JaredPar; 30.03.2011
comment
Верно, но как метод Execute узнает, какие значения из GetLines () от первого считывателя должны быть сохранены в первом файле, а какие - от 2-го считывателя? - person foson; 30.03.2011

Foreach неявно поддерживается IEnumerable. Так:

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 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++;
    }
}

Or:

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);
}
person Josh G    schedule 29.03.2011
comment
2 foreach вызывают выполнение sp дважды - person foson; 30.03.2011

Вы можете заархивировать результаты в Tuple<> с такими именами файлов:

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);
     }
}

Конечно, я почти уверен, что это произойдет, как только вы вернете другое количество строк, но он сделает то, что вы ищете.

person Kyle Sletten    schedule 03.11.2012