Convert IEnumerable to CSV

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP





.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;







up vote
5
down vote

favorite
1












I have an IEnumerable of a view model that I need to convert to CSV and send back as a file response. I have written it out quite manually and I'm just seeking code review and suggestions on a more elegant solution, or alternatively if this looks acceptable, confirmation of that:



 public ActionResult ExportBasicReads(string nmi, DateTime fromDate, DateTime toDate)

var reads = _dataService.GetBasicReads(nmi, fromDate, toDate)
.Where(r => r.CurrentReadQuality != ReadQuality.Estimate)
.OrderBy(r => r.CurrentReadDate)
.ThenBy(r => r.DeviceKey)
.ThenBy(r => r.ValidationCode);

StringBuilder csv = new StringBuilder();

csv.AppendLine("Read Date,Device : Suffix,Read,Quality,Tran Code,Service Order,Previous Read Date,Previous Read,Previous Quality," +
"Previous Tran Code,Dial Diff,Quantity,UOM,Dial Factor,Direction,Validation,From Date,To Date,Kwh,Avg Daily Load,Profile Name," +
"Profle Area,Reading Timestamp,Update Date,ChangedBy,ChangeType,ChangeComment");

reads.ForEach(x => csv.AppendLine(
string.Format("0,1 : 2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22," +
"23,24,25,26,27",
x.CurrentReadDate,
x.DeviceKey,
x.Suffix,
x.CurrentReadRawValue,
x.CurrentReadQualityDesc,
x.CurrentReadTransactionCode,
x.CurrentServiceOrder,
x.PreviousReadDate,
x.PreviousReadRawValue,
x.PreviousReadQualityDesc,
x.PreviousReadTransactionCode,
x.DialDifference,
x.Quantity,
x.UOMDesc,
x.Multiplier,
x.DirectionDesc,
x.ValidationDesc,
x.EffectiveFromDateTime,
x.EffectiveToDateTime,
x.Kwh,
x.AverageDailyLoad,
x.ProfileName,
x.ProfileArea,
x.EffectiveFromDateTime,
x.UpdateTimestamp,
x.ChangedBy,
x.ChangeType,
x.ChangeComment
)
));

return File(Encoding.ASCII.GetBytes(csv.ToString()), "text/csv", "BasicReadsExport.csv");







share|improve this question

















  • 2




    You can use string interpolation instead of string.Format()?
    – vasily.sib
    Jun 22 at 5:37






  • 2




    Looks pretty clean to me.
    – paparazzo
    Jun 22 at 12:21






  • 1




    No need to reinvent - the popular ServiceStack.Text NuGet package has an extension method called .ToCsv(): docs.servicestack.net/csv-format
    – Jesse C. Slicer
    Jun 22 at 14:35






  • 1




    the string.Format is awfully hard to follow given the large number of parameters. I would suggest something like $"x.CurrentReadDate,x.DeviceKey" .... This would also help avoid errors due to matching up parameter indexes if this code was later modified.
    – Zer0
    Jun 23 at 4:09

















up vote
5
down vote

favorite
1












I have an IEnumerable of a view model that I need to convert to CSV and send back as a file response. I have written it out quite manually and I'm just seeking code review and suggestions on a more elegant solution, or alternatively if this looks acceptable, confirmation of that:



 public ActionResult ExportBasicReads(string nmi, DateTime fromDate, DateTime toDate)

var reads = _dataService.GetBasicReads(nmi, fromDate, toDate)
.Where(r => r.CurrentReadQuality != ReadQuality.Estimate)
.OrderBy(r => r.CurrentReadDate)
.ThenBy(r => r.DeviceKey)
.ThenBy(r => r.ValidationCode);

StringBuilder csv = new StringBuilder();

csv.AppendLine("Read Date,Device : Suffix,Read,Quality,Tran Code,Service Order,Previous Read Date,Previous Read,Previous Quality," +
"Previous Tran Code,Dial Diff,Quantity,UOM,Dial Factor,Direction,Validation,From Date,To Date,Kwh,Avg Daily Load,Profile Name," +
"Profle Area,Reading Timestamp,Update Date,ChangedBy,ChangeType,ChangeComment");

