Handling DateTime fields from CsvDataReader that contains an empty string - c#

I'm trying load in data from a CSV file using CsvHelper to create a datatable with datacolumns that have a specified type.
var textReader = new StreamReader(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"{tableName}.csv"));
var csvReader = new CsvReader(textReader);
var csvDataReader = new CsvDataReader(csvReader);
var dataTable = new DataTable();
foreach(var column in metaColumns)
{
var dataColumn = new DataColumn(column.columnName, GetPropertyType(column.dataType));
dataColumn.AllowDBNull = column.isNull;
dataTable.Columns.Add(dataColumn);
}
dataTable.Load(csvDataReader);
On the load method I'm getting the following error:
String '' was not recognized as a valid DateTime.Couldn't store <> in
derived_mdd_date Column. Expected type is DateTime.
Apparently CsvHelper is loading the column from the CSV file as an empty string and then when given the DateTime type it's not converting the empty string to a null value.
After some research and just trying things I've added
csvReader.Configuration.TypeConverterOptionsCache.GetOptions<DateTime>().NullValues.Add("null");
csvReader.Configuration.TypeConverterOptionsCache.GetOptions<DateTime?>().NullValues.Add("null");
csvReader.Configuration.TypeConverterOptionsCache.GetOptions<string>().NullValues.Add("null");
csvReader.Configuration.TypeConverterCache.AddConverter<DateTime>(new DateFieldConverter());
csvReader.Configuration.TypeConverterCache.AddConverter<DateTime?>(new DateFieldConverter());
...
public class DateFieldConverter : DateTimeConverter
{
public override object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData)
{
bool result = DateTime.TryParse(text, out DateTime ret);
if (result) return ret;
return null;
}
}
Still getting the same error. I placed a breakpoint on the DateFieldConverter and it's never getting hit so something isn't syncing up correctly. I would think that the default behavior for a DateTime column would be either DateTime.MinValue or null but it's just throwing the error instead.

Unfortunately, it looks like CsvDataReader treats all values as strings and ignores the TypeConverters for other types. There appears to be a feature request to add that capability.
I can offer a workaround that might work for you. You might also check my answer here for another option.
public static void Main(string[] args)
{
using (MemoryStream stream = new MemoryStream())
using (StreamWriter writer = new StreamWriter(stream))
using (StreamReader reader = new StreamReader(stream))
using (CsvReader csv = new CsvReader(reader))
{
writer.WriteLine("DateTime,DateTimeNullable");
writer.WriteLine("5/4/2019,");
writer.WriteLine(",5/5/2019");
writer.Flush();
stream.Position = 0;
csv.Configuration.TypeConverterCache.AddConverter<DateTime>(new DateFieldConverter());
csv.Configuration.TypeConverterCache.AddConverter<DateTime?>(new DateFieldNullableConverter());
var dataTable = new DataTable();
dataTable.Columns.Add("DateTime", typeof(DateTime)).AllowDBNull = false;
dataTable.Columns.Add("DateTimeNullable", typeof(DateTime)).AllowDBNull = true;
csv.Read();
csv.ReadHeader();
while (csv.Read())
{
var row = dataTable.NewRow();
foreach (DataColumn column in dataTable.Columns)
{
if (column.DataType == typeof(DateTime) && column.AllowDBNull)
{
row[column.ColumnName] = csv.GetField(typeof(DateTime?), column.ColumnName);
}
else
{
row[column.ColumnName] = csv.GetField(column.DataType, column.ColumnName);
}
}
dataTable.Rows.Add(row);
}
}
}
public class DateFieldConverter : DateTimeConverter
{
public override object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData)
{
if (text == string.Empty)
{
return DateTime.MinValue;
}
return base.ConvertFromString(text, row, memberMapData);
}
}
public class DateFieldNullableConverter : DateTimeConverter
{
public override object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData)
{
if (text == string.Empty)
{
return DBNull.Value;
}
return base.ConvertFromString(text, row, memberMapData);
}
}

Related

Converting Excel to CSV with C# getting extra , on each row

