0

This question is a spin-off this one (Can this method be refactored to use a lamba?). In the comments to that question this was raised:

The refactoring needs to be done in a different place. It's not your function that sucks, it's the definition of CLMExplorerHistory.

The internal data structure does not need to represent the structure of the CSV file in a 1:1 fashion. Just make sure you can read and write in a compatible way.

I think that you're attempting to use the csv data structure directly rather than look at possible areas of duplication that would indicate the obvious place for implementing a class type in order to allow for code reuse. Could you post up a snippet of example data for your CSV file to indicate your starting point?


On the backend of those comments, here is a snippet of the example CSV data:

Date,Status,Weekly Bible Reading,Song1 #,Song1 Title,Song1 Scripture,Song2 #,Song2 Title,Song2 Scripture,Song3 #,Song3 Title,Song3 Scripture,Meeting?,CO Visit,Cancel Reason,# Classes,Chairman,PrayerOpen,PrayerClose,TreasuresTalk,TreasuresTalk_Theme,TreasuresDigging,BibleReading_A,BibleReading_Study_A,BibleReading_B,BibleReading_Study_B,BibleReading_C,BibleReading_Study_C,BibleReading_Source,Apply1_Description,Apply2_Description,Apply3_Description,Apply4_Description,Apply1_A,Apply1_Asst_A,Apply1_Study_A,Apply2_A,Apply2_Asst_A,Apply2_Study_A,Apply3_A,Apply3_Asst_A,Apply3_Study_A,Apply4_A,Apply4_Asst_A,Apply4_Study_A,Apply1_B,Apply1_Asst_B,Apply1_Study_B,Apply2_B,Apply2_Asst_B,Apply2_Study_B,Apply3_B,Apply3_Asst_B,Apply3_Study_B,Apply4_B,Apply4_Asst_B,Apply4_Study_B,Apply1_C,Apply1_Asst_C,Apply1_Study_C,Apply2_C,Apply2_Asst_C,Apply2_Study_C,Apply3_C,Apply3_Asst_C,Apply3_Study_C,Apply4_C,Apply4_Asst_C,Apply4_Study_C,LivingPart1,LivingPart1_Theme,LivingPart1_Length,LivingPart2,LivingPart2_Theme,LivingPart2_Length,CBS,CBS_Source,CBS_Read,Audience B,Audience C,AuxCounselor B,AuxCounselor C
01/03/2021,Registrato e completo,NUMERI 7-8,4,“Geova è il mio Pastore”,Salmo 23,54,“Questa è la via”,"Isaia 30:20, 21",127,Che tipo di persona sono?,2 Pietro 3:11,Y,N,,1,Italo De Gaeta,xxx,xxx,xxx,“L’accampamento d’Israele: lezioni utili”,xxx,xxx,5,,,,,Nu 7:1-17,Commemorazione,Visita ulteriore,Visita ulteriore,Visita ulteriore,xxx,xxx,11,xxx,xxx,6,xxx,xxx,12,xxx,xxx,17,,,,,,,,,,,,,,,,,,,,,,,,,xxx,Risultati raggiunti dall’organizzazione,(5 min),xxx,Bisogni locali,(10 min),xxx,"rr cap. 5 parr. 17-22, riquadro 5A",xxx,,,,

I use the CsvReader class to read it into the CLMExplorerHistory object. The class is defined like this:

public class CLMExplorerHistory
{
    [Format("dd/MM/yyyy")]
    [Name("Date")]
    public DateTime Date { get; set; }

    [BooleanFalseValues(new string[] { "N", "n" })]
    [BooleanTrueValues(new string[] { "Y", "y" })]
    [Name("Meeting?")]
    public bool Meeting { get; set; }

    [Name("# Classes")]
    public int Classes { get; set; }

    [Name("Chairman")]
    public string Chairman { get; set; }

    [Name("AuxCounselor B")]
    public string AuxCounsellor1 { get; set; }

    [Name("AuxCounselor C")]
    public string AuxCounsellor2 { get; set; }

    [Name("PrayerOpen")]
    public string PrayerOpen { get; set; }

    [Name("PrayerClose")]
    public string PrayerClose { get; set; }

    [Name("CBS")]
    public string CBSConductor { get; set; }

    [Name("CBS_Read")]
    public string CBSReader { get; set; }

    [Name("TreasuresTalk")]
    public string TreasuresTalkName { get; set; }

    [Name("TreasuresTalk_Theme")]
    public string TreasuresTalkTheme { get; set; }

    [Name("TreasuresDigging")]
    public string SpiritualGemsName { get; set; }

    [Name("LivingPart1")]
    public string LivingPart1Name { get; set; }

