JoshClose / CsvHelper

Library to help reading and writing CSV files
http://joshclose.github.io/CsvHelper/
Other
4.76k stars 1.07k forks source link

Bug when writing header record it sometimes is not written #1713

Open DeltaZsnes opened 3 years ago

DeltaZsnes commented 3 years ago

Describe the bug When using the CsvWriter in C# Visual Studion Pro 2019. I would sometimes not be able to write the CSV Header to the file. The file would finish as a blank file. After looking through your source code for a few hours I figured out a very simple fix. The code is included in the bottom. Apparently the class map is not always registered in the context so the fix makes sure to always register it. Thanks for an otherwise great library.

When trying to only write the header the file is empty When trying to write the records an exception thrown is "No properties are mapped for type"

To Reproduce Difficult to reproduce seems to work well in some projects and not at all in other projects. The fix seems to work for all projects.

Expected behavior When writing a csv file with headers included it should just work

Screenshots N/A

Additional context N/A

Code

                        using var fileStream = File.Open(Path.Combine(outputPath, fileName), FileMode.Create);
            using var streamWriter = new StreamWriter(fileStream);
            using var csvWriter = new CsvWriter(streamWriter, _configuration);

            // fixes weird bug where header sometimes does not get written
            var classMap = csvWriter.Context.WriterConfiguration.AutoMap(typeof(T));
            csvWriter.Context.WriterConfiguration.RegisterClassMap(classMap);

            csvWriter.WriteHeader(typeof(T));
            csvWriter.NextRecord();
            csvWriter.Flush();

            //this part cant be written without the header and throws an exception "No properties are mapped for type"
            //csvWriter.WriteRecords(list);
            //csvWriter.NextRecord();
            //csvWriter.Flush();

                        // appears to be some issue in the WriteHeader function in this code block when debugging
                        if (context.Maps[type] == null)
            {
                context.Maps.Add(context.AutoMap(type));
            }

            var members = new MemberMapCollection();
            members.AddMembers(context.Maps[type]);
JoshClose commented 3 years ago

What version are you on?

You should register your class map like this

csvWriter.Context.RegisterClassMap<T>();

You don't need to call csvWriter.Flush(). That will happen automatically.

You don't have any braces after your using statements, so there is no block to dispose of. My guess is dispose is never called, which means the StreamWriter is never flushed. You'll need to put braces after, or manually call streamWriter.Flush() to flush the StreamWriter's buffer to the file.

DeltaZsnes commented 3 years ago

Hi Josh. Cool to get an immediate answer from the author himself.

The version used is 16.0.0 from NuGetPackage

Flush is not the problem here I just included that code to be extra sure Dispose is called after it was not included in the snippet

From my understanding registering a class map is optional? I have other projects (also visual studio projects) where the same code work just fine without registering a class map. It is a very weird ninja bug.

JoshClose commented 3 years ago

There isn't enough information in your example for me to figure out anything else.

Can you make a small example that shows the error?

Maybe you can modify this example.

void Main()
{
    var records = new List<Foo>
    {
        new Foo { Id = 1, Name = "one" },
    };
    var config = new CsvConfiguration(CultureInfo.InvariantCulture)
    {
    };
    using (var writer = new StringWriter())
    using (var csv = new CsvWriter(writer, config))
    {
        csv.WriteRecords(records);
        writer.Dump();
    }
}

private class Foo
{
    public int Id { get; set; }
    public string Name { get; set; }
}

private class FooMap : ClassMap<Foo>
{
    public FooMap()
    {
        Map(m => m.Id);
        Map(m => m.Name);
    }
}

note: Dump is from LINQPad

dameng324 commented 3 years ago

What version are you on?

You should register your class map like this

csvWriter.Context.RegisterClassMap<T>();

You don't need to call csvWriter.Flush(). That will happen automatically.

You don't have any braces after your using statements, so there is no block to dispose of. My guess is dispose is never called, which means the StreamWriter is never flushed. You'll need to put braces after, or manually call streamWriter.Flush() to flush the StreamWriter's buffer to the file.

I was thought the streamWriter will flush when I called csvWriter.Flush() ,is it not?

JoshClose commented 3 years ago

CsvWriter works just like StreamWriter. It has an internal buffer that will flush to the TextWriter on it's own and when disposed. If you don't dispose after writing, you'll need to manually call CsvWriter.Flush() to flush the data in the buffer to the TextWriter and flush the TextWriter itself.