Better way to consume an IEnumerable<IEnumerable<string>>
I'm calling a stored procedure with multiple results sets (always 2) and writing the results to seperate files (in pipe delimited format). I can't split the results sets into seperate stored procs. I'm using IDataReader and IEnumerable to keep as little in memor开发者_运维问答y in the process.
Is there a cleaner way of consuming my IEnumerable<IEnumerable<string>>
than using GetEnumerator/MoveNext/Current to get to the inner IEnumerable<string>
to pass to 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);
}
}
Why not a foreach
loop? That is the most basic.
Personally, I would just use a foreach loop with a separate variable for tracking which file to write to, something like:
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;
}
}
You could use SelectMany()
to flatten the enumeration, since you are only interested in the values themselves.
Edit:
As per comment SelectMany()
is unsuitable given the use case, so best is to use a foreach
loop:
var reader = GetLines();
int index = 0;
foreach(var lines in reader)
File.AppendAllLines(string.Format("file{0}.dat", index++%2 + 1), lines);
Maybe turn the result of GetLines()
into an array and access it by index (since you said there will always be 2 result sets)?
public void Execute()
{
IEnumerable<string>[] rows = GetLines().ToArray();
File.AppendAllLines("file1.dat", rows[0]);
File.AppendAllLines("file2.dat", rows[1]);
}
I would just change my GetLines
method to the following
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();
}
}
}
Having it return an IEnumerable<IEnumerable<string>>
will produce an unnecessary burden to consumers of the API. My guess is they will all just prefer to see this as an IEnumerable<string>
.
foreach is implicitly supported by IEnumerable. So:
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);
}
}
Becomes:
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);
}
You can zip your results into a Tuple<>
with your filenames like so:
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);
}
}
Of course, I'm pretty sure this is going to throw as soon as you return a different number of rows, but it will do what you're looking for.
精彩评论