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!