Am converting an Excel file to a CSV in a Azure WebJob to keep the file in blob storage during the process using C# but am getting an extra comma after each row in my csv file.
Example:
1,Test,Doe,
2,Test,John,
Here is my code for producing the csv:
public static class ExcelToCSVConvertor
{
public static List<BlobInput> Convert(List<BlobOutput> inputs)
{
var dataForBlobInput = new List<BlobInput>();
try
{
foreach (BlobOutput item in inputs)
{
using (SpreadsheetDocument document = SpreadsheetDocument.Open(item.BlobContent, false))
{
foreach (Sheet _Sheet in document.WorkbookPart.Workbook.Descendants<Sheet>())
{
WorksheetPart _WorksheetPart = (WorksheetPart)document.WorkbookPart.GetPartById(_Sheet.Id);
Worksheet _Worksheet = _WorksheetPart.Worksheet;
SharedStringTablePart _SharedStringTablePart = document.WorkbookPart.GetPartsOfType<SharedStringTablePart>().First();
SharedStringItem[] _SharedStringItem = _SharedStringTablePart.SharedStringTable.Elements<SharedStringItem>().ToArray();
StringBuilder stringBuilder = new StringBuilder();
foreach (var row in _Worksheet.Descendants<Row>())
{
foreach (Cell _Cell in row)
{
string Value = string.Empty;
if (_Cell.CellValue != null)
{
if (_Cell.DataType != null && _Cell.DataType.Value == CellValues.SharedString)
Value = _SharedStringItem[int.Parse(_Cell.CellValue.Text)].InnerText;
else
Value = _Cell.CellValue.Text;
}
stringBuilder.Append(string.Format("{0},", Value.Trim()));
}
stringBuilder.Append("\n");
}
byte[] data = Encoding.UTF8.GetBytes(stringBuilder.ToString().Trim());
string fileNameWithoutExtn = item.BlobName.ToString().Substring(0, item.BlobName.ToString().IndexOf("."));
string newFilename = $"{fileNameWithoutExtn}_{_Sheet.Name}.csv";
dataForBlobInput.Add(new BlobInput { BlobName = newFilename, BlobContent = data });
}
}
}
}
catch (Exception Ex)
{
throw Ex;
}
return dataForBlobInput;
}
}
This line is adding comma after EVERY single value
stringBuilder.Append(string.Format("{0},", Value.Trim()));
1,
Test,
Doe,
2,
Test,
John,
You need to exclude the comma on last value in row (last iteration).
On last iteration foreach (Cell _Cell in row) stringBuilder.Append should be
stringBuilder.Append(string.Format("{0}", Value.Trim()));

Want to fill data from array of string to datatable in c#

