Here is a more abstract answer to your question. Rather than working with a SqlDataReader, I wrote a generic function that will write the data from any implementer of System.Data.IDataReader (System.Data.SqlClient.SqlDataReader is just one class that does) to any System.IO.Stream, which includes files (using FileStream).
/// <summary>
/// Writes the data from a given <see cref="IDataReader"/> <paramref name="reader"/> to the <paramref name="output"/> <see cref="Stream"/>.
/// There are optional parameters for writing a header, specifying the encoding, the buffer size, and whether or not the stream should be
/// closed when we're done reading.
/// </summary>
/// <param name="reader">Any object which implements <see cref="IDataReader"/>-- most likely a <see cref="System.Data.SqlClient.SqlDataReader"/>.</param>
/// <param name="output">The stream to output the CSV contents to.</param>
/// <param name="writeHeader">When true, a header is written using the column names.</param>
/// <param name="encoding">Optional parameter (defaulting to UTF8 without BOM) denoting how the data should be encoded.</param>
/// <param name="bufferSize">Optional parameter (defaulting to 1KB) which is used as a buffer for writing the data.</param>
/// <param name="closeOutput">Optional parameter which, when true, closes the <paramref name="output"/> <see cref="Stream"/> after we're doing writing.</param>
private static void WriteCsv(IDataReader reader, Stream output, bool writeHeader = true, Encoding encoding = null, int bufferSize = 1024, bool closeOutput = false)
{
// If no encoding is provided, use the same one the StreamWriter defaults to.
if (encoding == null)
encoding = new UTF8Encoding(false, true);
// Create a new writer to our CSV file.
using (var writer = new StreamWriter(output, encoding, bufferSize, !closeOutput))
{
// This will create an enumerable with every integer between 0 and FieldCount-1.
// Allows us to do a concise for loop and use String.Join to handle the comma placement.
var indices = Enumerable.Range(0, reader.FieldCount);
// Keep looping as long as their are rows returned by the reader.
while (reader.Read())
{
// Write a header with the names of each column.
if (writeHeader)
{
writer.WriteLine(String.Join(",", indices.Select(i => reader.GetName(i) ?? ("column" + i))));
writeHeader = false;
}
// Write the value of each field by its string representation separated by a comma.
writer.WriteLine(String.Join(",", indices.Select(i => (reader.IsDBNull(i) ? null : reader.GetString(i)) ?? "")));
}
}
}
This function gives you a great deal of control of some specifics like encoding and what kind of stream you're writing to (you could be writing to an HTTP response or a regular file, doesn't matter). If you get more complex data that you want to output into CSV files, I recommend reading this article on CSV "standards."
This writer is naive-- it just writes the raw data it read from the IDataReader. If your content has a newline, carriage return, or a comma, it could confuse whatever will ultimately consume the output of your program. I would write a CsvEncode function that you feed each value into and it properly encodes it according to the rules listed in the article above.
This is just an example, not code you should actually use:
private static string CsvEncode(string value)
{
// Handle commas within values.
if (value.Contains(','))
{
// Strim so we get rid of beginning and trailing whitespaces we'd usually ignore.
value = value.Trim();
// If the value is already wrapped with quotation marks but has quotation marks within as well,
if (value.StartsWith("\"") && value.EndsWith("\"") && value.IndexOf('\"', 1, value.Length-2) > 0)
value = "\"" + value.Substring(1, value.Length - 2).Replace("\"", "\"\"") + "\"";
else if (value.Contains("\"")) // Replace all quotations with two quotations, then wrap the final result.
value = "\"" + value.Replace("\"", "\"\"") + "\"";
}
return value;
}
And you would just update WriteCsv so when it's writing the values of the row, you invoke CsvEncode, something like (just an example):
// Write the value of each field by its string representation separated by a comma.
writer.WriteLine(String.Join(",", indices.Select(i => CsvEncode(reader.IsDBNull(i) ? "" : reader.GetString(i) ?? ""))));
And just to be thorough, this is how you would call it:
using (var reader = command.ExecuteReader())
{
if (!reader.HasRows)
return; // Nothing to do.
// You want it on the desktop? We'll put it on the desktop.
var filePath = string.Format("{0}{1}exclusion_{2:MMddyyHHmmss}.csv",
Environment.GetFolderPath(Environment.SpecialFolder.Desktop),
Path.PathSeparator,
today);
// Pass in the reader we got from executing the command. File.Create will replace any
// existing files. closeOutput is true because we do not keep a reference to the FileStream.
WriteCsv(reader, File.Create(filePath), closeOutput: true);
}