This question already has answers here:
Dealing with commas in a CSV file
(29 answers)
Parsing a CSV with comma in data [duplicate]
(3 answers)
Closed 9 years ago.
So here is my issue.
I am reading a CSV into a datagridview. the first row of the csv is the column headers and the of the file becomes the row. I am reading the file and using as the datasource of the datagridview. However some lines in the CSV have something similar to
name,test,1,2,3,test,2,3,2,1,test,name
Where it is bolded above when I use .Split() it considers it as a new cell in the row however the number of columns is 10 instead of 11 and I get the following error:
Input array is longer than the number of columns in this table
How can I get around this.
Below is my C# Code.
OpenFileDialog openFile = new OpenFileDialog();
openFile.InitialDirectory = "c:\\";
openFile.Filter = "txt files (*.txt)|*.txt| CSV Files (*.csv) | *.csv| All Files (*.*) | *.*";
openFile.FilterIndex = 2;
openFile.RestoreDirectory = true;
try {
if (openFile.ShowDialog() == DialogResult.OK)
{
string file = openFile.FileName;
StreamReader sr = new StreamReader(file);
/* gets all the lines of the csv */
string[] str = File.ReadAllLines(file);
/* creates a data table*/
DataTable dt = new DataTable();
/* gets the column headers from the first line*/
string[] temp = str[0].Split(',');
/* creates columns of the gridview base on what is stored in the temp array */
foreach (string t in temp)
{
dt.Columns.Add(t, typeof(string));
}
/* retrieves the rows after the first row and adds it to the datatable */
for (int i = 1; i < str.Length; i++)
{
string[] t = str[i].Split(',');
dt.Rows.Add(t);
}
/* assigns the data grid view a data source based on what is stored in dt */
dataGridView1.DataSource = dt;
}
}
catch (Exception ex)
{
MessageBox.Show("Error: The CSV you selected could not be loaded" + ex.Message);
}
This sounds more like a data issue than a programming issue. If your CSV file is supposed to have 10 columns yet some have 11, how are you to know which one is the extra column?
One thing you could do is check for the number of columns before adding the row.
for (int i = 1; i < str.Length; i++)
{
string[] t = str[i].Split(',');
if(t.Length == temp.Length)
dt.Rows.Add(t);
}
Related
This question already has answers here:
What is an IndexOutOfRangeException / ArgumentOutOfRangeException and how do I fix it?
(5 answers)
Closed 5 years ago.
I've been stuck on this problem for a little while now. I'm able to read information from a bunch (100) Textboxes and save the data into a CSV file but reading that information back into the form has me a little befuddled, I'm only trying to load the first 11 strings to start with. I can load the CSV into a List but I can't seem to move that data from the list to my Textboxes. Is there something I'm missing with my approach?
public List<string> LoadCsvFile(string filePath)
{
var reader = new StreamReader(File.OpenRead(filePath));
List<string> searchList = new List<string>();
while (!reader.EndOfStream)
{
var line = reader.ReadLine();
searchList.Add(line);
for (int i = 0; i < 11; i++)
{
string date = searchList[i];
string dropdownindex = searchList[i];
LasttextBox.Text = searchList[i];
FirsttextBox.Text = searchList[i];
EmailtextBox.Text = searchList[i];
PhonetextBox.Text = searchList[i];
HometextBox.Text = searchList[i];
InfotextBox.Text = searchList[i];
PrimarytextBox.Text = searchList[i];
EmailtextBox.Text = searchList[i];
SecondaryEmailtextBox.Text = searchList[i];
}
}
return searchList;
}
The error I'm getting is:
System.ArgumentOutOfRangeException: 'Index was out of range. Must be
non-negative and less than the size of the collection. Parameter name:
index'
I appreciate any help you can provide.
You are confusing lines with fields, and you don't even really need the List based on what it looks like you are trying to do. It seems like you are trying to assign field values to your text boxes.
Assuming your csv file looks similar to below(You have the e-mail textbox duplicated by the way):
Date,DropDownIndex,LastName,FirstName,Email,Phone,Home,Info,Primary,Email,SecondaryEmail
You can use Linq to parse your CSV file into an anonymous class with all the values you want and then loop through and assign those to your text boxes
For example:
var csvRecords = File
.ReadAllLines(filePath)
.Select(c => c.Split(',')).Select(c => new
{
Date = c[0],
DropDown = c[1],
LastName = c[2],
FirstName = c[3],
Email = c[4],
Phone = c[5],
Home = c[6],
Info = c[7],
Primary = c[8],
Email2 = c[9],
SecondaryEmail = c[10]
}).ToList();
foreach (var csvRecord in csvRecords)
{
var date = csvRecord.Date;
var dropdownindex = csvRecord.DropDown;
LasttextBox.Text = csvRecord.LastName;
FirsttextBox.Text = csvRecord.FirstName;
EmailtextBox.Text = csvRecord.Email;
PhonetextBox.Text = csvRecord.Phone;
HometextBox.Text = csvRecord.Home;
InfotextBox.Text = csvRecord.Info;
PrimarytextBox.Text = csvRecord.Primary;
EmailtextBox.Text = csvRecord.Email2;
SecondaryEmailtextBox.Text = csvRecord.SecondaryEmail;
}
Some things you will need to consider is if your CSV file only contains comma delimiters and no double quote qualifiers as there could potentially be commas in the actual values which will then throw off the positions of your various fields. If you are still wanting to return a List via the method, you can't return the anonymous typed list, so you will need to create a class, and then instead of using the new in the Linq select, you would use new and assign the fields the same way.
This approach is more scalable, i.e., it will work with any number of csv file rows and/or text boxes so long as you have control over the names of the text boxes. This design ignores lines from the csv file that there is no text box for
public Form1()
{
InitializeComponent();
//simulate a list loaded from a csv file
IList<string> stringsFromCsv = new List<string>
{
"from csv line dog",
"from csv line cat",
"from csv line fish",
"from csv line frog",
"from csv line squirrel",
"from csv line turtle",
"from csv line squid",
"from csv line bass",
"from csv line tiger",
"from csv line lion"
};
//make a collection of all the controls in the groupbox (or form, or whatever)
Control.ControlCollection controls = groupBox1.Controls;
int listIndex = 0;
//loop based on the number of items from the csv file
while (listIndex <= stringsFromCsv.Count - 1)
{
//create a text box name from the current list index
string expectedTextBoxName = "textBox" + ((listIndex + 1).ToString());
//this is brute force, but step thru all the controls until
//you find a text box whose name matches
foreach (Control control in controls)
{
//skip the control if its not a text box
if (control.GetType().Name != "TextBox")
continue;
if (control.Name == expectedTextBoxName)
{
control.Text = stringsFromCsv[listIndex];
break;
}
}
listIndex = listIndex + 1;
if (listIndex > stringsFromCsv.Count - 1)
break;
}
}
What I have is a CSV that I have imported into a Datagridview.
I am now looking for a way to only import the column with the header # and Delay and not all info in the CSV, so any help on this would be appreciated.
Here is the Code I have thus far:
private void button1_Click(object sender, EventArgs e)
{
DataTable dt = new DataTable();
DialogResult result = openFileDialog1.ShowDialog();
if (result == DialogResult.OK) // Test result.
{
String Fname = openFileDialog1.FileName;
//String Sname = "export";
string[] raw_text = System.IO.File.ReadAllLines(Fname);
string[] data_col = null;
int x = 0;
foreach (string text_line in raw_text)
{
data_col = text_line.Split(';');
if (x == 0)
{
for (int i = 0; i < data_col.Count(); i++)
{
dt.Columns.Add(data_col[i]);
}
x++;
}
else
{
dt.Rows.Add(data_col);
}
}
dataGridView1.DataSource = dt;
}
}
When I read from CSV files, I create a list of values that I want for each row and use that list as the basis for my INSERT statement to the database.
I know where to find the data I want in the CSV file so I specifically target those items while I'm building my list of parameters for the query.
See the code below:
// Read the file content from the function parameter.
string content = System.Text.Encoding.ASCII.GetString(bytes);
// Split the content into an array where each array item is a line for
// each row of data.
// The Replace simply removes the CarriageReturn LineFeed characters from
// the source text and replaces them with a Pipe character (`|`)
// and then does the split from that character.
// This is just personal preference to do it this way
string[] data = content.Replace("\r\n", "|").Split('|');
// Loop through each row and extract the data you want.
// Note that each value is in a fixed position in the row.
foreach (string row in data)
{
if (!String.IsNullOrEmpty(row))
{
string[] cols = row.Split(';');
List<MySqlParameter> args = new List<MySqlParameter>();
args.Add(new MySqlParameter("#sid", Session["storeid"]));
args.Add(new MySqlParameter("#name", cols[0]));
args.Add(new MySqlParameter("#con", cols[3]));
try
{
// Insert the data to the database.
}
catch (Exception ex)
{
// Report an error.
}
}
}
In the same way, you could build your list/dataset/whatever as a data source for your datagridview. I would build a table.
Here's a mockup (I haven't got time to test it right now but it should get you on the right track).
DataTable table = new DataTable();
table.Columns.Add("#");
table.Columns.Add("Delay");
foreach (var line in raw_text)
{
DataRow row = table.NewRow();
row[0] = line[0]; // The # value you want.
row[1] = line[1]; // The Delay value you want.
table.Rows.Add(row);
}
DataGridView1.DataSource = table;
DataGridView1.DataBind();
Using TextFieldParser can make handling CVS input less brittle:
// add this using statement for TextFieldParser - needs reference to Microsoft.VisualBasic assembly
using Microsoft.VisualBasic.FileIO;
...
// TextFieldParser implements IDisposable so you can let a using block take care of opening and closing
using (TextFieldParser parser = new TextFieldParser(Fname))
{
// configure your parser to your needs
parser.TextFieldType = FieldType.Delimited;
parser.Delimiters = new string[] { ";" };
parser.HasFieldsEnclosedInQuotes = false; // no messy code if your data comes with quotes: ...;"text value";"another";...
// read the first line with your headers
string[] fields = parser.ReadFields();
// add the desired headers with the desired data type
dt.Columns.Add(fields[2], typeof(string));
dt.Columns.Add(fields[4], typeof(string));
// read the rest of the lines from your file
while (!parser.EndOfData)
{
// all fields from one line
string[] line = parser.ReadFields();
// create a new row <-- this is missing in your code
DataRow row = dt.NewRow();
// put data values; cast if needed - this example uses string type columns
row[0] = line[2];
row[1] = line[4];
// add the newly created and filled row
dt.Rows.Add(row);
}
}
// asign to DGV
this.dataGridView1.DataSource = dt;
I am using below code to export data from a csv file to datatable.
As the values are of mixed text i.e. both numbers and Alphabets, some of the columns are not getting exported to Datatable.
I have done some research here and found that we need to set ImportMixedType = Text and TypeGuessRows = 0 in registry which even did not solve the problem.
Below code is working for some files even with mixed text.
Could someone tell me what is wrong with below code. Do I miss some thing here.
if (isFirstRowHeader)
{
header = "Yes";
}
using (OleDbConnection connection = new OleDbConnection(#"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + pathOnly +
";Extended Properties=\"text;HDR=" + header + ";FMT=Delimited\";"))
{
using (OleDbCommand command = new OleDbCommand(sql, connection))
{
using (OleDbDataAdapter adapter = new OleDbDataAdapter(command))
{
adapter.Fill(table);
}
connection.Close();
}
}
for comma delimited file this worked for me
public DataTable CSVtoDataTable(string inputpath)
{
DataTable csvdt = new DataTable();
string Fulltext;
if (File.Exists(inputpath))
{
using (StreamReader sr = new StreamReader(inputpath))
{
while (!sr.EndOfStream)
{
Fulltext = sr.ReadToEnd().ToString();//read full content
string[] rows = Fulltext.Split('\n');//split file content to get the rows
for (int i = 0; i < rows.Count() - 1; i++)
{
var regex = new Regex("\\\"(.*?)\\\"");
var output = regex.Replace(rows[i], m => m.Value.Replace(",", "\\c"));//replace commas inside quotes
string[] rowValues = output.Split(',');//split rows with comma',' to get the column values
{
if (i == 0)
{
for (int j = 0; j < rowValues.Count(); j++)
{
csvdt.Columns.Add(rowValues[j].Replace("\\c",","));//headers
}
}
else
{
try
{
DataRow dr = csvdt.NewRow();
for (int k = 0; k < rowValues.Count(); k++)
{
if (k >= dr.Table.Columns.Count)// more columns may exist
{ csvdt .Columns.Add("clmn" + k);
dr = csvdt .NewRow();
}
dr[k] = rowValues[k].Replace("\\c", ",");
}
csvdt.Rows.Add(dr);//add other rows
}
catch
{
Console.WriteLine("error");
}
}
}
}
}
}
}
return csvdt;
}
The main thing that would probably help is to first stop using OleDB objects for reading a delimited file. I suggest using the 'TextFieldParser' which is what I have successfully used for over 2 years now for a client.
http://www.dotnetperls.com/textfieldparser
There may be other issues, but without seeing your .CSV file, I can't tell you where your problem may lie.
The TextFieldParser is specifically designed to parse comma delimited files. The OleDb objects are not. So, start there and then we can determine what the problem may be, if it persists.
If you look at an example on the link I provided, they are merely writing lines to the console. You can alter this code portion to add rows to a DataTable object, as I do, for sorting purposes.
Following is the code for it:
protected void Upload(object sender, EventArgs e)
{
if (FileUpload1.HasFile)
{
//Upload and save the file
string csvPath = Server.MapPath("~/App_Data/") + Path.GetFileName(FileUpload1.PostedFile.FileName);
FileUpload1.SaveAs(csvPath);
DataTable dt = new DataTable();
dt.Columns.AddRange(new DataColumn[7]
{
new DataColumn("pataintno", typeof(int)),
new DataColumn("Firstname", typeof(string)),
new DataColumn("Lastname",typeof(string)),
new DataColumn("Age", typeof(int)),
new DataColumn("Address", typeof(string)),
new DataColumn("Email", typeof(string)),
new DataColumn("Phno", typeof(int)),});
string csvData = File.ReadAllText(csvPath);
foreach (string row in csvData.Split('\n'))
{
if (!string.IsNullOrEmpty(row))
{
dt.Rows.Add();
int i = 0;
foreach (string cell in row.Split(','))
{
dt.Rows[dt.Rows.Count - 1][i] = cell;
i++;
}
}
}
string consString = ConfigurationManager.ConnectionStrings["cnstr"].ConnectionString;
using (SqlConnection con = new SqlConnection(consString))
{
using (SqlBulkCopy sqlBulkCopy = new SqlBulkCopy(con))
{
//Set the database table name
sqlBulkCopy.DestinationTableName = "Pataint";
con.Open();
sqlBulkCopy.WriteToServer(dt);
con.Close();
Array.ForEach(Directory.GetFiles((Server.MapPath("~/App_Data/"))), File.Delete);
}
}
}
else
{
Label1.Text = "PlZ TRY AGAIN";
}
}
You have a DataTable with 3 fields of type integer, the error says that one or more of the data extracted from your file are not valid integers.
So you need to check for bad input (as always in these cases)
// Read all lines and get back an array of the lines
string[] lines = File.ReadAllLines(csvPath);
// Loop over the lines and try to add them to the table
foreach (string row in lines)
{
// Discard if the line is just null, empty or all whitespaces
if (!string.IsNullOrWhiteSpace(row))
{
string[] rowParts = row.Split(',');
// We expect the line to be splittes in 7 parts.
// If this is not the case then log the error and continue
if(rowParts.Length != 7)
{
// Log here the info on the incorrect line with some logging tool
continue;
}
// Check if the 3 values expected to be integers are really integers
int pataintno;
int age;
int phno;
if(!Int32.TryParse(rowParts[0], out pataintno))
{
// It is not an integer, so log the error
// on this line and continue
continue;
}
if(!Int32.TryParse(rowParts[3], out age))
{
// It is not an integer, so log the error
// on this line and continue
continue;
}
if(!Int32.TryParse(rowParts[6], out phno))
{
// It is not an integer, so log the error
// on this line and continue
continue;
}
// OK, all is good now, try to create a new row, fill it and add to the
// Rows collection of the DataTable
DataRow dr = dt.NewRow();
dr[0] = pataintno;
dr[1] = rowParts[1].ToString();
dr[2] = rowParts[2].ToString();
dr[3] = age
dr[4] = rowParts[4].ToString();
dr[5] = rowParts[5].ToString();
dr[6] = phno;
dt.Rows.Add(dr);
}
}
The check on your input is done using Int32.TryParse that will return false if the string cannot be converted in an integer. In this case you should write some kind of error log to look at when the loop is completed and discover which lines are incorrect and fix them.
Notice also that I have changed your code in some points: Use File.ReadAllLines so you have already your input splitted at each new line (without problem if the newline is just a \n or a \r\n code), also the code to add a new row to your datatable should follow the pattern: create a new row, fill it with values, add the new row to the existing collection.
I checked the code and it seems fine. I suggest you to check the csv file and make sure there are no headers for any columns.
I had this problem today while parsing csv to sql table. My parser was working good since one year but all of a sudden threw int conversion error today. SQL bulk copy is not that informative, neither reviewing the csv file shows anything wrong in data. All my numeric columns in csv had valid numeric values.
So to find the error, I wrote below custom method. Error immediately popped on very first record. Actual problem was vendor changed the csv format of numeric value and now started rendering decimal values in place of integer. So for example, in place of value 1, csv file had 1.0. When I open the csv file, it reflects only 1 but in notepad, it showed 1.0. My sql table had all integer values and somehow SQL BulkCopy can't handle this transformation. Spent around 3 hours to figure out this error.
Solution inspired from - https://sqlbulkcopy-tutorial.net/type-a-cannot-be-converted-to-type-b
private void TestData(CsvDataReader dataReader)
{
int a = 0;
while(dataReader.Read())
{
try
{
a = int.Parse(dataReader[<<Column name>>].ToString());
}
catch (Exception ex){}
}
}
I'm trying to upload a series of client info's through a csv ,I had some trouble with this in the eginning but my previous post was answered so I was able to start reading in the data however it only reads the first line. Just wondering if anyone had any ideas. I've included the code below
private void btnUpload_Click(object sender, EventArgs e)
{
//Browse for file
OpenFileDialog ofd = new OpenFileDialog();
//Only show .csv files
ofd.Filter = "Microsoft Office Excel Comma Separated Values File|*.csv";
DialogResult result = ofd.ShowDialog();
//If the user selects a valid file
if (result == DialogResult.OK)
{
//File is delimited by a comma
char[] laClientDelim = { ',' };
//New object for string manipulation
objStringManipulation = new StringManipulation();
// Parse the csv file
List<string[]> lsClientList = objStringManipulation.parseCSV(ofd.FileName, laClientDelim);
foreach (string[] laClient in lsClientList)
{
//Create new object for manipulating the database
objSqlCommands = new SqlCommands("Client", "ClientName");
string[] records = File.ReadAllLines(ofd.FileName); // read the file completely line by line
char splitChar = ',';
int splitCharCount = 0;
int k = 0;
string[] fields = records[k].Split(splitChar); // reads all the single values per line. 'splitChar' should be the delimiter.
splitCharCount++;
if (splitCharCount >= 4 && splitCharCount <= 10)
{
var stuff = from l in File.ReadLines(ofd.FileName)
let x = l.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries)
.Skip(1)
.Select(s => char.Parse(s))
select new
{
Client = x,
ClientName = x
};
}
//Inserts the client info into datbase
objSqlCommands.sqlCommandInsertorUpdate("INSERT", records[k]);//laClient[0]);
k++;
//Refreshs the Client table on display from the
this.clientTableAdapter.Fill(this.kIIDImplementationCalcDataSet.Client);
//MAKE SURE TO ONLY ADD IN CLIENT AND CLIENT NAME
//update the view
dgvClientlst.Update() ;
}
}
}
your loop looks like this essentially :
foreach (string[] laClient in lsClientList)
{
int k = 0;
string[] records = File.ReadAllLines(ofd.FileName);
string[] fields = records[k].Split(splitChar);
k++;
}
Your 'k' value never makes it past 0 for each laClient. You need to loop internally for each line.
I know my answer is not exactly what you are looking for but if you convert the .csv file to .xls file then you can manipulate it very easily. I have done this on multiple occasions and if you want I can provide you instructions with code.
Expanding on Jonesy's answer (because I can't yet comment), declare the variable k outside the loop. It's being reset to zero each time through.
int k = 0;
foreach (string[] laClient in lsClientList)
{
string[] records = File.ReadAllLines(ofd.FileName);
string[] fields = records[k].Split(splitChar);
k++;
}