0

I am looking for c# code to convert ADO.NET Datatable to csv file, However I want to save/restore

  • columns name,
  • column data Type and
  • column Value

in csv. Most of the solution I have found restores the datatable from CSV in string column type. I also want that nullable values should be restored as DBNull.Value. DateTime column should be saved and restored as DateTime Type only. The concept is to fill datatable using DataAdapter from Oracle/Sqlserver database and later save that table to CSV file and later restore from CSV.

I have used the code from below link to save DataTable to CSV file using DataTableExtensions class c# datatable to csv

For reading the CSV file back to DataTable I used the below Link http://www.codeproject.com/Articles/11698/A-Portable-and-Efficient-Generic-Parser-for-Flat-F

The Problem is when I restore the CSV file to datatable I have to create Entity from DataTable rows. But They throw Exception on InvalidCast.

Community
  • 1
  • 1
Hemendr
  • 673
  • 6
  • 12

1 Answers1

1

Assuming that you want to store the column-name in the first and the types in the second line and the data begins in the third line, you could use following code. Sample data:

DataTable tblExport = new DataTable();
tblExport.Columns.Add("ID", typeof(int));
tblExport.Columns.Add("Name", typeof(string));
tblExport.Columns.Add("DateofBirth", typeof(DateTime)).AllowDBNull = false;
tblExport.Columns.Add("DateofDeath", typeof(DateTime)).AllowDBNull = true;

tblExport.Rows.Add(1, "Tim", new DateTime(1973, 7, 9), DBNull.Value);
tblExport.Rows.Add(2, "Jim", new DateTime(1953, 3, 19), new DateTime(2011, 1, 2));
tblExport.Rows.Add(3, "Toby", new DateTime(1983, 4, 23), DBNull.Value);

Since you need to convert all values to string with value.ToString i'm changing the culture to InvariantCulture at the beginning to force a specific DateTime format, store the old so that you can enable it again at the end. I hope the code is self-explaining:

var oldCulture = CultureInfo.CurrentCulture;
System.Threading.Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;

write DataTable to csv-file

string delimiter = "\t"; // tab separated
StringBuilder sb = new StringBuilder();
// first line column-names
IEnumerable<string> columnNames = tblExport.Columns.Cast<DataColumn>()
    .Select(column => column.ColumnName);
sb.AppendLine(string.Join(delimiter, columnNames));
// second line column-types
IEnumerable<string> columnTypes = tblExport.Columns.Cast<DataColumn>()
    .Select(column => column.DataType.ToString());
sb.AppendLine(string.Join(delimiter, columnTypes));
// rest: table data
foreach (DataRow row in tblExport.Rows)
{
    IEnumerable<string> fields = row.ItemArray.Select(field => field.ToString());
    sb.AppendLine(string.Join(delimiter, fields)); 
}
string path = @"C:\Temp\Testfile.csv";
File.WriteAllText(path, sb.ToString());

read csv-file into DataTable

string[] lines = File.ReadAllLines(path);
string[] columns = lines[0].Split(new[] { delimiter }, StringSplitOptions.None);
string[] types   = lines[1].Split(new[] { delimiter }, StringSplitOptions.None);
DataTable tblImport = new DataTable();
for (int i = 0; i < columns.Length; i++)
{
    string colName  = columns[i];
    string typeName = types[i];
    tblImport.Columns.Add(colName, Type.GetType(typeName));
}

// import data
// use a typeValueConverter dictionary to convert values:
var typeValueConverter = new Dictionary<Type, Func<string, object>> {
    { typeof(DateTime), value =>  value.TryGetDateTime(null, null) },
    { typeof(Decimal),  value =>  value.TryGetDecimal(null) },
    { typeof(int),      value =>  value.TryGetInt32(null) },
};
foreach (string line in lines.Skip(2))
{ 
    string[] fields = line.Split(new[]{ delimiter }, StringSplitOptions.None);
    DataRow r = tblImport.Rows.Add(); // already added at this point
    for (int i = 0; i < tblImport.Columns.Count; i++)
    {
        DataColumn col = tblImport.Columns[i];
        string rawValue = fields[i];
        object val = rawValue;
        if (typeValueConverter.ContainsKey(col.DataType))
            val = typeValueConverter[col.DataType](rawValue);
        else if (col.DataType != typeof(string) && string.IsNullOrEmpty(rawValue))
            val = DBNull.Value;
        r.SetField(col, val);
    }
}
System.Threading.Thread.CurrentThread.CurrentCulture = oldCulture;

Of course you should separate both in two methods, one for exporting and one for importing.

I've used my extensions method TryGetDateTime, TryGetDecimal and TryGetInt32 which parses strings to DateTime?,Decimal? and int?(null if it couldn't be parsed). They are especially handy in LINQ queries:

public static DateTime? TryGetDateTime(this string item, DateTimeFormatInfo dfi, params string[] allowedFormats)
{
    if (dfi == null) dfi = DateTimeFormatInfo.InvariantInfo;
    DateTime dt;
    bool success;
    if(allowedFormats == null)
        success = DateTime.TryParse(item, dfi, DateTimeStyles.None, out dt);
    else
        success = DateTime.TryParseExact(item, allowedFormats, dfi, DateTimeStyles.None, out dt);
    if (success) return dt;
    return null;
}

public static decimal? TryGetDecimal(this string item, IFormatProvider formatProvider = null, NumberStyles nStyles = NumberStyles.Any)
{
    if (formatProvider == null) formatProvider = NumberFormatInfo.InvariantInfo;
    decimal d = 0m;
    bool success = decimal.TryParse(item, nStyles, formatProvider, out d);
    if (success)
        return d;
    else
        return null;
}

public static int? TryGetInt32(this string item, IFormatProvider formatProvider = null, NumberStyles nStyles = NumberStyles.Any)
{
    if (formatProvider == null) formatProvider = NumberFormatInfo.InvariantInfo;
    int i = 0;
    bool success = int.TryParse(item, nStyles, formatProvider, out i);
    if (success)
        return i;
    else
        return null;
}
Tim Schmelter
  • 450,073
  • 74
  • 686
  • 939
  • Hi Tim, Thanks for the code but it is failing while importing data back. Getting error Couldn't store <> in SALARY Column. Expected type is Decimal. Since the table is too big I am still checking what could be the reason for that – Hemendr Oct 14 '15 at 13:30
  • @Hemendr: are you using tab as delimiter or comma? – Tim Schmelter Oct 14 '15 at 13:36
  • Since the data also have comma and tabs so I am using custom Delimiter = "|*|" – Hemendr Oct 14 '15 at 13:41
  • While storing the datatype in 2'nd row can we store System.Decimal as nullable deciaml ?. all column type should be nullable. In your code it is failing at this line r.SetField(col, val) where col.DataType is Deciaml but val datatype is string with value "" – Hemendr Oct 14 '15 at 13:49
  • @Hemendr: you don't need to store that it's nullable since all columns are nullable by default. But you should also provide parsing methods for other relevant types. I've edited my code to show them. I've also extended the `typeValueConverter` dictionary. – Tim Schmelter Oct 14 '15 at 13:57
  • 1
    Brilliant!!. Thanks for your great help Tim!. Its working now – Hemendr Oct 14 '15 at 14:03
  • @Hemendr: note that I've improved it slightly. Search for `else if (col.DataType != typeof(string)`. That will prevent an exception on other types that are currently not in the dictionary. It will set the value to `DBNull.Value` if the field is an empty string. There was also a bug in `TryGetInt32` which returned `decimal?` instead of `int?`. – Tim Schmelter Oct 14 '15 at 14:07