0

I have the following classes to work with and I want to export/flatten the contents to a csv file. I have tried to use SelectMany but I can’t find a way of selecting fields for my csv file from both Company level and the underlying level with SalesArea, Node and EthernetArea. Can someone explain what I need to do to accomplish this?

public class Rootobject
{
    public Company[] Companies { get; set; }
}
public class Company
{
    public string ORG_NAME { get; set; }
    public SalesArea[] Salesareas { get; set; }
    public Node[] Nodes { get; set; }
    public EthernetArea[] Ethernetareas { get; set; }
}
public class SalesArea
{
    public string OBJ_NAME { get; set; }
    public string AREA_CTYPE { get; set; }
}
public class Node
{
    public string OBJ_NAME { get; set; }
    public object BUILDING { get; set; }
}
public class EthernetArea
{
    public string OBJ_NAME { get; set; }
    public string AREA_CTYPE { get; set; }
    public Product[] Products { get; set; }
}
public class Product
{
    public string BANDWIDTH_A { get; set; }
    public string CONNECTION_TYPE { get; set; }
}
Christer
  • 3
  • 2
  • 2
    Do you really, really need to use CSV? Can't you use JSON or XML instead? – Rui Jarimba Aug 03 '18 at 13:23
  • 1
    You 1st need to determine how your CSV will look like with different sets and lengths of data. – Andrei Tătar Aug 03 '18 at 13:25
  • Yes,I want to be able to import the csv to Excel. – Christer Aug 03 '18 at 13:25
  • Check FileHelpers, it's an old library but it might be useful to you: https://www.filehelpers.net/example/MasterDetail/MasterDetailCustomSelector/ – Rui Jarimba Aug 03 '18 at 13:28
  • 2
    You need a class that represent the flattern data like you want it in the CSV file. and then https://stackoverflow.com/questions/6428940/how-to-flatten-nested-objects-with-linq-expression. – Drag and Drop Aug 03 '18 at 13:28
  • I know which fields I want for my CSV but I can' t find the syntax for selecting fields from several arrays in one command. – Christer Aug 03 '18 at 13:29
  • It would help me at least if you put an example of how you want the data flattened. It does seem to make way more sense as XML or JSON though given the structure. – KSib Aug 03 '18 at 13:30
  • The book example is simpler than my structure, I have three arrays at the same level and I can only select from one of them. – Christer Aug 03 '18 at 13:36
  • The linked question covert this properly. You didn't address the result model. As there is many object property with same name. – Drag and Drop Aug 03 '18 at 13:49

2 Answers2

1

I'm sure there should be another way to do this, but anyways here it's how I did it...

If I understand correctly you want to 'Denormalize' the data and export the collection to a CSV.

I used many Select SelectMany and Join

var root = new Rootobject
    {
        Companies = new Company[] {
            new Company
        {
            ORG_NAME = "Co",
            Salesareas = new SalesArea[]{
            new SalesArea {
                OBJ_NAME = "Sa",
                AREA_CTYPE = "SaAr",
            }
        },
            Nodes = new Node[] {
            new Node {
                OBJ_NAME = "No",
                BUILDING = "NoBu"
            }
        },
            Ethernetareas = new EthernetArea[] {
            new EthernetArea {
                OBJ_NAME = "Et",
                AREA_CTYPE = "EtAr",
                Products = new Product[] {
                    new Product {
                        BANDWIDTH_A = "ProA",
                        CONNECTION_TYPE = "ProCon"
                    }
                }
            }
        }
        },
        new Company
        {
            ORG_NAME = "Co2",
            Salesareas = new SalesArea[]{
            new SalesArea {
                OBJ_NAME = "Sa2",
                AREA_CTYPE = "SaAr2",
            }
        },
            Nodes = new Node[] {
            new Node {
                OBJ_NAME = "No2",
                BUILDING = "NoBu2"
            }
        },
            Ethernetareas = new EthernetArea[] {
            new EthernetArea {
                OBJ_NAME = "Et2",
                AREA_CTYPE = "EtAr2",
                Products = new Product[] {
                    new Product {
                        BANDWIDTH_A = "ProA2",
                        CONNECTION_TYPE = "ProCon2"
                    },
                    new Product {
                        BANDWIDTH_A = "ProA3",
                        CONNECTION_TYPE = "ProCon3"
                    }
                }
            }
        }
        }
    }
    };

    var sas = root.Companies.SelectMany(x => x.Salesareas.Select(y => new { Company = x.ORG_NAME, SalesName = y.OBJ_NAME, SalesAreaType = y.AREA_CTYPE }));
    var nodes = root.Companies.SelectMany(x => x.Nodes.Select(y => new { Company = x.ORG_NAME, NodesName = y.OBJ_NAME, NodeBuilding = y.BUILDING }));
    var ethes = root.Companies.SelectMany(x => x.Ethernetareas.SelectMany(y => y.Products .Select(z => new { Company = x.ORG_NAME, EthernetName = y.OBJ_NAME, EthernetArea = y.AREA_CTYPE, BandwithA = z.BANDWIDTH_A, ConnnectionType = z.CONNECTION_TYPE })));

    sas.Join(nodes, x => x.Company, y => y.Company, (x, y) => new {x.Company, x.SalesName, x.SalesAreaType, y.NodesName, y.NodeBuilding})
        .Join(ethes, x => x.Company, y => y.Company, (x, y) => new {x.Company, x.SalesName, x.SalesAreaType, x.NodesName, x.NodeBuilding, y.EthernetName, y.EthernetArea, y.BandwithA, y.ConnnectionType})
        .Dump();
  • Dump() is a LinqPad Extension Method

