0

So I'll explain my situation first. I have a WPF View for my customer that is populated based on SQL strings that the customer defines. They can change these and add/remove these at any point and the structure of the result set is not in my control. My expected output for this is

  1. Populating the DataGrid at runtime without prior knowledge of the structure so using AutoGenerateColumns and providing dataTable.DefaultView as the ItemsSource for the DataGrid. This is bound to my DataGrid.
GetItemsSource = dataTable.DefaultView;
  1. Export this DataGrid to a CSV for the customer to check whenever they want.

Now I already have a Generic List function to Save to CSV but since the structure is not known I can't change my dataTable to a list to use this.

My current solution is Save To CSV function that uses a dataTable instead of a List.

Is there some other type of data structure I could use instead of dataTable that would make using my generic function possible or do I have just have an extra Save To CSV function just for this scenario?

UPDATE

My generic list function

public static void SaveToCsv<T>(List<T> data, string filePath) where T : class
        {
            CreateDirectoryIfNotExists(filePath);

            List<string> lines = new();
            StringBuilder line = new();

            if (data == null || data.Count == 0)
            {
                throw new ArgumentNullException("data", "You must populate the data parameter with at least one value.");
            }
            var cols = data[0].GetType().GetProperties();

            foreach (var col in cols)
            {
                line.Append(col.Name);
                line.Append(",");
            }

            lines.Add(line.ToString().Substring(0, line.Length - 1));

            foreach (var row in data)
            {
                line = new StringBuilder();

                foreach (var col in cols)
                {
                    line.Append(col.GetValue(row));
                    line.Append(",");
                }

                lines.Add(line.ToString().Substring(0, line.Length - 1));
            }
            System.IO.File.WriteAllLines(filePath, lines);
        }

My current Data Table function

public static void SaveToCsv(DataTable data, string filePath)
        {
            CreateDirectoryIfNotExists(filePath);

            List<string> lines = new();
            StringBuilder line = new();

            if(data == null)
            {
                throw new ArgumentNullException("data", "The DataTable has no values to Save to CSV.");
            }

            IEnumerable<string> columnNames = data.Columns.Cast<DataColumn>().Select(column => column.ColumnName);
            line.AppendLine(string.Join(",", columnNames));

            lines.Add(line.ToString().Substring(0, line.Length - 3));
            int prevlinelength = line.Length - 1;

            foreach (DataRow row in data.Rows)
            {
                IEnumerable<string> fields = row.ItemArray.Select(field => field.ToString());
                line.AppendLine(string.Join(",", fields));

                lines.Add(line.ToString().Substring(prevlinelength + 1, line.Length - 3 - prevlinelength));
                prevlinelength = line.Length - 1;
            }
            File.WriteAllLines(filePath, lines);
        }
SahilSyal
  • 1
  • 1
  • What is the problem with the DataTable? – ProgrammingLlama Aug 12 '21 at 05:57
  • @Llama Just duplicated code, I have no other issues with the solution. Performance isn't critical in this scenario and it works as expected – SahilSyal Aug 12 '21 at 05:58
  • Sounds like a code organization problem? If you had a "write to file" method that writes an object array to csv you can have a "save to csv" method that takes a datatable (and repeatedly passes the row's ItemArray to the writer method) and another overload of it that takes a List (and does what with it, I'm not sure.. something reflective? however it works, turn it into an object array for writing). We cannot really help further unless you show us the code you have written – Caius Jard Aug 12 '21 at 06:20
  • Added the code for saving the list and dataTable as CSV to my question – SahilSyal Aug 12 '21 at 06:24
  • You can build types at run time. You can generate code using emit and build a dll even. If the user was building custom views or reports or some such and re-used them that might be worthwhile. I am doubtful from your description though. – Andy Aug 12 '21 at 07:43
  • Yeah it's just calls to SQL tables that are for example getting all rows in Orders where Resource is not assigned but only getting one or two columns but sometimes they take the whole table. Their is no standard way they build those SQL queries, they don't work on views or tables directly. They even have joins sometimes. Will look up emit to get a bit of a learning experience nonetheless. – SahilSyal Aug 12 '21 at 08:35
  • Since your code is reflection based, reading all public properties and their values, you don't need to know the type at all at compile-time. You might simply pass a non generic `IEnumerable` / `ICollection`/ `IList` to SaveToCsv. – lidqy Aug 12 '21 at 08:45
  • But how will I go about actually passing the dataTable as an `IEnumerable`/`ICollection`/`IList`. DataTable doesn't implement those If I'm not wrong? Won't I still be stuck at trying to convert my datatable to a defined type? Or were you suggesting as an improvement to my current situation? – SahilSyal Aug 12 '21 at 08:50

1 Answers1

0

Is it possible to convert a DataTable to IEnumerable where the T can not be defined at compile time and is not known beforehand?

you can create generic objects at runtime, but it is not simple, so I would avoid it if possible.

Is there some other type of data structure I could use instead of dataTable that would make using my generic function possible or do I have just have an extra Save To CSV function just for this scenario?

You could simply convert the Rows property on your datatable and convert it to a List<DataRow> and give to your function. But it would probably not do what you want.

What you need is a some way to convert a DataRow into an object of a class with properties for each column, and while it is possible to create classes from a database model, it will be a lot of work to do so at runtime. I would guess far more than your current solution.

To conclude, keep your current solution if it works. Messing around with reflection and runtime code generation will just make things more complicated.

JonasH
  • 28,608
  • 2
  • 10
  • 23
  • Don't want to over-engineer a solution when 10 lines of code is doing the job right now. I was more looking to see if I missed something simple when I built this code. It's for a base library and the caller is a client project. Like you said, having so much more code to solve a minor issue would be detrimental. Thanks for the information. Will look into the link for a learning experience. – SahilSyal Aug 12 '21 at 08:30