    [Name("LivingPart1_Theme")]
    public string LivingPart1Theme { get; set; }

    [Name("LivingPart2")]
    public string LivingPart2Name { get; set; }

    [Name("LivingPart2_Theme")]
    public string LivingPart2Theme { get; set; }

    [Name("BibleReading_A")]
    public string BibleReadingClass1Name { get; set; }

    [Name("BibleReading_B")]
    public string BibleReadingClass2Name { get; set; }

    [Name("BibleReading_C")]
    public string BibleReadingClass3Name { get; set; }

    [Name("BibleReading_Study_A")]
    public string BibleReadingStudy { get; set; }

    [Name("Apply1_Description")]
    public string StudentItem1Description { get; set; }

    [Name("Apply2_Description")]
    public string StudentItem2Description { get; set; }

    [Name("Apply3_Description")]
    public string StudentItem3Description { get; set; }

    [Name("Apply4_Description")]
    public string StudentItem4Description { get; set; }

    [Name("Apply1_A")]
    public string StudentItem1Class1StudentName { get; set; }

    [Name("Apply1_B")]
    public string StudentItem1Class2StudentName { get; set; }

    [Name("Apply1_C")]
    public string StudentItem1Class3StudentName { get; set; }

    [Name("Apply1_Asst_A")]
    public string StudentItem1Class1AssistantName { get; set; }

    [Name("Apply1_Asst_B")]
    public string StudentItem1Class2AssistantName { get; set; }

    [Name("Apply1_Asst_C")]
    public string StudentItem1Class3AssistantName { get; set; }

    [Name("Apply2_A")]
    public string StudentItem2Class1StudentName { get; set; }

    [Name("Apply2_B")]
    public string StudentItem2Class2StudentName { get; set; }

    [Name("Apply2_C")]
    public string StudentItem2Class3StudentName { get; set; }

    [Name("Apply2_Asst_A")]
    public string StudentItem2Class1AssistantName { get; set; }

    [Name("Apply2_Asst_B")]
    public string StudentItem2Class2AssistantName { get; set; }

    [Name("Apply2_Asst_C")]
    public string StudentItem2Class3AssistantName { get; set; }

    [Name("Apply3_A")]
    public string StudentItem3Class1StudentName { get; set; }

    [Name("Apply3_B")]
    public string StudentItem3Class2StudentName { get; set; }

    [Name("Apply3_C")]
    public string StudentItem3Class3StudentName { get; set; }

    [Name("Apply3_Asst_A")]
    public string StudentItem3Class1AssistantName { get; set; }

    [Name("Apply3_Asst_B")]
    public string StudentItem3Class2AssistantName { get; set; }

    [Name("Apply3_Asst_C")]
    public string StudentItem3Class3AssistantName { get; set; }

    [Name("Apply4_A")]
    public string StudentItem4Class1StudentName { get; set; }

    [Name("Apply4_B")]
    public string StudentItem4Class2StudentName { get; set; }

    [Name("Apply4_C")]
    public string StudentItem4Class3StudentName { get; set; }

    [Name("Apply4_Asst_A")]
    public string StudentItem4Class1AssistantName { get; set; }

    [Name("Apply4_Asst_B")]
    public string StudentItem4Class2AssistantName { get; set; }

    [Name("Apply4_Asst_C")]
    public string StudentItem4Class3AssistantName { get; set; }

    [Name("Apply1_Study_A")]
    public string StudentItem1Study { get; set; }

    [Name("Apply2_Study_A")]
    public string StudentItem2Study { get; set; }

    [Name("Apply3_Study_A")]
    public string StudentItem3Study { get; set; }

    [Name("Apply4_Study_A")]
    public string StudentItem4Study { get; set; }
}

And I read in the CSV file like this:

