Home > Software engineering >  C# CsvHelper: WriteRecord throws "ConfigurationException: Types that inherit IEnumerable cannot
C# CsvHelper: WriteRecord throws "ConfigurationException: Types that inherit IEnumerable cannot

Time:10-12

This is my original code, writing to a CSV file without CsvHelper.

 public static void Write(string file, List<PayRecord> payrecords) {
   using(StreamWriter sw = new StreamWriter(file)) {
     sw.WriteLine("EmployeeId,Gross,Tax,Net");
     foreach(PayRecord c in payrecords) {
       string line = string.Format("{0},{1},{2},{3}",
         c.Id,
         c.Gross,
         c.Tax,
         c.Net);
       sw.WriteLine(line);
     }
   }
 }

This is the same code, however using CsvHelper this time:

public static void Write(string file, List<PayRecord> payrecords)
{
    using (StreamWriter sw = new StreamWriter(file))
    using (CsvWriter cw = new CsvWriter(sw, CultureInfo.CurrentCulture))
    {
        cw.WriteHeader<PayRecord>();
        foreach (var c in payrecords)
        {
            string line = string.Format("{0},{1},{2},{3}",
                c.Id,
                c.Gross,
                c.Tax,
                c.Net);
            cw.WriteRecord(line);

        }
    }
}

But I'm getting exceptions like:

Unhandled exception. CsvHelper.Configuration.ConfigurationException: Types that inherit IEnumerable cannot be auto mapped. Did you accidentally call GetRecord or WriteRecord which acts on a single record instead of calling GetRecords or WriteRecords which acts on a list of records?
   at CsvHelper.CsvWriter.WriteRecord[T](T record)

and:

Unhandled exception. System.ArgumentNullException: Value cannot be null. (Parameter 'source')
   at System.Linq.ThrowHelper.ThrowArgumentNullException(ExceptionArgument argument)
   at System.Linq.Enumerable.Any[TSource](IEnumerable`1 source)
   at CsvHelper.Configuration.ClassMap.AutoMapConstructorParameters(ClassMap map, CsvContext context, LinkedList`1 mapParents, Int32 indexStart)
   at CsvHelper.Configuration.ClassMap.AutoMapConstructorParameters(ClassMap map, CsvContext context, LinkedList`1 mapParents, Int32 indexStart)
   at CsvHelper.Configuration.ClassMap.AutoMap(CsvContext context)
   at CsvHelper.CsvContext.AutoMap(Type type)
   at CsvHelper.CsvWriter.WriteHeader(Type type)
   at CsvHelper.CsvWriter.WriteHeader[T]()

What's going wrong?

CodePudding user response:

You're trying to write a string as a record.

cw.WriteRecord(line);

There's no way I've been able to reproduce your ArgumentNullException but I do know that even with that fixed, your code still won't work.

The above will throw a CsvHelper.Configuration.ConfigurationException as a string is classed as a single field & CsvHelper.WriteRecord is more for a class as opposed to a string.

Secondly, CsvWriter.WriteHeader will not move the "cursor" to the next row so you need to use CsvWriter.NextRecord() otherwise your data will all be on the same line as your header.

The below code works:

  public static void Write(string file, List<PayRecord> payrecords) {
    using(StreamWriter sw = new StreamWriter(file))
    using(CsvWriter cw = new CsvWriter(sw, CultureInfo.CurrentCulture)) {
      cw.WriteHeader<PayRecord>();
      cw.NextRecord();

      foreach(var c in payrecords) {
        cw.WriteRecord(line);
      }
    }
}

However, even better would be to not write the header manually, use CsvHelper.WriteRecords and pass the entire collection of objects like below.

This results in smaller, cleaner & more efficient code using CsvHelper.WriteRecords correctly, while also writing the correct header.

This is better:

public static void Write(string file, List<PayRecord> payrecords)
{
    using (StreamWriter sw = new StreamWriter(file))
    using (CsvWriter cw = new CsvWriter(sw, CultureInfo.CurrentCulture))
    {
        cw.WriteRecords(payrecords);
    }
}

Even better?

To perform I/O operations (writing to the CSV file) without blocking the main thread, use CsvHelper.WriteRecordsAsync, use await using to not block on resource cleanup & make your method async to be able to await the call.

I'd also use ICollection<PayRecord> so that if you ever change List<PayRecord> to any other type which implements ICollection, you won't need to change the method.

Crème de la crème:

public static async Task WritePayRecords(string file, ICollection<PayRecord> payrecords)
{
    await using (StreamWriter sw = new StreamWriter(file))
    await using (CsvWriter cw = new CsvWriter(sw, CultureInfo.CurrentCulture))
    {
        if (payrecords == null)
            throw new ArgumentNullException(nameof(payrecords), "Collection of pay records cannot be null");

        await cw.WriteRecordsAsync(payrecords);
    }
}

The code above will work even with null fields, null objects and will also throw an exception if the collection is null (as otherwise, CsvHelper will throw one).

Hope that helps!

  • Related