public void DecryptFile(string filePath, string CompanyName)
{
using (var fileStream = new FileStream(filePath, FileMode.Open))
{
using (var gzStream = new GZipStream(fileStream, CompressionMode.Decompress))
{
using (var outputStream = new MemoryStream())
{
gzStream.CopyTo(outputStream);
byte[] outputBytes = outputStream.ToArray();
// DeserailizeByteArrayToDatatable(outputBytes, CompanyName);
string FileContents = Encoding.ASCII.GetString(outputBytes);
DataTable dt = new DataTable();
string[] arrayList = FileContents.Split("\n");
}
}
}
}
I have decrypted contents of file and now I want to fill the data to DataTable. The format of file contents is:
serial_number,peak_period_start_time,stop_time
15AA01AF361903PC,1602619200,1602615600
15AA01AF361902QB,1602619200,1602615600
15AA01AF361906YL,1602619200,1602630000
09AA01AF32190YXP,1602619200,1602630000
so I want to fil the first line as column in DataTable and rest all as rows and aslo additionally I want to change the start_time and stop time to correct format
I would suggest you to first create a method that will accept a DataTable and an array of columns and will add columns to the DataTable.
public DataTable AddColumns(DataTable dt, string[] columns)
{
if (dt == null) dt = new DataTable();
foreach (string column in columns)
{
dt.Columns.Add(column.Trim());
}
return dt;
}
Then, Create a method that will populate data into that DataTable:
public DataTable PopulateData(DataTable dt, string[] dataLines)
{
if (dataLines == null || dataLines.Length == 0) return dt;
foreach (string line in dataLines)
{
var splittedLine = line.Split(',');
var row = dt.NewRow();
for (int i = 0; i < splittedLines.Length; i++)
{
row[i] = splittedLines[i].Trim();
}
dt.Rows.Add(row);
}
return dt;
}
Call the methods above in your DecryptFile method, like below:
public void DecryptFile(string filePath, string CompanyName)
{
using (var fileStream = new FileStream(filePath, FileMode.Open))
{
using (var gzStream = new GZipStream(fileStream, CompressionMode.Decompress))
{
using (var outputStream = new MemoryStream())
{
gzStream.CopyTo(outputStream);
byte[] outputBytes = outputStream.ToArray();
// DeserailizeByteArrayToDatatable(outputBytes, CompanyName);
string FileContents = Encoding.ASCII.GetString(outputBytes);
DataTable dt = new DataTable();
string[] arrayList = FileContents.Split("\n");
// Usage
if (arrayList.Length > 0)
{
var columns = arrayList[0].Split(',');
var data = arrayList.AsEnumerable().Skip(1).ToArray(); // skip the first line.
dt = PopulateData(AddColumns(dt, columns), data);
}
}
}
}
}
Note: I see, your file has 4 columns in header row and it is supplying 3 values which is not correct, you must supply equal columns and its values otherwise, you might want to handle the row creation differently.
The general concept I would follow would be to:
Read the first row and determine which and how many columns I need to create
For each column use f.Columns.Add() to add the column to the DataTable.
For each row following do f.Rows.Add() filling in the data.
Something like:
public void DecryptFile(string filePath, string CompanyName)
{
using (var fileStream = new FileStream(filePath, FileMode.Open))
{
using (var gzStream = new GZipStream(fileStream, CompressionMode.Decompress))
{
using (var outputStream = new MemoryStream())
{
gzStream.CopyTo(outputStream);
byte[] outputBytes = outputStream.ToArray();
//DeserailizeByteArrayToDatatable(outputBytes, CompanyName);
string FileContents = Encoding.ASCII.GetString(outputBytes);
DataTable newTable = new DataTable();
string[] arrayList = FileContents.Split('\n');
int rowIndex = 0;
foreach (string line in arrayList)
{
// Assuming comma separated columns
string[] columns = line.Split(',');
int columnIndex = 0;
DataRow row = (rowIndex == 0 ? null : newTable.NewRow());
foreach (string columnValue in columns)
{
if (rowIndex == 0)
{
newTable.Columns.Add(new DataColumn(columnValue));
}
else
{
row[columnIndex] = columnValue;
}
}
rowIndex ++;
}
}
}
}
(have not tested the code)
You can create data table by using below function. I am not sure what format you want for start time and end time, so I have mentioned it in comments where you can do that.
public static DataTable ArrayOfStringToDataTable(string[] stringArray)
{
DataTable dataTable = new DataTable();
if (stringArray.Length == 0)
{
return dataTable;
}
var headers = stringArray[0].Split(',');
foreach (var header in headers)
{
dataTable.Columns.Add(header, typeof(string));
}
if (stringArray.Length == 1)
{
return dataTable;
}
for (var i = 1; i < stringArray.Length; i++) {
var rows = stringArray[i].Split(',');
var dataRow = dataTable.NewRow();
dataRow[0] = rows[0];
dataRow[1] = rows[1]; // do required formatting
dataRow[2] = rows[2]; // do required formatting
dataTable.Rows.Add(dataRow);
}
return dataTable;
}

C# Reading CSV to DataTable and Invoke Rows/Columns

i am currently working on a small Project and i got stuck with a Problem i currently can not manage to solve...
I have multiple ".CSV" Files i want to read, they all have the same Data just with different Values.
Header1;Value1;Info1
Header2;Value2;Info2
Header3;Value3;Info3
While reading the first File i Need to Create the Headers. The Problem is they are not splited in Columns but in rows (as you can see above Header1-Header3).
Then it Needs to read the Value 1 - Value 3 (they are listed in the 2nd Column) and on top of that i Need to create another Header -> Header4 with the data of "Info2" which is always placed in Column 3 and Row 2 (the other values of Column 3 i can ignore).
So the Outcome after the first File should look like this:
Header1;Header2;Header3;Header4;
Value1;Value2;Value3;Info2;
And after multiple files it sohuld be like this:
Header1;Header2;Header3;Header4;
Value1;Value2;Value3;Value4;
Value1b;Value2b;Value3b;Value4b;
Value1c;Value2c;Value3c;Value4c;
I tried it with OleDB but i get the Error "missing ISAM" which i cant mange to fix. The Code i Used is the following:
public DataTable ReadCsv(string fileName)
{
DataTable dt = new DataTable("Data");
/* using (OleDbConnection cn = new OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=\"" +
Path.GetDirectoryName(fileName) + "\";Extendet Properties ='text;HDR=yes;FMT=Delimited(,)';"))
*/
using (OleDbConnection cn = new OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" +
Path.GetDirectoryName(fileName) + ";Extendet Properties ='text;HDR=yes;FMT=Delimited(,)';"))
{
using(OleDbCommand cmd = new OleDbCommand(string.Format("select *from [{0}]", new FileInfo(fileName).Name,cn)))
{
cn.Open();
using(OleDbDataAdapter adapter = new OleDbDataAdapter(cmd))
{
adapter.Fill(dt);
}
}
}
return dt;
}
Another attempt i did was using StreamReader. But the Headers are in the wrong place and i dont know how to Change this + do this for every file. the Code i tried is the following:
public static DataTable ReadCsvFilee(string path)
{
DataTable oDataTable = new DataTable();
var fileNames = Directory.GetFiles(path);
foreach (var fileName in fileNames)
{
//initialising a StreamReader type variable and will pass the file location
StreamReader oStreamReader = new StreamReader(fileName);
// CONTROLS WHETHER WE SKIP A ROW OR NOT
int RowCount = 0;
// CONTROLS WHETHER WE CREATE COLUMNS OR NOT
bool hasColumns = false;
string[] ColumnNames = null;
string[] oStreamDataValues = null;
//using while loop read the stream data till end
while (!oStreamReader.EndOfStream)
{
String oStreamRowData = oStreamReader.ReadLine().Trim();
if (oStreamRowData.Length > 0)
{
oStreamDataValues = oStreamRowData.Split(';');
//Bcoz the first row contains column names, we will poluate
//the column name by
//reading the first row and RowCount-0 will be true only once
// CHANGE TO CHECK FOR COLUMNS CREATED
if (!hasColumns)
{
ColumnNames = oStreamRowData.Split(';');
//using foreach looping through all the column names
foreach (string csvcolumn in ColumnNames)
{
DataColumn oDataColumn = new DataColumn(csvcolumn.ToUpper(), typeof(string));
//setting the default value of empty.string to newly created column
oDataColumn.DefaultValue = string.Empty;
//adding the newly created column to the table
oDataTable.Columns.Add(oDataColumn);
}
// SET COLUMNS CREATED
hasColumns = true;
// SET RowCount TO 0 SO WE KNOW TO SKIP COLUMNS LINE
RowCount = 0;
}
else
{
// IF RowCount IS 0 THEN SKIP COLUMN LINE
if (RowCount++ == 0) continue;
//creates a new DataRow with the same schema as of the oDataTable
DataRow oDataRow = oDataTable.NewRow();
//using foreach looping through all the column names
for (int i = 0; i < ColumnNames.Length; i++)
{
oDataRow[ColumnNames[i]] = oStreamDataValues[i] == null ? string.Empty : oStreamDataValues[i].ToString();
}
//adding the newly created row with data to the oDataTable
oDataTable.Rows.Add(oDataRow);
}
}
}
//close the oStreamReader object
oStreamReader.Close();
//release all the resources used by the oStreamReader object
oStreamReader.Dispose();
}
return oDataTable;
}
I am thankful for everyone who is willing to help. And Thanks for reading this far!
Sincerely yours
If I understood you right, there is a strict parsing there like this:
string OpenAndParse(string filename, bool firstFile=false)
{
var lines = File.ReadAllLines(filename);
var parsed = lines.Select(l => l.Split(';')).ToArray();
var header = $"{parsed[0][0]};{parsed[1][0]};{parsed[2][0]};{parsed[1][0]}\n";
var data = $"{parsed[0][1]};{parsed[1][1]};{parsed[2][1]};{parsed[1][2]}\n";
return firstFile
? $"{header}{data}"
: $"{data}";
}
Where it would return - if first file:
Header1;Header2;Header3;Header2
Value1;Value2;Value3;Value4
if not first file:
Value1;Value2;Value3;Value4
If I am correct, rest is about running this against a list file of files and joining the results in an output file.
EDIT: Against a directory:
void ProcessFiles(string folderName, string outputFileName)
{
bool firstFile = true;
foreach (var f in Directory.GetFiles(folderName))
{
File.AppendAllText(outputFileName, OpenAndParse(f, firstFile));
firstFile = false;
}
}
Note: I missed you want a DataTable and not an output file. Then you could simply create a list and put the results into that list making the list the datasource for your datatable (then why would you use semicolons in there? Probably all you need is to simply attach the array values to a list).
(Adding as another answer just to make it uncluttered)
void ProcessMyFiles(string folderName)
{
List<MyData> d = new List<MyData>();
var files = Directory.GetFiles(folderName);
foreach (var file in files)
{
OpenAndParse(file, d);
}
string[] headers = GetHeaders(files[0]);
DataGridView dgv = new DataGridView {Dock=DockStyle.Fill};
dgv.DataSource = d;
dgv.ColumnAdded += (sender, e) => {e.Column.HeaderText = headers[e.Column.Index];};
Form f = new Form();
f.Controls.Add(dgv);
f.Show();
}
string[] GetHeaders(string filename)
{
var lines = File.ReadAllLines(filename);
var parsed = lines.Select(l => l.Split(';')).ToArray();
return new string[] { parsed[0][0], parsed[1][0], parsed[2][0], parsed[1][0] };
}
void OpenAndParse(string filename, List<MyData> d)
{
var lines = File.ReadAllLines(filename);
var parsed = lines.Select(l => l.Split(';')).ToArray();
var data = new MyData
{
Col1 = parsed[0][1],
Col2 = parsed[1][1],
Col3 = parsed[2][1],
Col4 = parsed[1][2]
};
d.Add(data);
}
public class MyData
{
public string Col1 { get; set; }
public string Col2 { get; set; }
public string Col3 { get; set; }
public string Col4 { get; set; }
}
I don't know if this is the best way to do this. But what i would have done in your case, is to rewrite the CSV's the conventionnal way while reading all the files, then create a stream containing the new CSV created.
It would look like something like this :
var csv = new StringBuilder();
csv.AppendLine("Header1;Header2;Header3;Header4");
foreach (var item in file)
{
var newLine = string.Format("{0},{1},{2},{3}", item.value1, item.value2, item.value3, item.value4);
csv.AppendLine(newLine);
}
//Create Stream
MemoryStream stream = new MemoryStream();
StreamReader reader = new StreamReader(stream);
//Fill your data table here with your values
Hope this will help.

