Note
In the following code examples - for easier readability - exception
handling has been omitted.
public static void ReadCsv(
this DataTable dataTable,
string filePath,
CsvMapping mapping,
char delimiter = ',',
Encoding? textEncoding = null,
bool isHeaderPresent = true,
CsvOpts options = CsvOpts.Default
)
<ExtensionAttribute>
Public Shared Sub ReadCsv (
dataTable As DataTable,
filePath As String,
mapping As CsvMapping,
Optional delimiter As Char = ","C,
Optional textEncoding As Encoding = Nothing,
Optional isHeaderPresent As Boolean = true,
Optional options As CsvOpts = CsvOpts.Default
)
public:
[ExtensionAttribute]
static void ReadCsv(
DataTable^ dataTable,
String^ filePath,
CsvMapping^ mapping,
wchar_t delimiter = L',',
Encoding^ textEncoding = nullptr,
bool isHeaderPresent = true,
CsvOpts options = CsvOpts::Default
)
[<ExtensionAttribute>]
static member ReadCsv :
dataTable : DataTable *
filePath : string *
mapping : CsvMapping *
?delimiter : char *
?textEncoding : Encoding *
?isHeaderPresent : bool *
?options : CsvOpts
(* Defaults:
let _delimiter = defaultArg delimiter ','
let _textEncoding = defaultArg textEncoding null
let _isHeaderPresent = defaultArg isHeaderPresent true
let _options = defaultArg options CsvOpts.Default
*)
-> unit
Each PropertyName of mapping MUST have a corresponding DataColumn in dataTable - corresponding in the Caption property (case-insensitive) and the accepted data type.
Effort must be taken that the PropertyNames in mapping are unique, even when treated case-insensitive.
The DynamicProperty instances in mapping don't need to match all columns of the DataTable or all columns of the CSV file (neither in number nor in order).
When importing CSV data from Excel, the appropriate arguments can be determined with GetExcelArguments.
DataTable serialization with CSV:
using FolkerKinzel.CsvTools;
using FolkerKinzel.CsvTools.Mappings;
using System.Data;
// A namespace alias helps to avoid name conflicts
// with the converters from System.ComponentModel
using Conv = FolkerKinzel.CsvTools.Mappings.TypeConverters;
namespace Examples;
internal static class DataTableExample
{
public static void DataTableWriteReadCsv(string filePath)
{
using var dataTable = new DataTable();
dataTable.Columns.Add(new DataColumn("not_used", typeof(int)));
dataTable.Columns.Add(new DataColumn("name"));
dataTable.Columns.Add(new DataColumn("subject"));
dataTable.Columns.Add(new DataColumn("day", typeof(DayOfWeek)));
dataTable.Columns.Add(new DataColumn("lesson start", typeof(TimeOnly)));
// The DataColumn.Caption property allows you to override the DataColumn.ColumnName property
// when the ColumnName does not meet C# identifier requirements. The values of the
// DataColumn.Caption properties must be unique for CSV serialization (case-insensitive,
// like DataColumn.ColumnName).
dataTable.Columns["lesson start"]!.Caption = "begin";
_ = dataTable.Rows.Add(
[4711, "Susi Meyer", "Piano", DayOfWeek.Wednesday, new TimeOnly(14, 30, 0)]);
_ = dataTable.Rows.Add(
[0, "Carl Czerny", "Piano", DayOfWeek.Thursday, new TimeOnly(15, 15, 0)]);
_ = dataTable.Rows.Add(
[111, "Frederic Chopin", "Piano"]);
// Store the stringConverter because you can reuse the same
// converter for more than one property in CsvRecordWrapper.
Conv::TypeConverter<object> stringConverter
= Conv::StringConverter.CreateNullable().ToDBNullConverter();
// Each dynamic property name of the CsvMapping has to have a corresponding column in
// the DataTable - corresponding in the DataColumn.Caption property (case-insensitive)
// and the accepted data type. Mapping properties and DataColumns don't need to
// correspond in their number and order and they don't need to match the columns of
// the CSV file:
CsvMapping mapping = CsvMappingBuilder
.Create()
.AddProperty("Name", stringConverter)
.AddProperty("Subject", stringConverter)
.AddProperty("Day", new Conv::EnumConverter<DayOfWeek>(format: "G")
.ToNullableConverter()
.ToDBNullConverter())
.AddProperty("Begin", ["begin", "*start"], new Conv::TimeOnlyConverter()
.ToNullableConverter()
.ToDBNullConverter())
.Build();
// Write the CSV file:
// (The CSV column names and the CsvMapping determine which DataColumns will be
// part of the CSV and their order in the CSV file.)
dataTable.WriteCsv(filePath,
mapping,
csvColumnNames: ["Subject", "Lesson Start", "Name", "Day", "Reserved"]);
dataTable.Clear();
// Refill the DataTable from the CSV-file:
dataTable.ReadCsv(filePath, mapping);
Console.WriteLine("Csv file:");
Console.WriteLine();
Console.WriteLine(File.ReadAllText(filePath));
Console.WriteLine();
Console.WriteLine("Content of the refilled DataTable:");
Utility.WriteConsole(dataTable);
}
}
/*
Console output:
Csv file:
Subject,Lesson Start,Name,Day,Reserved
Piano,14:30:00,Susi Meyer,Wednesday,
Piano,15:15:00,Carl Czerny,Thursday,
Piano,,Frederic Chopin,,
Content of the refilled DataTable:
<DBNull> Susi Meyer Piano 3 14:30
<DBNull> Carl Czerny Piano 4 15:15
<DBNull> Frederic Chopin Piano <DBNull> <DBNull>
*/
ArgumentNullException | dataTable, or filePath, or mapping is null. |
ArgumentException | filePath is not a valid file path. - or - There is a DynamicProperty in mapping whose PropertyName finds no corresponding ColumnName in dataTable. |
FormatException | Parsing fails and Throwing is true. |
NoNullAllowedException | The mapping doesn't match the schema of the dataTable. |
ConstraintException | The parsed CSV data does not match the schema of the dataTable. |
IOException | I/O error. |
ObjectDisposedException | The file was already closed. |