CsvHelper没有向内存流中写入任何内容。

73
我有以下方法:

我有以下方法:

public byte[] WriteCsvWithHeaderToMemory<T>(IEnumerable<T> records) where T : class
{
    using (var memoryStream = new MemoryStream())
    using (var streamWriter = new StreamWriter(memoryStream))
    using (var csvWriter = new CsvWriter(streamWriter))
    {
        csvWriter.WriteRecords<T>(records);

        return memoryStream.ToArray();
    }
}

这个函数被调用的时候会传递一个对象列表 - 通常来自于数据库,但是由于某些问题,我只是暂时使用静态集合填充。被传递的对象如下:

using CsvHelper.Configuration;

namespace Application.Models.ViewModels
{
    public class Model
    {
        [CsvField(Name = "Field 1", Ignore = false)]
        public string Field1 { get; set; }

        [CsvField(Name = "Statistic 1", Ignore = false)]
        public int Stat1{ get; set; }

        [CsvField(Name = "Statistic 2", Ignore = false)]
        public int Stat2{ get; set; }

        [CsvField(Name = "Statistic 3", Ignore = false)]
        public int Stat3{ get; set; }

        [CsvField(Name = "Statistic 4", Ignore = false)]
        public int Stat4{ get; set; }
    }
}

我想做的是在MVC应用程序中将一组内容写入CSV进行下载。 但每次尝试写入方法时,MemoryStream都会返回零长度,并且没有传递任何内容。 我之前使用过这个方法,但由于某种原因它现在无法正常工作-我有点困惑。 有人可以指出我在这里做错了什么吗?

2
你尝试过刷新你的流吗?在返回之前加上 csvWriter.Flush(); - Eli Gassert
谢谢。我知道这一定是些愚蠢的问题,但是我已经浪费了半个上午盯着它看,无法想出哪里出了问题... - Ian Cotterill
这个可以?太好了。你想让我将其提交为答案供未来搜索者使用,还是你自己提交解决方案? - Eli Gassert
如果您能提交一个答案,我会检查它并给予您相应的信用。 - Ian Cotterill
1
CsvField属性目前不受支持,您可以使用CsvClassMap,然后将它们注册为:csv.Configuration.RegisterClassMap(new MyClassMap); - Devesh
6个回答

83

您已经有了一个很好的using块。这将为您刷新写入器。您只需要稍微改变代码就可以让它工作。

using (var memoryStream = new MemoryStream())
{
    using (var streamWriter = new StreamWriter(memoryStream))
    using (var csvWriter = new CsvWriter(streamWriter))
    {
        csvWriter.WriteRecords<T>(records);
    } // StreamWriter gets flushed here.

    return memoryStream.ToArray();
}

如果您打开AutoFlush,需要小心。这将在每次写操作后刷新。如果您的流是网络流并且通过网络传输,那么速度会非常慢。


2
我认为这是不正确的。如果你这么做,StreamWriter 将会被刷新,但它也会释放基础流。 - 2-bits
1
是的,返回值应该在 using 块内部。 - Josh Close
1
似乎CsvWriter在3.0.0.0版本中有两个Bug。它在Dispose时不会刷新,这是其中一个问题,第二个问题是第一条记录与标题之间没有用新行分隔开。因此,刷新csvWriter是必要的解决方法。 - Radek Strugalski
1
请在 GitHub 上提交一个 bug,我会尽快修复。 - Josh Close
1
@RadekStrugalski 修复了第一个问题。现在第二个问题需要手动调用 NextRecord()。这是为了给用户更多的控制权。你可以通过这种方式编写标题并进行更多的手动编写。 - Josh Close
谢谢,虽然不省时,但比其他任何东西都棒。 - Hardik Masalawala

45

在返回之前加上 csvWriter.Flush(); 来刷新写入器/流。

编辑:根据杰克的回复,应该刷新流而不是 csvWriter。streamWriter.Flush();。保留原始解决方案,但添加此更正。

编辑2:我推荐的答案是:https://dev59.com/12Yr5IYBdhLWcg3wdqGw#22997765 让 using 语句为您处理繁重工作。


13
这个回答是错误的。CsvWriter 没有 Flush 方法。 - Jack Hughes
4
我更喜欢的答案是:https://dev59.com/12Yr5IYBdhLWcg3wdqGw#22997765。 让 using 语句为你完成大部分工作。 - Eli Gassert
1
streamwriter.Flush(); 对我有用。感谢您的帮助。同时也感谢 @JackHughes。 - Rohit Arora
1
在CsvHelper >= 17中有一个Flush,这是对我有效的一个。 - Saeb Amini

32

将所有这些内容综合考虑在一起(包括更正的注释),并重置内存流位置,对我来说最终的解决方案是;

        using (MemoryStream ms = new MemoryStream())
        {
            using (TextWriter tw = new StreamWriter(ms))
            using (CsvWriter csv = new CsvWriter(tw, CultureInfo.InvariantCulture))
            {
                csv.WriteRecords(errors); // Converts error records to CSV

                tw.Flush(); // flush the buffered text to stream
                ms.Seek(0, SeekOrigin.Begin); // reset stream position

                Attachment a = new Attachment(ms, "errors.csv"); // Create attachment from the stream
                // I sent an email here with the csv attached.
            }
        }

如果有助于其他人,这是重点!


2
"ms.Seek(0, SeekOrigin.Begin);" 对我来说是缺失的一部分。Flushes、使用语句,都没有任何作用,直到我重置了流。 - Brick
1
答案可能已经过时且不起作用。CsvWriter(tw)不再仅接受一个参数,即使添加了它,它仍会抛出错误,抱怨关闭的内存流。 - brianc

9

csvWriter中没有flush方法,flush方法在streamWriter中。当调用

csvWriter.Dispose();

时,会刷新流。 另一种方法是设置

streamWriter.AutoFlush = true;

这将自动刷新流每次。


8

这里是一个可行的示例:

void Main()
{
    var records = new List<dynamic>{
       new { Id = 1, Name = "one" },
       new { Id = 2, Name = "two" },
    };

    Console.WriteLine(records.ToCsv());
}

public static class Extensions {
    public static string ToCsv<T>(this IEnumerable<T> collection)
    {
        using (var memoryStream = new MemoryStream())
        {
            using (var streamWriter = new StreamWriter(memoryStream))
            using (var csvWriter = new CsvWriter(streamWriter))
            {
                csvWriter.WriteRecords(collection);
            } // StreamWriter gets flushed here.

            return Encoding.ASCII.GetString(memoryStream.ToArray());
        }
    }
}

基于这个答案。


0

using CsvHelper;
public class TwentyFoursStock
{
    [Name("sellerSku")]
    public string ProductSellerSku { get; set; }

    [Name("shippingPoint")]
    public string ProductShippingPoint { get; set; }
}

using (var writer = new StreamWriter("file.csv"))
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
{
    csv.WriteRecords(TwentyFoursStock);
}


这对我很有效,Name属性允许您指定标题列的名称。 - Redhwan Ghailan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接