using (var reader = new StreamReader(_calendarDBPath))
using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
{
    var records = csv.GetRecords<CLMExplorerHistory>();

    foreach (var record in records)
    {
        ...

At the moment the CSV record is read into this list of public properties. I wanted to know if it could directly read the fields into a more complex object?

Specifcally, the # Classes field has a value of 1, 2 or 3. And for each of these classes (not C# classes - think school) there is similar data.

There are 4 items per class and the description / study is the same in each class. So these CSV fields are common:

[Name("Apply1_Description")]
[Name("Apply2_Description")]
[Name("Apply3_Description")]
[Name("Apply4_Description")]

[Name("BibleReading_Study_A")]
[Name("Apply1_Study_A")]
[Name("Apply2_Study_A")]
[Name("Apply3_Study_A")]
[Name("Apply4_Study_A")]

But the rest are specific to the 3 Classes (schools):

  • These fields belong to Class 1:
[Name("BibleReading_A")]
[Name("Apply1_A")]
[Name("Apply1_Asst_A")]
[Name("Apply2_A")]
[Name("Apply2_Asst_A")]
[Name("Apply3_A")]
[Name("Apply3_Asst_A")]
[Name("Apply4_A")]
[Name("Apply4_Asst_A")]
  • These fields belong to Class 2:
[Name("BibleReading_B")]
[Name("Apply1_B")]
[Name("Apply1_Asst_B")]
[Name("Apply2_B")]
[Name("Apply2_Asst_B")]
[Name("Apply3_B")]
[Name("Apply3_Asst_B")]
[Name("Apply4_B")]
[Name("Apply4_Asst_B")]
  • These fields belong to Class 3:
[Name("BibleReading_C")]
[Name("Apply1_C")]
[Name("Apply1_Asst_C")]
[Name("Apply2_C")]
[Name("Apply2_Asst_C")]
[Name("Apply3_C")]
[Name("Apply3_Asst_C")]
[Name("Apply4_C")]
[Name("Apply4_Asst_C")]

The 3 Classes ca be represented by 3 "StudentClass" objects in a Lists. Something like

StudentClasses
    NumClasses
    Item1Desc
    Item2Desc
    Item3Desc
    Item4Desc
    BibleReadingStudy
    Item1Study
    Item2Study
    Item3Study
    Item4Study
    List<StudentClass>
           BibleReading
           Item1Student
           Item1Assistant
           Item2Student
           Item2Assistant
           Item3Student
           Item3Assistant
           Item4Student
           Item4Assistant

The above is built from:

StudentClasses
    NumClasses:        # Classes
    Item1Desc:         Apply1_Description
    Item2Desc:         Apply2_Description
    Item3Desc:         Apply3_Description
    Item4Desc:         Apply4_Description
    BibleReadingStudy: BibleReading_Study_A
    Item1Study:        Apply1_Study_A
    Item2Study:        Apply2_Study_A
    Item3Study:        Apply3_Study_A
    Item4Study:        Apply4_Study_A
    List<StudentClass>
           BibleReading    BibleReading_A|B|C
           Item1Student    Apply1_A|B|C
           Item1Assistant  Apply1_Asst_A|B|C
           Item2Student    Apply2_A|B|C
           Item2Assistant  Apply2_Asst_A|B|C
           Item3Student    Apply3_A|B|C
           Item3Assistant  Apply3_Asst_A|B|C
           Item4Student    Apply4_A|B|C
           Item5Assistant  Apply4_Asst_A|B|C

Now you know the mapping between fields to the proposed object. Obviously I can create that C# class myself and I can manually transfer the fields into such a structure. But does CvsReader have the ability to directly read into a class like this? Rather than one big list of properties?

Andrew Truckle
  • 17,769
  • 16
  • 66
  • 164
  • 1
    *be improved to aid in code reuse* - I'd be tempted to say don't bother; it's a POCO holder for purposes of parsing data in. Transfer the data you're interested in, to something else that is aligned with what you need it for, such as storing in a relational database. You could, if you wanted, extend the get/set on the peoperties you have so instead of storing in a single backing field, they store in e.g. a dedicated position in an array, so they're easier to enumerate if that makes sense (hard to tell what you would deem to be "better for" because there isn't really any detail on "for what") – Caius Jard Jan 12 '22 at 17:05
  • It's not quite clear what problem you're trying to solve. It might be because you're using the word "Class" two different ways, so it's hard to tell whether you mean `class` or Class. – Scott Hannen Jan 12 '22 at 17:19
  • I don't understand the organization and what you want do with the data. Could you draw an ERM of how the data is supposed to work together in the end? Why does a class have 4 students only, but also 4 assistants? Why are there 3 bible readings only? The bible is a book where we can read much more from. – Thomas Weller Jan 12 '22 at 18:11
  • @CaiusJard Thank for your thoughts. – Andrew Truckle Jan 12 '22 at 19:27
  • @ScottHannen Sorry for any confusion. Naturally we are talking about redesigning a `class`. But, the content of the `class` is to represent the CSV row of data. And, in that date it contains assignment information for three "classes" (like mini school). – Andrew Truckle Jan 12 '22 at 21:55
  • @ThomasWeller I thought it was understood from my 1st question because it was suggested to me to improve this. We have weekly meetings. During the weekly meeting we have up to 3 "classes". In each class we will have 4 "items". The 1st is the bible reading. The other 3 are done by 2 (student & assist). I displayed the relationship of the CSV fields to how they group together already. The linked question showed how i am parsing the data at the moment. I think given the complexity of having to explain what improvements are needed I best leave as is. – Andrew Truckle Jan 12 '22 at 22:02
  • @CaiusJard What you say makes sense. Once I read in the fields. then refactor those fields into a new object better suited for use through my code. – Andrew Truckle Jan 12 '22 at 22:08
  • @ScottHannen I have tried to add more explanation. – Andrew Truckle Jan 12 '22 at 23:14
  • @ThomasWeller I have added more information about the relationship of the fields to the proposed structure to read into. – Andrew Truckle Jan 12 '22 at 23:15
  • When you say "these fields belong to class 1" and then put things with _C, is that a typo and you mean to say "class 3" – Caius Jard Jan 13 '22 at 07:15
  • Any chance you could swap the word Class for Lesson or Course (I can't quite decide which is more appropriate) every time you want to refer to "a session/sessions of targeted learning on a topic" to disambiguate between a school Class and a c# `class` - or have you reliably used Class (not formatted as code) and `class` (formatted as code) when you mean one or the other? – Caius Jard Jan 13 '22 at 07:18
  • @CaiusJard No typos. The csv is another programs file. It used the concept of A, B and C. I use 1, 2 and 3. I can rename Class. – Andrew Truckle Jan 13 '22 at 09:12
  • @cai. Typo. Fixed. – Andrew Truckle Jan 13 '22 at 09:37

2 Answers2

2

But does CvsReader have the ability to directly read into a class like this? Rather than one big list of properties?

I'd say "yes", though I'm not 100% sure of what you're ultimately hoping to create, but you can get cute with what the properties do. I'll give a simple example.

Suppose we have a CSV that is listing Shops and 3 products that need to be purchased from each shop

Shop,Product1,P1Price,Product2,P2Price,Product3,P3Price
Walmart,Eggs,1,Milk,2,Bread,3
BestBuy,PC,4,iPad,5,iPhone,6

You could have your class that holds the props:

class ShopLine
{
  string Shop {get;set;}
  string Product1 {get;set;}
  string Product2 {get;set;}
  string Product3 {get;set;}
  int P1Price {get;set;}
  int P2Price {get;set;}
  int P3Price {get;set;}
}

But noone has a gun to your head saying the data storage has to be like this.. You could:

class Product
{
  string Name {get;set;}
  int Price {get;set;}
}

class ShopLineTrick
{
  string Shop {get;set;}
  string Product1 {get => products[0].Name; set => products[0].Name= value;}
  string Product2 {get => products[1].Name; set => products[1].Name= value;}
  string Product3 {get => products[2].Name; set => products[2].Name= value;}
  int P1Price {get => products[0].Price; set => products[0].Price = value;}
  int P2Price {get => products[1].Price; set => products[1].Price = value;}
  int P3Price {get => products[2].Price; set => products[2].Price = value;}

  List<Product> products;

  ShopLineTrick(){
     //precreate 3 products
     products = Enumerable.Range(1,3).Select(x => new Product()).ToList();
  }
}

At the end of the operation you have some collection of ShopLineTrick that each contain some built hierarchy of Product; Product are a "reusable class from somewhere else" so this operation is like a parsing and mapping in one. What was a POCO for receiving the data in the file before, has become adevice capable of building the hierarchy of your smaller, reusable classes from somewhere else. You could query with LINQ to get them, for example

shopLines.SelectMany(sl => sl.Products). // a straight list of every product

I've explained using an alternative example because I don't quite yet understand what you're hoping to achieve with the data you have: I'm describing an alternative data storage so you can apply it to further your goal. For a recent project, for example, I had to store every version of a property at some point in time and then let a process I didn't control alter some properties of the object, and then examine what had changed. That was easy to do in a loop, with every one of 10 properties using a single object[10] for its storage. At some point in time I would clone the array, let the process alter stuff and then look for array entries in the current that were different to the cloned copy. Backing the properties with an array made that check (and even reset/accept changes) trivial by looping the arrays


The only thing to watch out for is that some kinds of reading CsvH does, it reuses an object you pass, so if you're e.g. doing an async read just be aware that it could read all the lines of the file and youd end up with only the data from the last one (it would be on you to extract the Products list, and renew it each loop pass to prevent overwriting)

Caius Jard
  • 72,509
  • 5
  • 49
  • 80
  • When the CvsReader reads in the properties when I call GetRecords … where does the example fit in? – Andrew Truckle Jan 13 '22 at 11:51
  • 1
    Because CsvReader will make a ShopLine object for every line in the file, so it does 'var x = new ShopLineTrick(); x.Shop = value_of_col_1; x.Product1 = value_of_col_2; xPrice1 = int.Parse(valueA_of_col_3); ... yield return x;` - csvh is making new objects, calling set to set the properties, your trick properties are stashing the data in some more complex object hierarchy as a customized mapping operation.. You don't have to do it, you could just read all into a flat list of ShopLine, then enumerate through it pulling the data out and building N sets of List in the same way.. – Caius Jard Jan 13 '22 at 12:16
  • 1
    Added some more words to the answer – Caius Jard Jan 13 '22 at 12:26
  • I have accepted your answer. Once I have come up with my code I will add it as a second answer for everyone’s info. – Andrew Truckle Jan 13 '22 at 12:52
  • 1
    No probs! Any queries you get along the way, drop a comment or post up again if it's more involved(and @ me her/link to there if you think it makes sense for me to take a look because it led out of something here) – Caius Jard Jan 13 '22 at 13:24
  • I need 3 `List` in an array, and each of those lists needs 5 elements. List[3]. And for each, its size is 5 elements. – Andrew Truckle Jan 13 '22 at 16:21
  • 1
    If they're fixed dimensions and you don't plan to add to them later, it might be better to use an `MSAHistoryItemStudent[3][5]` but don't forget, in either case, initing a collection of collections only makes a holder capable of holding the collections. `var x = new MSAHistoryItemStudent[3][]; for(int i = 0; i < x.Length; i++) x[i] = new MSAHistoryItemStudent[5];` or if you want to init the [5] long array to actual empty instances `for(int i = 0; i < x.Length; i++) x[i] = Enumerable.Range(1, 5).Select(s => new MSAHistoryItemStudent()).ToArray();` – Caius Jard Jan 13 '22 at 16:32
  • 1
    Swap the `ToArray` (and swap the type on the `new`) to be `ToList` if you want a `List<...>[3]` where each of the 3 Lists has 5 empty ... instances in – Caius Jard Jan 13 '22 at 16:35
  • It looks like that my equivalent of `Product` must be a private field because if it is a property then `CsvReader` tries to read all its headers. So I have setit as a private field with a public method to return the object. Trying it out now. – Andrew Truckle Jan 13 '22 at 17:59
  • 1
    I think you can just decorate a prop with `[Ignore]` and csvh will take no interest in it.. https://joshclose.github.io/CsvHelper/examples/configuration/attributes/ – Caius Jard Jan 13 '22 at 18:04
  • Ok, it is not working but it may be side code. In my constructor I have: ` TalkItems = new MSAHistoryItemTalk[6];` Will that create 6 actual items? – Andrew Truckle Jan 13 '22 at 18:11
  • 1
    No, it just creates an array capable of holding 6 items. it's like saying: `string s0; string s1; string s2; string s3; string s4; string s5;` - 6 variables all unintilialized, will cause a null exception (actually the compiler won't even let you use them) – Caius Jard Jan 13 '22 at 18:11
  • So I got to do a for loop – Andrew Truckle Jan 13 '22 at 18:12
  • But `Teaching = new string[3];` will be fine because it is a bsic type? – Andrew Truckle Jan 13 '22 at 18:13
  • 1
    er, sort of. string is immutable and fairly primitive, so realistically you can only say `Teaching[0] = some_string_here`, and that will work... but you couldn't say `Teaching[0].Length` without first setting it to a string instance whereas if it were an `int[3]` they would all be pre-filled with 0 because int is "even more basic" than string - it's a value type that cannot be null and starts out as 0 as its default value – Caius Jard Jan 13 '22 at 18:14
  • Ok. Need to find out how to use enumeradle.repeat for these. New question for that? – Andrew Truckle Jan 13 '22 at 18:17
  • *So I got to do a for loop* - you could `Enumerable.Range(1,6).Select(x => new MSAHistoryItemTalk()).ToArray()` - don't use Repeat - I forgot it will make 3 of the same instance, which will cause a problem. Well.. you could use Repeat, but not to make the instances, only to form a sequence of 6 things. You'd have to do `Enumerable.Repeat(null,6).Select(x => new MSAHistoryItemTalk()).ToArray()` and that's more typing than Range :D - if there was a variation of Repeat that accepted a method, then you could, but alas there isn't – Caius Jard Jan 13 '22 at 18:18
  • In your constructor put `TalkItems = Enumerable.Range(1,6).Select(x => new MSAHistoryItemTalk()).ToArray();` or `TalkItems = new MSAHistoryItemTalk[6]; for(int x = 0; x – Caius Jard Jan 13 '22 at 18:28
  • I am getting lost with it. Let me work at this more manually for moment as I have crash. Then I start new question. – Andrew Truckle Jan 13 '22 at 18:40
  • Linked question - I am nearly there! https://stackoverflow.com/q/70702287/2287576 – Andrew Truckle Jan 13 '22 at 19:49
0

This answer is based on the principles in the accepted answer. I felt I would add it as an expanded explanation for my specific scenario. It may help someone else.


Step 1

I designed a set of classes to represent a meeting week:

public class MSAHistoryItemTalk
{
    public string Name { get; set; }
    public string Theme { get; set; }
    public string Method { get; set; }
}
public class MSAHistoryItemStudent
{
    public string Name { get; set; }
    public string Assistant { get; set; }
    public string Type { get; set; }
    public string Study { get; set; }
}

public class MSAHistoryWeek
{
    public DateTime Week { get; set; }
    public bool Meeting { get; set; }
    public int NumClasses { get; set; }
    public string Host { get; set; }
    public string Cohost { get; set; }
    public string Chairman { get; set; }
    public string AuxCounsellor1 { get; set; }
    public string AuxCounsellor2 { get; set; }
    public string PrayerOpen { get; set; }
    public string PrayerClose { get; set; }
    public string CBSConductor { get; set; }
    public string CBSReader { get; set; }
    public List<MSAHistoryItemTalk> TalkItems { get; set; }
    public List<string> Teaching { get; set; }
    public List<string> StudentItemStudyNumbers { get; set; }
    public List<string> StudentItemDescriptions { get; set; }
    public int NumStudentItems { get; set; }
    public List<MSAHistoryItemStudent>[] StudentItems { get; set; }
    public MSAHistoryWeek()
    {
        TalkItems = Enumerable.Range(1, 6).Select(x => new MSAHistoryItemTalk()).ToList();
        Teaching = Enumerable.Range(1, 3).Select(x => string.Empty).ToList();
        StudentItemStudyNumbers = Enumerable.Range(1, 5).Select(x => string.Empty).ToList();
        StudentItemDescriptions = Enumerable.Range(1, 5).Select(x => string.Empty).ToList();
        StudentItems = new List<MSAHistoryItemStudent>[]
        {
            Enumerable.Range(1, 5).Select(x => new MSAHistoryItemStudent()).ToList(),
            Enumerable.Range(1, 5).Select(x => new MSAHistoryItemStudent()).ToList(),
            Enumerable.Range(1, 5).Select(x => new MSAHistoryItemStudent()).ToList(),
        };
    }

    public override string ToString()
    {
        return Week.ToShortDateString();
    }
}

Step 2

I added an instance of the MSAHistoryWeek object to a modified version of the class used with CsvReader:

public class CLMExplorerHistory
{
    [Format("dd/MM/yyyy")]
    [Name("Date")]
    public DateTime Date { get => HistoryWeek.Week; set => HistoryWeek.Week = value; }

    [BooleanFalseValues(new string[] { "N", "n" })]
    [BooleanTrueValues(new string[] { "Y", "y" })]
    [Name("Meeting?")]
    public bool Meeting { get => HistoryWeek.Meeting; set => HistoryWeek.Meeting = value; }

    [Name("# Classes")]
    public int Classes { get => HistoryWeek.NumClasses; set => HistoryWeek.NumClasses = value; }

    [Name("Chairman")]
    public string Chairman { get => HistoryWeek.Chairman; set => HistoryWeek.Chairman = value; }

    [Name("AuxCounselor B")]
    public string AuxCounsellor1 { get => HistoryWeek.AuxCounsellor1; set => HistoryWeek.AuxCounsellor1 = value; }

    [Name("AuxCounselor C")]
    public string AuxCounsellor2 { get => HistoryWeek.AuxCounsellor2; set => HistoryWeek.AuxCounsellor2 = value; }

    [Name("PrayerOpen")]
    public string PrayerOpen { get => HistoryWeek.PrayerOpen; set => HistoryWeek.PrayerOpen = value; }

    [Name("PrayerClose")]
    public string PrayerClose { get => HistoryWeek.PrayerClose; set => HistoryWeek.PrayerClose = value; }

    [Name("CBS")]
    public string CBSConductor { get => HistoryWeek.CBSConductor; set => HistoryWeek.CBSConductor = value; }

    [Name("CBS_Read")]
    public string CBSReader { get => HistoryWeek.CBSReader; set => HistoryWeek.CBSReader = value; }

    [Name("TreasuresTalk")]
    public string TreasuresTalkName { get => HistoryWeek.TalkItems[0].Name; set => HistoryWeek.TalkItems[0].Name = value; }

    [Name("TreasuresTalk_Theme")]
    public string TreasuresTalkTheme { get => HistoryWeek.TalkItems[0].Theme; set => HistoryWeek.TalkItems[0].Theme = value; }

    [Name("TreasuresDigging")]
    public string SpiritualGemsName { get => HistoryWeek.TalkItems[1].Name; set => HistoryWeek.TalkItems[1].Name = value; }

    [Name("LivingPart1")]
    public string LivingPart1Name { get => HistoryWeek.TalkItems[3].Name; set => HistoryWeek.TalkItems[3].Name = value; }

    [Name("LivingPart1_Theme")]
    public string LivingPart1Theme { get => HistoryWeek.TalkItems[3].Theme; set => HistoryWeek.TalkItems[3].Theme = value; }

    [Name("LivingPart2")]
    public string LivingPart2Name { get => HistoryWeek.TalkItems[4].Name; set => HistoryWeek.TalkItems[4].Name = value; }

    [Name("LivingPart2_Theme")]
    public string LivingPart2Theme { get => HistoryWeek.TalkItems[4].Theme; set => HistoryWeek.TalkItems[4].Theme = value; }

    [Name("BibleReading_A")]
    public string BibleReadingClass1Name { get => HistoryWeek.StudentItems[0][0].Name; set => HistoryWeek.StudentItems[0][0].Name = value; }

    [Name("BibleReading_B")]
    public string BibleReadingClass2Name { get => HistoryWeek.StudentItems[1][0].Name; set => HistoryWeek.StudentItems[1][0].Name = value; }

    [Name("BibleReading_C")]
    public string BibleReadingClass3Name { get => HistoryWeek.StudentItems[2][0].Name; set => HistoryWeek.StudentItems[2][0].Name = value; }

    [Name("BibleReading_Study_A")]
    public string BibleReadingStudy { get => HistoryWeek.StudentItemStudyNumbers[0]; set => HistoryWeek.StudentItemStudyNumbers[0] = value; }

    [Name("Apply1_Description")]
    public string StudentItem1Description { get => HistoryWeek.StudentItemDescriptions[1]; set => HistoryWeek.StudentItemDescriptions[1] = value; }

    [Name("Apply2_Description")]
    public string StudentItem2Description { get => HistoryWeek.StudentItemDescriptions[2]; set => HistoryWeek.StudentItemDescriptions[2] = value; }

    [Name("Apply3_Description")]
    public string StudentItem3Description { get => HistoryWeek.StudentItemDescriptions[3]; set => HistoryWeek.StudentItemDescriptions[3] = value; }

    [Name("Apply4_Description")]
    public string StudentItem4Description { get => HistoryWeek.StudentItemDescriptions[4]; set => HistoryWeek.StudentItemDescriptions[4] = value; }

    [Name("Apply1_A")]
    public string StudentItem1Class1StudentName { get => HistoryWeek.StudentItems[0][1].Name; set => HistoryWeek.StudentItems[0][1].Name = value; }

    [Name("Apply1_B")]
    public string StudentItem1Class2StudentName { get => HistoryWeek.StudentItems[1][1].Name; set => HistoryWeek.StudentItems[1][1].Name = value; }

    [Name("Apply1_C")]
    public string StudentItem1Class3StudentName { get => HistoryWeek.StudentItems[2][1].Name; set => HistoryWeek.StudentItems[2][1].Name = value; }

    [Name("Apply1_Asst_A")]
    public string StudentItem1Class1AssistantName { get => HistoryWeek.StudentItems[0][1].Assistant; set => HistoryWeek.StudentItems[0][1].Assistant = value; }

    [Name("Apply1_Asst_B")]
    public string StudentItem1Class2AssistantName { get => HistoryWeek.StudentItems[1][1].Assistant; set => HistoryWeek.StudentItems[1][1].Assistant = value; }

    [Name("Apply1_Asst_C")]
    public string StudentItem1Class3AssistantName { get => HistoryWeek.StudentItems[2][1].Assistant; set => HistoryWeek.StudentItems[2][1].Assistant = value; }

    [Name("Apply2_A")]

    public string StudentItem2Class1StudentName { get => HistoryWeek.StudentItems[0][2].Name; set => HistoryWeek.StudentItems[0][2].Name = value; }

    [Name("Apply2_B")]
    public string StudentItem2Class2StudentName { get => HistoryWeek.StudentItems[1][2].Name; set => HistoryWeek.StudentItems[1][2].Name = value; }

    [Name("Apply2_C")]
    public string StudentItem2Class3StudentName { get => HistoryWeek.StudentItems[2][2].Name; set => HistoryWeek.StudentItems[2][2].Name = value; }

    [Name("Apply2_Asst_A")]
    public string StudentItem2Class1AssistantName { get => HistoryWeek.StudentItems[0][2].Assistant; set => HistoryWeek.StudentItems[0][2].Assistant = value; }

    [Name("Apply2_Asst_B")]
    public string StudentItem2Class2AssistantName { get => HistoryWeek.StudentItems[1][2].Assistant; set => HistoryWeek.StudentItems[1][2].Assistant = value; }

    [Name("Apply2_Asst_C")]
    public string StudentItem2Class3AssistantName { get => HistoryWeek.StudentItems[2][2].Assistant; set => HistoryWeek.StudentItems[2][2].Assistant = value; }

    [Name("Apply3_A")]
    public string StudentItem3Class1StudentName { get => HistoryWeek.StudentItems[0][3].Name; set => HistoryWeek.StudentItems[0][3].Name = value; }

    [Name("Apply3_B")]
    public string StudentItem3Class2StudentName { get => HistoryWeek.StudentItems[1][3].Name; set => HistoryWeek.StudentItems[1][3].Name = value; }

    [Name("Apply3_C")]
    public string StudentItem3Class3StudentName { get => HistoryWeek.StudentItems[2][3].Name; set => HistoryWeek.StudentItems[2][3].Name = value; }

    [Name("Apply3_Asst_A")]
    public string StudentItem3Class1AssistantName { get => HistoryWeek.StudentItems[0][3].Assistant; set => HistoryWeek.StudentItems[0][3].Assistant = value; }

    [Name("Apply3_Asst_B")]
    public string StudentItem3Class2AssistantName { get => HistoryWeek.StudentItems[1][3].Assistant; set => HistoryWeek.StudentItems[1][3].Assistant = value; }

    [Name("Apply3_Asst_C")]
    public string StudentItem3Class3AssistantName { get => HistoryWeek.StudentItems[2][3].Assistant; set => HistoryWeek.StudentItems[2][3].Assistant = value; }

    [Name("Apply4_A")]
    public string StudentItem4Class1StudentName { get => HistoryWeek.StudentItems[0][4].Name; set => HistoryWeek.StudentItems[0][4].Name = value; }

    [Name("Apply4_B")]
    public string StudentItem4Class2StudentName { get => HistoryWeek.StudentItems[1][4].Name; set => HistoryWeek.StudentItems[1][4].Name = value; }

    [Name("Apply4_C")]
    public string StudentItem4Class3StudentName { get => HistoryWeek.StudentItems[2][4].Name; set => HistoryWeek.StudentItems[2][4].Name = value; }

    [Name("Apply4_Asst_A")]
    public string StudentItem4Class1AssistantName { get => HistoryWeek.StudentItems[0][4].Assistant; set => HistoryWeek.StudentItems[0][4].Assistant = value; }

    [Name("Apply4_Asst_B")]
    public string StudentItem4Class2AssistantName { get => HistoryWeek.StudentItems[1][4].Assistant; set => HistoryWeek.StudentItems[1][4].Assistant = value; }

    [Name("Apply4_Asst_C")]
    public string StudentItem4Class3AssistantName { get => HistoryWeek.StudentItems[2][4].Assistant; set => HistoryWeek.StudentItems[2][4].Assistant = value; }

    [Name("Apply1_Study_A")]
    public string StudentItem1Study { get => HistoryWeek.StudentItemStudyNumbers[1]; set => HistoryWeek.StudentItemStudyNumbers[1] = value; }

    [Name("Apply2_Study_A")]
    public string StudentItem2Study { get => HistoryWeek.StudentItemStudyNumbers[2]; set => HistoryWeek.StudentItemStudyNumbers[2] = value; }

    [Name("Apply3_Study_A")]
    public string StudentItem3Study { get => HistoryWeek.StudentItemStudyNumbers[3]; set => HistoryWeek.StudentItemStudyNumbers[3] = value; }

    [Name("Apply4_Study_A")]
    public string StudentItem4Study { get => HistoryWeek.StudentItemStudyNumbers[4]; set => HistoryWeek.StudentItemStudyNumbers[4] = value; }

    [Ignore]
    public MSAHistoryWeek HistoryWeek;

    public CLMExplorerHistory()
    {
        HistoryWeek = new MSAHistoryWeek();
    }
}

As you can see, it follows the principles in the accepted answer, of mapping the public CSV properties to the enhanced MSAHistoryWeek object.


The Result

This refactoring has made it much easier to work with the data (ie. writing to XML).

Andrew Truckle
  • 17,769
  • 16
  • 66
  • 164