And here is the result...

enter image description here

With the Collection you can use CsvHelper as recommended by @Drag And Drop in order to generate the CSV file.... :)

Edit

I just realized that @Drag And Drop code is more straightforward, cleaner and better.... :)

jjchiw
  • 4,375
  • 1
  • 29
  • 30
  • Thx for the last paragraph, and for showing the SelectMany syntax for this question. I could not bring myself to admit I wrote one too. It's so messy syntax that there is no way to format it in a readable way. – Drag and Drop Aug 08 '18 at 08:56
0

Define a class that will be the representation of what you want in your CSV. As multiple object properties have the same name in your model.
In your comment you choosed to prefix your properties with the class name so you should have something like :

public class CompanyCSV
{
    public string Company_ORG_NAME { get; set; }

    public string SalesArea_OBJ_NAME { get; set; }
    public string SalesArea_AREA_CTYPE { get; set; }

    public string Node_OBJ_NAME { get; set; }
    public string Node_BUILDING { get; set; }

    public string EthernetArea_OBJ_NAME { get; set; }
    public string EthernetArea_AREA_CTYPE { get; set; }

    public string Product_BANDWIDTH_A { get; set; }
    public string Product_CONNECTION_TYPE { get; set; }
}

Then simply:

var result = from c in dataInput.Companies
                from s in c.Salesareas
                from n in c.Nodes
                from e in c.Ethernetareas
                from p in e.Products
                select new CompanyCSV
                {
                    Company_ORG_NAME = c.ORG_NAME,

                    SalesArea_OBJ_NAME = s.OBJ_NAME,
                    SalesArea_AREA_CTYPE = s.AREA_CTYPE,

                    Node_OBJ_NAME = n.OBJ_NAME,
                    Node_BUILDING = n.BUILDING.ToString(),

                    EthernetArea_OBJ_NAME = e.OBJ_NAME,
                    EthernetArea_AREA_CTYPE = e.AREA_CTYPE,

                    Product_BANDWIDTH_A = p.BANDWIDTH_A,
                    Product_CONNECTION_TYPE = p.CONNECTION_TYPE
                };


foreach (var line in result)
{ // Don't use this to generate your csv this is simply to show the result
    Console.WriteLine($"{line.c},{line.s},{line.n},{line.e},{line.p}, ..etc ..");
}

To write your CSV you can use CsvHelper. This will help you handlevalue with comma or quote. Do not generate your CSV by hand.

If your collection (Node[], Company[], etc.) can be null you should guard this edge case using a null-coalescing operator in the getter, like :

private Node[] nodes;
public Node[] Nodes {
    get { return nodes ?? (nodes = new Node[] { }); }
    set { nodes = value; }
}
Drag and Drop
  • 2,672
  • 3
  • 25
  • 37
  • How do I define the class you are suggsting? I tried with this but it didn't work: public class dataInput { public string Companies { get; set; } public string Salesareas { get; set; } public string Nodes { get; set; } public string Ethernetareas { get; set; } public string Products { get; set; } } – Christer Aug 06 '18 at 07:11
  • Like any other class: `public class MyCSV{ public type propertyName; ...` with understandable name etc. Then you modify the `select` part of the query to `select new MyCSV{ propertyName = value,...`. – Drag and Drop Aug 06 '18 at 07:17
  • It's a confusing edit you have here: `public string Companies`? Companies is an array of Company, that have one field ORG_NAME. And 3 arrays Salesareas, Nodes, Ethernetareas. This is a lot of information in a single string. Imagine that every column of your CSV is an property. no amtter where this property is in the complexe object. Write down every csv column you have, and nex to it write where it is in the object. The 1rst part the list of column will be your output object definition – Drag and Drop Aug 06 '18 at 07:22
  • Thanks a lot for your advices, I can now produce the output for Company and Salesarea. The problem I have now is selecting the Nodes. In my input data they can be NULL but they can also consist of several Node objects. My current declaration in my class MyCSV for the field OBJ_NAME (from Nodes) is: public string Nodes_OBJ_NAME { get; set; } I guess that this declaration that causes the problem? How should I declare this field it so it matches the corresponding field in the class Node? – Christer Aug 06 '18 at 08:54
  • If for exemple, Nodes= null. You have an issue, 'NullReferenceException'. Why node is not an empty collection? Imagine someone that may or may not have kids, if you ask him how many kids he has, do he has to : Answer 1/. Have a stroke and die. 2/. Count them and answer 0. If the simple evocation of the word kid must kill him then null is fine. – Drag and Drop Aug 06 '18 at 09:15
  • Simply change your Property `Nodes` like `private Node[] nodes; public Node[] Nodes { get { return nodes ?? new Node[] { }; } set { nodes = value; } }`. This way you won't have issue with null and you have nothing to change expect 2 lines in your definition, for each nullable – Drag and Drop Aug 06 '18 at 09:53
  • I have update my answer to make it more clear base on your comments. I hope it will be enought. – Drag and Drop Aug 06 '18 at 11:14