String not appearing as text in CSV while exporting from C#

I have a set codes, 0075, 0062 etc saved as a string in my database and declared as string in my model. However, when I am exporting my details in CSV using CSV Helper, the codes are not saved as text but as number. That is, 0075 saved as 75. I have tried adding "=" in front of the string, but this does not work. Or tried it as below. But in vain.
Below is my code:
streamWriter.WriteLine("Code;");
streamWriter.WriteLine(string.Join(";", "\""+result.Code+"\""));
Any idea how to saved result.Code which is declared as a string, as a text in my CSV?
Code as declared in the model:
public string Code { get; set; }
Looks like the method suggested by Formatting a comma-delimited CSV to force Excel to interpret value as a string works (on Excel 2010 at least), which is to format each cell as
"=""String Value"""
Here's a static helper class that does the necessary work. Since you used ; for a delimiter I reckon you are in a region where , is the decimal separator; to generalize my answer I'm using System.Globalization.CultureInfo.CurrentCulture.TextInfo.ListSeparator as the list separator.
public static class CsvWriter
{
public static void WriteToCsv(IEnumerable<string> cells, TextWriter writer, CultureInfo cultureInfo = null)
{
if (cells == null || writer == null)
throw new ArgumentNullException();
string listSeparator = (cultureInfo ?? System.Globalization.CultureInfo.CurrentCulture).TextInfo.ListSeparator;
bool first = true;
foreach (var cell in cells)
{
if (!first)
writer.Write(listSeparator);
writer.Write(ToCsvCell(cell));
first = false;
}
writer.Write("\r\n");
}
public static void WriteToCsv<TEnumerable>(IEnumerable<TEnumerable> lines, TextWriter writer, CultureInfo cultureInfo = null) where TEnumerable : IEnumerable<string>
{
if (lines == null || writer == null)
throw new ArgumentNullException();
cultureInfo = cultureInfo ?? System.Globalization.CultureInfo.CurrentCulture;
foreach (var cells in lines)
WriteToCsv(cells, writer, cultureInfo);
}
public static string ToCsv<TEnumerable>(IEnumerable<TEnumerable> lines, CultureInfo cultureInfo = null) where TEnumerable : IEnumerable<string>
{
using (var writer = new StringWriter())
{
WriteToCsv(lines, writer, cultureInfo);
return writer.ToString();
}
}
static string ToCsvCell(string s)
{
if (s == null)
return "";
s = s.Replace("\"", "\"\"\"\"");
return string.Format("\"=\"\"{0}\"\"\"", s);
}
}
Then, to test:
var lines = new[]
{
new [] { "0075", "0062", "abc", DateTime.Today.ToShortDateString() },
new [] { "I said \"this is a quote\"" },
new [] { "Embedded new line: \r\nSecond Line", string.Concat(Enumerable.Repeat(System.Globalization.CultureInfo.CurrentCulture.TextInfo.ListSeparator, 5).ToArray()) },
};
var path = Path.Combine(Path.GetTempPath(), "TestQuestion34034950.csv");
using (var writer = new StreamWriter(path))
{
CsvWriter.WriteToCsv(lines, writer);
}
Console.WriteLine("Wrote " + path);
Excel will interpret all the CSV cells created by the above as string literals.

Convert C# DataTable(ColumnName, ColumnType, ColumnValue) to csv and restore back

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.
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;
}

Categories

Resources