reads.ForEach(x => csv.AppendLine(
string.Format("0,1 : 2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22," +
"23,24,25,26,27",
x.CurrentReadDate,
x.DeviceKey,
x.Suffix,
x.CurrentReadRawValue,
x.CurrentReadQualityDesc,
x.CurrentReadTransactionCode,
x.CurrentServiceOrder,
x.PreviousReadDate,
x.PreviousReadRawValue,
x.PreviousReadQualityDesc,
x.PreviousReadTransactionCode,
x.DialDifference,
x.Quantity,
x.UOMDesc,
x.Multiplier,
x.DirectionDesc,
x.ValidationDesc,
x.EffectiveFromDateTime,
x.EffectiveToDateTime,
x.Kwh,
x.AverageDailyLoad,
x.ProfileName,
x.ProfileArea,
x.EffectiveFromDateTime,
x.UpdateTimestamp,
x.ChangedBy,
x.ChangeType,
x.ChangeComment
)
));

return File(Encoding.ASCII.GetBytes(csv.ToString()), "text/csv", "BasicReadsExport.csv");







share|improve this question

















  • 2




    You can use string interpolation instead of string.Format()?
    – vasily.sib
    Jun 22 at 5:37






  • 2




    Looks pretty clean to me.
    – paparazzo
    Jun 22 at 12:21






  • 1




    No need to reinvent - the popular ServiceStack.Text NuGet package has an extension method called .ToCsv(): docs.servicestack.net/csv-format
    – Jesse C. Slicer
    Jun 22 at 14:35






  • 1




    the string.Format is awfully hard to follow given the large number of parameters. I would suggest something like $"x.CurrentReadDate,x.DeviceKey" .... This would also help avoid errors due to matching up parameter indexes if this code was later modified.
    – Zer0
    Jun 23 at 4:09













up vote
5
down vote

favorite
1









up vote
5
down vote

favorite
1






1





I have an IEnumerable of a view model that I need to convert to CSV and send back as a file response. I have written it out quite manually and I'm just seeking code review and suggestions on a more elegant solution, or alternatively if this looks acceptable, confirmation of that:



 public ActionResult ExportBasicReads(string nmi, DateTime fromDate, DateTime toDate)

var reads = _dataService.GetBasicReads(nmi, fromDate, toDate)
.Where(r => r.CurrentReadQuality != ReadQuality.Estimate)
.OrderBy(r => r.CurrentReadDate)
.ThenBy(r => r.DeviceKey)
.ThenBy(r => r.ValidationCode);

StringBuilder csv = new StringBuilder();

csv.AppendLine("Read Date,Device : Suffix,Read,Quality,Tran Code,Service Order,Previous Read Date,Previous Read,Previous Quality," +
"Previous Tran Code,Dial Diff,Quantity,UOM,Dial Factor,Direction,Validation,From Date,To Date,Kwh,Avg Daily Load,Profile Name," +
"Profle Area,Reading Timestamp,Update Date,ChangedBy,ChangeType,ChangeComment");

reads.ForEach(x => csv.AppendLine(
string.Format("0,1 : 2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22," +
"23,24,25,26,27",
x.CurrentReadDate,
x.DeviceKey,
x.Suffix,
x.CurrentReadRawValue,
x.CurrentReadQualityDesc,
x.CurrentReadTransactionCode,
x.CurrentServiceOrder,
x.PreviousReadDate,
x.PreviousReadRawValue,
x.PreviousReadQualityDesc,
x.PreviousReadTransactionCode,
x.DialDifference,
x.Quantity,
x.UOMDesc,
x.Multiplier,
x.DirectionDesc,
x.ValidationDesc,
x.EffectiveFromDateTime,
x.EffectiveToDateTime,
x.Kwh,
x.AverageDailyLoad,
x.ProfileName,
x.ProfileArea,
x.EffectiveFromDateTime,
x.UpdateTimestamp,
x.ChangedBy,
x.ChangeType,
x.ChangeComment
)
));

return File(Encoding.ASCII.GetBytes(csv.ToString()), "text/csv", "BasicReadsExport.csv");







share|improve this question













I have an IEnumerable of a view model that I need to convert to CSV and send back as a file response. I have written it out quite manually and I'm just seeking code review and suggestions on a more elegant solution, or alternatively if this looks acceptable, confirmation of that:



 public ActionResult ExportBasicReads(string nmi, DateTime fromDate, DateTime toDate)

