Migration guide
Migrating to v10.0.0
Moving to v10.0.0 from v9.2.3. Changelog.
CsvHelper 10 changes three things you may rely on: the default delimiter is the current culture's list separator
instead of a comma, ReadingExceptionOccurred returns whether to throw instead of throwing itself, and
PrepareHeaderForMatch also gets an index. Validation failures have their own exception types, dynamic records
changed a little, and there is an IDataReader for loading a DataTable. The target frameworks don't change (.NET
Standard 2.0 and .NET Framework 4.5), but on .NET Framework the package now depends on System.ValueTuple.
Delimiter
The default delimiter was a comma. It's now CultureInfo.CurrentCulture.TextInfo.ListSeparator, taken when the
configuration is created, so it follows the culture the code runs under: a comma for English cultures, but a
semicolon for others, such as German. Set the delimiter when your files use commas wherever the code runs, for reading
and for writing:
using var csv = new CsvReader(reader);using var csv = new CsvReader(reader);
csv.Configuration.Delimiter = ",";Setting Configuration.CultureInfo doesn't change the delimiter: it comes from the current culture.
New options
Escape: the character that escapes a quote in a quoted field, when reading and writing. It's"by default, so quotes are doubled as before; a file that escapes them as\"reads withEscape = '\\'.LineBreakInQuotedFieldIsBadData: whentrue, a line break inside a quoted field callsBadDataFound, which throws by default. It'sfalseby default.- Your own configuration class: if you implement
IParserConfiguration, addEscapeandLineBreakInQuotedFieldIsBadData; if you implementISerializerConfiguration, addEscape.
Header matching
PrepareHeaderForMatch gets an index after the header name, so it's a Func<string, int, string>. Add the parameter
to your function:
using var csv = new CsvReader(reader);
csv.Configuration.PrepareHeaderForMatch = header => header.Trim().ToLower();using var csv = new CsvReader(reader);
csv.Configuration.PrepareHeaderForMatch = (header, index) => header.Trim().ToLower();- What the index is: when the header record is read, and for the property names of dynamic records, it's the
column's index. When CsvHelper looks up a member, it's the position of the name among the member's names (the names
given to
Name("a", "b")). - The lookup cache:
ReadingContext.NamedIndexCacheholds(string, int)value tuples instead ofTuple<string, int>.
Reading errors
ReadingExceptionOccurred is called when a record can't be read, or when the number of columns changes with
DetectColumnCountChanges. In 9 it was an Action: it threw to stop reading (the default did), and returning kept
reading. It's now a Func<CsvHelperException, bool> that returns whether to throw, and CsvHelper does the throwing:
true throws, false keeps reading (GetRecords skips the record, and GetRecord returns default).
using var csv = new CsvReader(reader);
csv.Configuration.ReadingExceptionOccurred = ex =>
{
Console.WriteLine($"Skipped row {ex.ReadingContext.Row}: {ex.Message}");
};using var csv = new CsvReader(reader);
csv.Configuration.ReadingExceptionOccurred = ex =>
{
Console.WriteLine($"Skipped row {ex.ReadingContext.Row}: {ex.Message}");
return false;
};| CsvHelper 9 | CsvHelper 10 |
|---|---|
ex => throw ex (the default) |
ex => true (the default) |
ex => { Log(ex); } to skip |
ex => { Log(ex); return false; } |
ReadingExceptionOccurred = null to ignore errors |
ReadingExceptionOccurred = ex => false |
null throws now. In 9, setting ReadingExceptionOccurred to null ignored the errors; in 10 a null callback
means every error is thrown. Use ex => false to ignore them.
Validation errors
ValidationException is abstract. A missing header throws a HeaderValidationException, with the names it looked
for in HeaderNames and the name index in HeaderNameIndex. A field that fails a class map's Validate throws a
FieldValidationException, with the field's text in Field. catch (ValidationException) still catches both. If you
throw ValidationException yourself, throw one of these:
using var csv = new CsvReader(reader);
csv.Configuration.HeaderValidated = (isValid, headerNames, headerNameIndex, context) =>
{
if (!isValid)
{
throw new ValidationException(context, $"The file has no '{headerNames[0]}' column.");
}
};using var csv = new CsvReader(reader);
csv.Configuration.HeaderValidated = (isValid, headerNames, headerNameIndex, context) =>
{
if (!isValid)
{
throw new HeaderValidationException(
context, headerNames, headerNameIndex, $"The file has no '{headerNames[0]}' column.");
}
};The default message for a missing header reads differently too, such as "Header with name 'Id' was not found.", followed by how to turn the validation off.
Loading a DataTable
CsvDataReader is an IDataReader over a CsvReader, so a DataTable (or anything else that takes an
IDataReader) can load a file:
using var csv = new CsvReader(reader);
using var dataReader = new CsvDataReader(csv);
var table = new System.Data.DataTable();
table.Load(dataReader);
Creating it reads the first record, and makes it the header when HasHeaderRecord is true, so pass a CsvReader
that hasn't read anything yet. Disposing it disposes the CsvReader.
Behavior changes
- Dynamic records have every column: a record read with
GetRecords<dynamic>()has a property for each header column, and a field that's missing from a short row isnull. Before, the record had properties only for the fields the row had. - Dynamic objects are written in name order: an
ExpandoObjector other dynamic object is written with its properties, and its header, sorted by name. Before, they were in the order the object listed them (for anExpandoObject, the order they were added), so objects whose properties were added in a different order didn't line up with the header.
All API changes
Every change to the public API since v9.2.3: 8 added, 8 changed (8 breaking). Open a type to see its changes.
CsvHelper
ReadingContextclass1 breaking1 changed
NamedIndexCacheBreaking
The type changed from Dictionary<string, Tuple<string, int>> to Dictionary<string, (string, int)>.
public Dictionary<string, Tuple<string, int>> NamedIndexCachepublic Dictionary<string, (string, int)> NamedIndexCacheValidationExceptionclass1 breaking1 changed
ValidationExceptionBreaking
The type is now abstract.
public class ValidationException : CsvHelperExceptionpublic abstract class ValidationException : CsvHelperExceptionAdded types (3)
CsvHelper.Configuration
Configurationclass2 breaking2 changed2 added
PrepareHeaderForMatchBreaking
The type changed from Func<string, string> to Func<string, int, string>.
public virtual Func<string, string> PrepareHeaderForMatch { get; set; }public virtual Func<string, int, string> PrepareHeaderForMatch { get; set; }ReadingExceptionOccurredBreaking
The type changed from Action<CsvHelperException> to Func<CsvHelperException, bool>.
public virtual Action<CsvHelperException> ReadingExceptionOccurred { get; set; }public virtual Func<CsvHelperException, bool> ReadingExceptionOccurred { get; set; }Added
ConfigurationFunctionsstatic class2 breaking2 changed
PrepareHeaderForMatch(string, int)Breaking
The parameters changed from (string) to (string, int).
public static string PrepareHeaderForMatch(string header)public static string PrepareHeaderForMatch(string header, int index)ReadingExceptionOccurred(CsvHelperException)Breaking
The return type changed from void to bool.
public static void ReadingExceptionOccurred(CsvHelperException exception)public static bool ReadingExceptionOccurred(CsvHelperException exception)IParserConfigurationinterface2 added
Added
IReaderConfigurationinterface2 breaking2 changed
PrepareHeaderForMatchBreaking
The type changed from Func<string, string> to Func<string, int, string>.
Func<string, string> PrepareHeaderForMatch { get; set; }Func<string, int, string> PrepareHeaderForMatch { get; set; }ReadingExceptionOccurredBreaking
The type changed from Action<CsvHelperException> to Func<CsvHelperException, bool>.
Action<CsvHelperException> ReadingExceptionOccurred { get; set; }Func<CsvHelperException, bool> ReadingExceptionOccurred { get; set; }ISerializerConfigurationinterface1 added
Added