-1

I need to upload a report to other different system, the system have their own predefined structure of the reports which they accept. Those report on our system is generated on the basis of the code which the user selects.

User does not care about the column they wanted to export on the reports, they only need to select the code from the UI. Our system will generate, download the right format of the CSV with reference to code and that will be used as report to upload to another system.

The headers and the number of columns on the CSV files differ by the type of code user selects.

How i have approached by far:

public ActionResult GetFileResult(string code)
{
    var record = new Employee().GetEmployeeData(code);
    var csvResult = GetCSVResult(code, record);

    return csvResult;
}

private string GetCSVResult(string code, List<Employee> employees)
{
    //How can i model here the GetCSVResult to convert 
    //the List of employees to CSV with refrence to code

    //the value on the code will determine which format to used for the csv result.
}

Here in GetCSVResult method i can use multiple if or switch-case statement to call the different method which has it's own implementation of converting list to CSV, but there are at least 20 different CSV configuration which will lead to multiple if statement and a lot of method.

public class Employee
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string DOB { get; set; }
    public string StartDate { get; set; }
    public string SSN { get; set; }
    public bool PreviousEmployee { get; set; } 
    public List<Employee> GetEmployeeData(string code)
    {
        return new List<Employee>();
    }
}

The format for the initial data are in the following structure. i.e the value returned from GetEmployeeData

FirstName | LastName    |   SSN         |   StartDate   |   DOB         | PreviousEmployee
------------------------------------------------------------------------------------------ 
Jane      | Smith       |   111111121   |   01/03/2018  |   01/01/1983  | true
John      | Smith       |   111111111   |   01/01/2018  |   01/01/1970  | true
Jeff      | Smith       |   111111122   |   01/03/2018  |   01/01/1983  | false

Now i have to convert this data into CSV(or some into XML) files with different file configuration.

Like Examples:

Some Format could be like:

Format 1:

FirstName,LastName,SSN,AppStartDate,AppDOB Jane,Smith,111111121,01/03/2018,01/01/1983 John,Smith,111111111,01/01/2018,01/01/1970 Jeff,Smith,111111122,01/03/2018,01/01/1983

Format 2:

EMP_FIRST_NAME,EMP_LAST_NAME,EMP_SSN,EMP_DOB,EMP_JOB_START_DATE,PREV_EMPLOYED_BY_EMPLOYER Jane,Smith,111111121,01/03/2018,01/01/1983,Y John,Smith,111111111,01/01/2018,01/01/1970,Y Jeff,Smith,111111122,01/03/2018,01/01/1983,N

The format in the Examples differ with the type of Code. The format are in structure defined by the value on the code.

So, if there are any suggestion for some generic way to this type of scenario? or Any suggestion for some pattern?

For converting to CSV i am looking at this thread

https://medium.com/@utterbbq/c-serializing-list-of-objects-to-csv-9dce02519f6b

Rasik
  • 1,961
  • 3
  • 35
  • 72

2 Answers2

0

I'm not an expert in CSV export, and having an example of what you tried in GetCSVResult would have been great. But, my guess is:

Just in case in misunderstood your issue, have you looked at those links? Fastest way to convert a list of objects to csv with each object values in a new line and Converting a generic list to a CSV string

Now if I undersantd well, the user can decide which column to export or not and to change its name if needed. So you could have an object decribing such a column. For example:

public class ColumnCSV
{
    public int OriginalColumnIndex{ get; set; }
    public string Name { get; set; }
    public bool ExpectedInCSV { get; set; } 
}

Then you store a list of those columns based on user input, and you can easily write a lovely switch to handle the export based on the column index.