var reads = _dataService.GetBasicReads(nmi, fromDate, toDate)
.Where(r => r.CurrentReadQuality != ReadQuality.Estimate)
.OrderBy(r => r.CurrentReadDate)
.ThenBy(r => r.DeviceKey)
.ThenBy(r => r.ValidationCode);

StringBuilder csv = new StringBuilder();

csv.AppendLine("Read Date,Device : Suffix,Read,Quality,Tran Code,Service Order,Previous Read Date,Previous Read,Previous Quality," +
"Previous Tran Code,Dial Diff,Quantity,UOM,Dial Factor,Direction,Validation,From Date,To Date,Kwh,Avg Daily Load,Profile Name," +
"Profle Area,Reading Timestamp,Update Date,ChangedBy,ChangeType,ChangeComment");

reads.ForEach(x => csv.AppendLine(
string.Format("0,1 : 2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22," +
"23,24,25,26,27",
x.CurrentReadDate,
x.DeviceKey,
x.Suffix,
x.CurrentReadRawValue,
x.CurrentReadQualityDesc,
x.CurrentReadTransactionCode,
x.CurrentServiceOrder,
x.PreviousReadDate,
x.PreviousReadRawValue,
x.PreviousReadQualityDesc,
x.PreviousReadTransactionCode,
x.DialDifference,
x.Quantity,
x.UOMDesc,
x.Multiplier,
x.DirectionDesc,
x.ValidationDesc,
x.EffectiveFromDateTime,
x.EffectiveToDateTime,
x.Kwh,
x.AverageDailyLoad,
x.ProfileName,
x.ProfileArea,
x.EffectiveFromDateTime,
x.UpdateTimestamp,
x.ChangedBy,
x.ChangeType,
x.ChangeComment
)
));

return File(Encoding.ASCII.GetBytes(csv.ToString()), "text/csv", "BasicReadsExport.csv");









share|improve this question












share|improve this question




share|improve this question








edited Jun 22 at 14:22









Jesse C. Slicer

10.9k2738




10.9k2738









asked Jun 22 at 4:02









TDSnet

283




283







  • 2




    You can use string interpolation instead of string.Format()?
    – vasily.sib
    Jun 22 at 5:37






  • 2




    Looks pretty clean to me.
    – paparazzo
    Jun 22 at 12:21






  • 1




    No need to reinvent - the popular ServiceStack.Text NuGet package has an extension method called .ToCsv(): docs.servicestack.net/csv-format
    – Jesse C. Slicer
    Jun 22 at 14:35






  • 1




    the string.Format is awfully hard to follow given the large number of parameters. I would suggest something like $"x.CurrentReadDate,x.DeviceKey" .... This would also help avoid errors due to matching up parameter indexes if this code was later modified.
    – Zer0
    Jun 23 at 4:09













  • 2




    You can use string interpolation instead of string.Format()?
    – vasily.sib
    Jun 22 at 5:37






  • 2




    Looks pretty clean to me.
    – paparazzo
    Jun 22 at 12:21






  • 1




    No need to reinvent - the popular ServiceStack.Text NuGet package has an extension method called .ToCsv(): docs.servicestack.net/csv-format
    – Jesse C. Slicer
    Jun 22 at 14:35






  • 1




    the string.Format is awfully hard to follow given the large number of parameters. I would suggest something like $"x.CurrentReadDate,x.DeviceKey" .... This would also help avoid errors due to matching up parameter indexes if this code was later modified.
    – Zer0
    Jun 23 at 4:09








2




2




You can use string interpolation instead of string.Format()?
– vasily.sib
Jun 22 at 5:37




You can use string interpolation instead of string.Format()?
– vasily.sib
Jun 22 at 5:37




2




2




Looks pretty clean to me.
– paparazzo
Jun 22 at 12:21




Looks pretty clean to me.
– paparazzo
Jun 22 at 12:21




1




1




No need to reinvent - the popular ServiceStack.Text NuGet package has an extension method called .ToCsv(): docs.servicestack.net/csv-format
– Jesse C. Slicer
Jun 22 at 14:35




No need to reinvent - the popular ServiceStack.Text NuGet package has an extension method called .ToCsv(): docs.servicestack.net/csv-format
– Jesse C. Slicer
Jun 22 at 14:35




1




1




the string.Format is awfully hard to follow given the large number of parameters. I would suggest something like $"x.CurrentReadDate,x.DeviceKey" .... This would also help avoid errors due to matching up parameter indexes if this code was later modified.
– Zer0
Jun 23 at 4:09





