CsvMappingBuilderAddPropertyT(String, IEnumerableString, TypeConverterT) Method

Adds a new DynamicProperty instance that accesses a single column of a CSV file with a collection of column name aliases.

Definition

Namespace: FolkerKinzel.CsvTools.Mappings
Assembly: FolkerKinzel.CsvTools.Mappings (in FolkerKinzel.CsvTools.Mappings.dll) Version: 1.1.0+1263e8243dc2cd78095f678f813d7d9c52ea4315
C#
public CsvMappingBuilder AddProperty<T>(
	string propertyName,
	IEnumerable<string?> columnNameAliases,
	TypeConverter<T> converter
)

Parameters

propertyName  String
The identifier under which the property is addressed. It must follow the rules for C# identifiers. Only ASCII characters are accepted.
columnNameAliases  IEnumerableString

Column names of the CSV file that the DynamicProperty can access. The aliases can use the wildcard characters '*' and '?'.

The first alias, which is a match with a column name of the CSV file, is used. If a wildcard alias matches several columns in the CSV file, the column with the lowest index is referenced.

The collection will be copied.

converter  TypeConverterT
The TypeConverterT that does the type conversion.

Type Parameters

T
The .NET data type of the dynamic property.

Return Value

CsvMappingBuilder
The CsvMappingBuilder to chain calls.

Remarks

Use this method if a CSV column name doesn't match the requirements of C# identifiers, or if the CSV column name is unknown.

In the very special case where '*' is a letter of the column name, replace '*' with '?'.

Example

  Note

In the following code examples - for easier readability - exception handling has been omitted.

Saving the contents of a DataTable as a CSV file and importing data from a CSV file into a DataTable:

C#
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>
*/

Object serialization with CSV:

C#
using FolkerKinzel.CsvTools;
using FolkerKinzel.CsvTools.Mappings;
using System.Text;
// A namespace alias helps to avoid name conflicts
// with the converters from System.ComponentModel
using Conv = FolkerKinzel.CsvTools.Mappings.TypeConverters;

namespace Examples;

internal sealed record Pupil(string? Name, string? Subject, DayOfWeek? LessonDay, TimeOnly? LessonBegin);

internal static class ObjectSerializationExample
{
    public static void CsvReadWritePupils(string filePath)
    {
        Pupil[] pupils = [
                            new("Susi", "Piano", DayOfWeek.Wednesday, new TimeOnly(14, 30)),
                            new("Carl Czerny", "Piano", DayOfWeek.Thursday, new TimeOnly(15, 15)),
                            new("Frederic Chopin", "Piano", null, null)
                         ];

        // A converter can be reused for more than one DynamicProperty:
        Conv::TypeConverter<string?> stringConverter = Conv::StringConverter.CreateNullable();

        // Initialize a CsvMapping that maps the data from the CSV-Columns and converts it to the right data type.
        // Aliases with wildcards can be used to match the column-headers of the CSV file. 
        CsvMapping mapping = CsvMappingBuilder
            .Create()
            .AddProperty("Name", ["*name"], stringConverter)
            .AddProperty("Subject", ["*subject", "*fach"], stringConverter)
            .AddProperty("LessonDay", ["*day", "*tag"], new Conv::EnumConverter<DayOfWeek>().ToNullableConverter())
            .AddProperty("LessonBegin", ["*begin?"], new Conv::TimeOnlyConverter().ToNullableConverter())
            .Build();

        // Create a CSV-File:
        pupils.SaveCsv(filePath,
                       mapping,
                       static (pupil, mapping) =>
                       {
                           mapping.Name = pupil.Name;
                           mapping.Subject = pupil.Subject;
                           mapping.LessonDay = pupil.LessonDay;
                           mapping.LessonBegin = pupil.LessonBegin;
                       },
                       columnNames:
                       ["Unterrichtstag", "Unterrichtsbeginn", "Vollständiger Name", "Unterrichtsfach"]);

        Console.WriteLine(File.ReadAllText(filePath));
        Console.WriteLine();

        // Read the CSV file:
        using CsvReader<Pupil> pupilsReader =
           CsvConverter.OpenRead<Pupil>(filePath,
                                        mapping,
                                        static mapping => new Pupil(mapping.Name,
                                                                    mapping.Subject,
                                                                    mapping.LessonDay,
                                                                    mapping.LessonBegin));
        pupils = [.. pupilsReader];

        // Write the results to the Console:
        foreach (Pupil pupil in pupils)
        {
            Console.WriteLine(pupil);
        }
    }
}

/*
Console output: 

Unterrichtstag,Unterrichtsbeginn,Vollständiger Name,Unterrichtsfach
3,14:30:00,Susi,Piano
4,15:15:00,Carl Czerny,Piano
,,Frederic Chopin,Piano

Pupil { Name = Susi, Subject = Piano, LessonDay = Wednesday, LessonBegin = 14:30 }
Pupil { Name = Carl Czerny, Subject = Piano, LessonDay = Thursday, LessonBegin = 15:15 }
Pupil { Name = Frederic Chopin, Subject = Piano, LessonDay = , LessonBegin =  }
*/

Exceptions

ArgumentNullExceptionpropertyName, or columnNameAliases, or converter is null.
ArgumentException

propertyName does not conform to the rules for C# identifiers (only ASCII characters)

- or -

a DynamicProperty with the same PropertyName has already been added.

RegexMatchTimeoutException Validating of propertyName takes too long.

See Also