Mikitori
  • 589
  • 9
  • 21
  • i am looking at the thread https://medium.com/@utterbbq/c-serializing-list-of-objects-to-csv-9dce02519f6b, and according to the requirement user can't select which column to export. they are predefined, user can chose only the code. The column name and the column configuration is dependent upon the code. – Rasik Sep 13 '18 at 07:26
  • Then I'm a bit lost, I'm not sure this code really fits your need, Is it mandatory for you to use this? What do you really want to achieve, without talking about what you already did? Even if the user cannot precisely decide for each column, based on your example selecting different configuration through the "code" string may impact on the number of columns. Despite the names and the visibility of columns, is there other possible impact in chosing an other one? – Mikitori Sep 13 '18 at 07:38
0

If you didn't like switch statements and you have some statically typed key, you might wanna use something like this:

static readonly string _divider = ",";

static readonly Dictionary<string, (string header, Func<Employee, string[]> employee)> _configurations
            = new Dictionary<string, (string, Func<Employee, string[]>)>() {
                ["code1"] = ("FirstName,LastName,SSN,AppStartDate,AppDOB",
                        employee => new string[] {
                                    employee.FirstName,
                                    employee.LastName,
                                    employee.SSN,
                                    employee.StartDate,
                                    employee.DOB }),
                ["code2"] = ("EMP_FIRST_NAME,EMP_LAST_NAME,EMP_SSN,EMP_DOB,EMP_JOB_START_DATE,PREV_EMPLOYED_BY_EMPLOYER",
                        employee => new string[] {
                                    employee.FirstName,
                                    employee.LastName,
                                    employee.SSN,
                                    employee.DOB,
                                    employee.StartDate,
                                    employee.PreviousEmployee ? "Y" : "N" })
                    //...
                };

private string GetCSVResult( string code, List<Employee> employees )
        => _configurations[code].header
            + Environment.NewLine
            + employees.Select( e => _configurations[code]
                                     .employee( e )
                                     .Join( _divider ) )
                       .Join( Environment.NewLine );

I'm using this custom extension here:

public static class Extensions {
    public static string Join<T>( this IEnumerable<T> array, string divider = "," ) => string.Join( divider, array );
}

Also pay attention to static readonly Dictionary. You might wanna use Concurrent one instead or use some lock mechanism to copy and then execute (not sure about all of it, tbh). And it might be wrapped into some sort of class for cleaner view (usage is kinda clean, you might wanna rid off of _divider, as we talking about CSV).

It's not optimal (neither on memory, nor on cpu), there's no cache for the same results and all of that stuff (and I'm pretty sure string[] result is not the best way to concat, whether it's ever OK to concat strings; also notice, that generic version of string.Join is not that optimal). It's all up to you, I hope you get the idea with the dictionary.

The other way is to use Reflection. It's not that fast and nice to do so. OOD-killing magic with a scent of a schizophrenia. Yeah, you could assign Attributes and then somehow execute it, but it's ugly anyway (several attributes or lots of arguments for every configuration).

There are several other ways to kinda 'compile' this funcs, using pure IL or delegates as a signature + System.Linq.Expressions (it involves Reflection either, but only in a matter of first usage).

And if you want some truly clean solution, you should think about abstract conversion factory with injecting realization and custom script language for structure description, not to hardcode it. But why? This code will return kinda-correct CSV. Hope it'll help.

Namynnuz
  • 1
  • 1
  • i will have at least 20+ code, thus it will have at least 20+ configuration for CSV. So, do i need to statically implement the code like ["code1"], ["code2"]...? – Rasik Sep 13 '18 at 08:51
  • The point is to set right position for every used element of your class, with proper formatting, if needed. "code1" and so on is just an example of your _string code_ argument, 'coz I have no idea what kinda code it could be. You can also implement it in static constructor of you class, with classic Add or AddRange, if you want to. In this case, It's way better than Reflection, which usually causing runtime errors you have to catch. If it compiles, it will work. – Namynnuz Sep 13 '18 at 09:09
  • can you point on how to implement by `abstract conversion factory with injecting realization and custom script language for structure description` ? i think by using abstract factory the solution seems clean and it will be a better approach? – Rasik Sep 13 '18 at 09:10