the string.Format is awfully hard to follow given the large number of parameters. I would suggest something like $"x.CurrentReadDate,x.DeviceKey" .... This would also help avoid errors due to matching up parameter indexes if this code was later modified.
– Zer0
Jun 23 at 4:09











1 Answer
1






active

oldest

votes

















up vote
4
down vote



accepted










I modified the formatting part for better readability and maintainability, at the cost of lower performance:



var columns = new Dictionary<string, Func<Reads, object>>

["Read Date"] = x => x.CurrentReadDate,
["Device"] = x => x.DeviceKey,

["Suffix"] = x => x.Suffix,
["Read"] = x => x.CurrentReadRawValue,
// add the rest of headers/property selectors below
;
//var format = "0,1 : 2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27";
var format = "01 : " + string.Join(",", columns.Select((_, i) => $"i").Skip(2));

var csv = new StringBuilder()
.AppendFormat(format, columns.Keys.ToArray())
.AppendLine();
foreach (var read in reads)

var values = columns.Values.Select(selector => selector(read));
csv
.AppendFormat(format, values.ToArray())
.AppendLine();



The advantage of this approach is that you can add/move/remove the columns without having to worry about updating the header and the format, and if they are kept in sync or not.



Note:



  • You can also revert to the constant format if it becomes more complex.

  • The property selector can be chained if you need special format on individual property, like: x => x.CurrentReadDate.ToString("yyyyMMddThhmm")





