Search code examples
c#csvcsvhelper

CsvHelper not setting properties that have classpaths


I am having a strange issue where CsvHelper does not change properties that use classpaths to save data eg set => Channel.Snippet.Title or set => Channel.Id.

using (StreamReader fileStream = new StreamReader(openFileDialog.FileName))
using (CsvReader reader = new CsvReader(fileStream, new CsvConfiguration(CultureInfo.InvariantCulture)
{
    DetectColumnCountChanges = false,
    HeaderValidated = (args) => // This is set because the CSV is only partial data
    {
        Console.WriteLine(args.ToString());
    },
    MissingFieldFound = null // This is set because the CSV is only partial data
}))
{
    foreach (FullChannel record in reader.GetRecords<FullChannel>()) // tried adding .ToList()
    {
        Settings.Default.Channels.Add(record); // record.Id and record.Title are both null
        Console.WriteLine($"Id: {record.Id} Title: {record.Title}"); // Logs: Id: Title: 
        record.Id = "foo"; // This works fine
        record.Title = "bar"; // This works fine too
        Console.WriteLine($"Id: {record.Id} Title: {record.Title}"); // Logs: Id: foo Title: bar 
    }
}

This always deserializes the correct amount. I've tried to create a new TestChannel, clean and rebuild. CsvHelper seems to be able to save if there is no classpath eg using private string title in Title (The commented out code).

using Google.Apis.YouTube.v3.Data;

public class FullChannel
{
    private string title;

    public FullChannel()
    {
        Channel.Snippet = new ChannelSnippet();
    }

    // Functionality 
    public DateTime LastUpdated { get; set; }

    public bool AllNotifications { get; set; }

    // Channel
    /// <summary>
    /// Warning: Nothing in this property gets saved
    /// </summary>
    [XmlIgnore]
    //public Channel Channel { get; set; } = new Channel();  // this is the original code
    public TestChannel Channel { get; set; } = new TestChannel(); // this doesn't work for Id or Title

    // For csv
    [Name("Channel Id")]
    public string Id { get => Channel.Id; set => Channel.Id = value; }

    [Name("Channel Url")]
    [XmlIgnore] // Generated value only
    public string Url { get => $"https://www.youtube.com/channel/{Channel.Id}"; set => _ = value; }

    [Name("Channel Title")]
    public string Title {
        //get => title;
        //set => title = value;
        get => Channel.Snippet.Title;
        set => Channel.Snippet.Title = value;
    }
}

public class TestChannel
{
    public ChannelSnippet Snippet { get; set; }

    public string Id { get; set; }
}

Solution

  • I have a feeling that the way CsvHelper reconstitutes the class is not recognizing the classpaths. The only thing I can suggest is saving directly to Channel.Snippet.Title and Channel.Id.

    void Main()
    {
        using (var fileStream = new StringReader("Channel Id,Channel Url,Channel Title\n1,this.com,MyTitle"))
        using (CsvReader reader = new CsvReader(fileStream, new CsvConfiguration(CultureInfo.InvariantCulture)
        {
            DetectColumnCountChanges = false,
            HeaderValidated = (args) => // This is set because the CSV is only partial data
            {
                Console.WriteLine(args.ToString());
            },
            MissingFieldFound = null // This is set because the CSV is only partial data
        }))
        {
            reader.Context.RegisterClassMap<FullChannelMap>();
            foreach (FullChannel record in reader.GetRecords<FullChannel>()) // tried adding .ToList()
            {
                Console.WriteLine($"Id: {record.Id} Title: {record.Title}"); // Logs: Id: Title: 
                record.Id = "foo"; // This works fine
                record.Title = "bar"; // This works fine too
                Console.WriteLine($"Id: {record.Id} Title: {record.Title}"); // Logs: Id: foo Title: bar 
            }
        }
    }
    
    public class FullChannelMap : ClassMap<FullChannel>
    {
        public FullChannelMap()
        {
            Map(x => x.Channel.Id).Name("Channel Id");
            Map(x => x.Channel.Snippet.Title).Name("Channel Title");
            Map(x => x.Url).Name("Channel Url");
        }
    }