2

I have a stored procedure that will return a column of 0 or more rows or account numbers.

If 0 rows I of course don't have to do anything but if 1 or more are returned I need to throw those account numbers into a csv file.

The data returned will look like this:

100000
200000
286598

Here is my method:

private static void ThirtyMinuteUpload(DateTime today)
{
    using (SqlConnection connection = new SqlConnection(connString))
    {
        using (SqlCommand command = new SqlCommand("mySP", connection))
        {
            command.CommandType = CommandType.StoredProcedure;

            connection.Open();
            if (command.ExecuteReader().HasRows)
            {
                // Create csv
            }
        }
    }
}

I need to throw those into a csv from my reader and name the file like this using the today variable passed in:

exlcusion_mmddyyhhmmss.csv 

I have never worked with file creation and for now it can just be saved to my desktop. Is something that is easy to do?

1
  • What type of output do you except? All in one row(Example A1:1000,B1:2000,C13000 etc...) Or all in one column(A1:1000,A2:2000,A3:3000 etc...)? Commented Nov 18, 2013 at 20:37

3 Answers 3

1

I would do something like this:

Update: Fixed last comma problem.

using (SqlDataReader dr = command.ExecuteReader()) 
{
   if (dr.HasRows()) 
   {           
      string dateFormatted = today.ToString("MMddyyhhmmss");
      string path = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
      var writer = new StreamWriter(String.Format("{0}\exclusion_{1}.csv",
         path, dateFormatted);

      var cont = true;
      while (cont) 
      {
         // Grab the accountid before we read ahead
         var accId = dr["accountid"];

         // Read ahead to check if we've read the last record
         cont = dr.Read();

         // Last record, don't add comma
         if (!cont) 
         {
            writer.Write(accId); 
         }
         else 
         { 
            writer.Write(accId + ",");
         }
      }
   }
}
Sign up to request clarification or add additional context in comments.

9 Comments

I am using this writer.WriteLine("{0},", reader["accountid"]); Will it be a problem if it always adds a comma to the last record? 100000,200000,
continue is a keyword in C#, you should probably use @continue instead.
I had to surround the while (cont) with a while (reader.Read()) or it would error out. But with that addition it works perfectly thanks again.
I suggest at least in answers (but generally for all code IMO) that you don't use var when it is not clear which type it is. For instance we cannot immediately read without knowing which type dr will be in var dr = command.ExecuteReader().
If there's only one column, why use any commas at all? There are none in the sample.
|
0
private static void ThirtyMinuteUpload(DateTime today)
{
    using (var cn = new SqlConnection(connString))
    using (var cmd = new SqlCommand("mySP", cn))
    {
        cmd.CommandType = CommandType.StoredProcedure;
        cn.Open();

        using (var rdr = cmd.ExecuteReader())
        {
            if (!rdr.HasRows) return;

            var fileName = string.Format("{0}{1}exclusion_{2:MMddyyHHmmss}.csv",
                Environment.GetFolderPath(Environment.SpecialFolder.Desktop),
                Path.PathSeparator,
                today);

            using (var writer = new StreamWriter(fileName))
            {
                while (rdr.Read())
                {
                    writer.WriteLine(rdr.GetString(0));
                }
            }
        }
    }
}

Comments

0

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.