share|improve this answer





















    Your Answer




    StackExchange.ifUsing("editor", function ()
    return StackExchange.using("mathjaxEditing", function ()
    StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
    StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
    );
    );
    , "mathjax-editing");

    StackExchange.ifUsing("editor", function ()
    StackExchange.using("externalEditor", function ()
    StackExchange.using("snippets", function ()
    StackExchange.snippets.init();
    );
    );
    , "code-snippets");

    StackExchange.ready(function()
    var channelOptions =
    tags: "".split(" "),
    id: "196"
    ;
    initTagRenderer("".split(" "), "".split(" "), channelOptions);

    StackExchange.using("externalEditor", function()
    // Have to fire editor after snippets, if snippets enabled
    if (StackExchange.settings.snippets.snippetsEnabled)
    StackExchange.using("snippets", function()
    createEditor();
    );

    else
    createEditor();

    );

    function createEditor()
    StackExchange.prepareEditor(
    heartbeatType: 'answer',
    convertImagesToLinks: false,
    noModals: false,
    showLowRepImageUploadWarning: true,
    reputationToPostImages: null,
    bindNavPrevention: true,
    postfix: "",
    onDemand: true,
    discardSelector: ".discard-answer"
    ,immediatelyShowMarkdownHelp:true
    );



    );








     

    draft saved


    draft discarded


















    StackExchange.ready(
    function ()
    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f197025%2fconvert-ienumerablereads-to-csv%23new-answer', 'question_page');

    );

    Post as a guest






























    1 Answer
    1






    active

    oldest

    votes








    1 Answer
    1






    active

    oldest

    votes









    active

    oldest

    votes






    active

    oldest

    votes








    up vote
    4
    down vote



    accepted










    I modified the formatting part for better readability and maintainability, at the cost of lower performance:



    var columns = new Dictionary<string, Func<Reads, object>>

    ["Read Date"] = x => x.CurrentReadDate,
    ["Device"] = x => x.DeviceKey,

    ["Suffix"] = x => x.Suffix,
    ["Read"] = x => x.CurrentReadRawValue,
    // add the rest of headers/property selectors below
    ;
    //var format = "0,1 : 2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27";
    var format = "01 : " + string.Join(",", columns.Select((_, i) => $"i").Skip(2));

    var csv = new StringBuilder()
    .AppendFormat(format, columns.Keys.ToArray())
    .AppendLine();
    foreach (var read in reads)

    var values = columns.Values.Select(selector => selector(read));
    csv
    .AppendFormat(format, values.ToArray())
    .AppendLine();



    The advantage of this approach is that you can add/move/remove the columns without having to worry about updating the header and the format, and if they are kept in sync or not.



    Note:



    • You can also revert to the constant format if it becomes more complex.

    • The property selector can be chained if you need special format on individual property, like: x => x.CurrentReadDate.ToString("yyyyMMddThhmm")





    share|improve this answer

























      up vote
      4
      down vote



      accepted










      I modified the formatting part for better readability and maintainability, at the cost of lower performance:



      var columns = new Dictionary<string, Func<Reads, object>>

      ["Read Date"] = x => x.CurrentReadDate,
      ["Device"] = x => x.DeviceKey,

      ["Suffix"] = x => x.Suffix,
      ["Read"] = x => x.CurrentReadRawValue,
      // add the rest of headers/property selectors below
      ;
      //var format = "0,1 : 2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27";
      var format = "01 : " + string.Join(",", columns.Select((_, i) => $"i").Skip(2));

      var csv = new StringBuilder()
      .AppendFormat(format, columns.Keys.ToArray())
      .AppendLine();
      foreach (var read in reads)

      var values = columns.Values.Select(selector => selector(read));
      csv
      .AppendFormat(format, values.ToArray())
      .AppendLine();



      The advantage of this approach is that you can add/move/remove the columns without having to worry about updating the header and the format, and if they are kept in sync or not.



      Note:



      • You can also revert to the constant format if it becomes more complex.

      • The property selector can be chained if you need special format on individual property, like: x => x.CurrentReadDate.ToString("yyyyMMddThhmm")





      share|improve this answer























        up vote
        4
        down vote



        accepted







        up vote
        4
        down vote



        accepted






        I modified the formatting part for better readability and maintainability, at the cost of lower performance:



        var columns = new Dictionary<string, Func<Reads, object>>

        ["Read Date"] = x => x.CurrentReadDate,
        ["Device"] = x => x.DeviceKey,

        ["Suffix"] = x => x.Suffix,
        ["Read"] = x => x.CurrentReadRawValue,
        // add the rest of headers/property selectors below
        ;
        //var format = "0,1 : 2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27";
        var format = "01 : " + string.Join(",", columns.Select((_, i) => $"i").Skip(2));

        var csv = new StringBuilder()
        .AppendFormat(format, columns.Keys.ToArray())
        .AppendLine();
        foreach (var read in reads)

        var values = columns.Values.Select(selector => selector(read));
        csv
        .AppendFormat(format, values.ToArray())
        .AppendLine();



        The advantage of this approach is that you can add/move/remove the columns without having to worry about updating the header and the format, and if they are kept in sync or not.



        Note:



        • You can also revert to the constant format if it becomes more complex.

        • The property selector can be chained if you need special format on individual property, like: x => x.CurrentReadDate.ToString("yyyyMMddThhmm")





        share|improve this answer













        I modified the formatting part for better readability and maintainability, at the cost of lower performance:



        var columns = new Dictionary<string, Func<Reads, object>>

        ["Read Date"] = x => x.CurrentReadDate,
        ["Device"] = x => x.DeviceKey,

        ["Suffix"] = x => x.Suffix,
        ["Read"] = x => x.CurrentReadRawValue,
        // add the rest of headers/property selectors below
        ;
        //var format = "0,1 : 2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27";
        var format = "01 : " + string.Join(",", columns.Select((_, i) => $"i").Skip(2));

        var csv = new StringBuilder()
        .AppendFormat(format, columns.Keys.ToArray())
        .AppendLine();
        foreach (var read in reads)

        var values = columns.Values.Select(selector => selector(read));
        csv
        .AppendFormat(format, values.ToArray())
        .AppendLine();



        The advantage of this approach is that you can add/move/remove the columns without having to worry about updating the header and the format, and if they are kept in sync or not.



        Note:



        • You can also revert to the constant format if it becomes more complex.

        • The property selector can be chained if you need special format on individual property, like: x => x.CurrentReadDate.ToString("yyyyMMddThhmm")






        share|improve this answer













        share|improve this answer



        share|improve this answer











        answered Jun 22 at 18:07









        Xiaoy312

        2,767915




        2,767915






















             

            draft saved


            draft discarded


























             


            draft saved


            draft discarded














            StackExchange.ready(
            function ()
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f197025%2fconvert-ienumerablereads-to-csv%23new-answer', 'question_page');

            );

            Post as a guest













































































            Popular posts from this blog

            Greedy Best First Search implementation in Rust

            Function to Return a JSON Like Objects Using VBA Collections and Arrays

            C++11 CLH Lock Implementation