The code below works for me however, I would like to add a condition before it is added to the table. What I need is - if the "Scan Time" is between two dates, then it should be added to the "table" if not, then it should be disregarded.
This is for selecting the file..
private void btnSelectFile_Click(object sender, EventArgs e)
{
OpenFileDialog ofd = new OpenFileDialog()
{
Title = "Select the file to open.",
Filter = "DAT (*.dat)|*.dat|TXT (*.txt)|*.txt|All Files (*.*)|*.*",
InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)
};
if (ofd.ShowDialog() == DialogResult.OK)
{
txtFilePath.Text = ofd.FileName;
loadDataToGridview();
}
}
This is for reading the file then adding it to the datagridview
private void loadDataToGridview()
{
DataTable table = new DataTable();
table.Columns.Add("Emp ID");
table.Columns.Add("Scan Time");
table.Columns.Add("Undefined 1");
table.Columns.Add("Undefined 2");
table.Columns.Add("Undefined 3");
table.Columns.Add("Undefined 4");
var lines = File.ReadAllLines(txtFilePath.Text).ToList();
lines.ForEach(line => table.Rows.Add(line.Split((char)9)));
return table;
}
I got the loadDataToGridview method from here but I do not know how to explode the
lines.ForEach(line => table.Rows.Add(line.Split((char)9)));
lambda expression to include the condition that I need. Let's assume that the name of the datepickers are dateFrom and dateTo.
Your help is greatly appreciated.
Do not use ReadAllLines method because it will load the entire file into memory. In other words, why load the entire file if only 1 line is between your dates.
Use the ReadLines method instead. Why? See my answer here.
var lines = File.ReadLines("").Select(x => Split(x)).Where(x => IsBetweenDates(x[1]));
lines.ForEach(row => table.Rows.Add(row));
dataGridView1.DataSource = table;
You should add your own error handling here as per your needs. I have added a few for you:
private bool IsBetweenDates(string value)
{
var dateValue = DateTime.ParseExact(value, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture);
return dateValue >= fromDate.Value && dateValue <= toDate.Value;
}
private string[] Split(string line)
{
if (string.IsNullOrWhitespace(x))
{
// There is nothing in this line. Is this allowed in your case?
// If yes do whatever you need to do here. For example, log it or something.
}
var splits = line.Split((char)9);
if (splits.Length != 6)
{
// This line does not have 6 fields so what do you want to do?
}
return splits;
}
Use Where() as suggested by #CodingYoshi, but split the line first (so you don't have to do it twice), using a Select() statement:
var lines = File.ReadAllLines(txtFilePath.Text).Select(line => line.Split(';')).Where(fields => fields[1] >= fromDate && fields[1] <= toDate).ToList();
lines.ForEach(row => table.Rows.Add(row));
You may also want to consider using something like CsvHelper instead of parsing the file manually
You can probably also use Select Method which in turns gets an array of all DataRow objects that match the filter criteria.
DataTable table = new DataTable();
table.Columns.Add("Emp ID");
//Add All your columns
var lines = File.ReadAllLines(txtFilePath.Text).ToList();
lines.ForEach(line => table.Rows.Add(line.Split((char)9)));
//Till the data has been already there in your DataTable.
//Create a new DataTable for Filtered Records.
DataTable FilteredTable = new DataTable();
//The Statement works like a SQL Statement which is equal to
//Select * from TableName Where DateColumn Between two dates.
DataRow[] rows = table.Select("date >= #" + from_date + "# AND date <= #" + to_date + "#");
//Now add all rows to the new Table.
foreach (DataRow dr in rows)
{
FilteredTable.ImportRow(dr);
}
dataGridView1.DataSource = FilteredTable;
if from_date and to_date is a DateTime and not a string, you need to use the ToString(your date format) to get the correct sql statement
Related
I am working on a report project and I have a DataTable:
The dates are coming from database (PIVOTS).
I have a code which get me only weekly-off dates:
List<string> getWeekOffDates = ds.Tables[0].AsEnumerable().Select(x => x.Field<DateTime>("WeekOff").ToString("yyyy-MM-dd")).ToList();
I want to compare that whether the getWeekOffDates and the dates in my DataTable matches (out of the entire month dates). If yes,then assign "WO" string to them which means WeekOff
For example:
The getWeekOffDates will have a list of dates, and 2020-01-04, 2020-01-05 are two of them. Now I want to check these dates in my datatable, if present then I would like to assign "WO" string to those dates in all rows to mention it is a week off.
I have tried this code, but doesn't seem that datatable gets updated.
public DataTable GetHolidays(DataSet ds, List<string> dates, DataTable dt)
{
for (int i = 0; i <= dates.Count - 1; i++)
{
// checks if dates from WeekOffDays Column is present
bool weekOff = ds.Tables[0].AsEnumerable().Any(x => dates.Contains(x.Field<DateTime>("WeekOffDays").ToString("yyyy-MM-dd")));
// get the weekoff dates
List<string> getWeekOffDates = ds.Tables[0].AsEnumerable().Select(x => x.Field<DateTime>("WeekOff").ToString("yyyy-MM-dd")).ToArray();
if (weekOff)
{
// string wo = dates[i].ToString();
string offs = getWeekOffDates[i].ToString();
dt.Columns.Cast<DataColumn>().Where(r => r.ColumnName == offs).FirstOrDefault().DefaultValue="WO";
}
}
dt.AcceptChanges();
return dt;
}
Then I am calling this function on a button click:
protected void btnGetLeaveData_Click(object sender, EventArgs e){
DataTable report= DataAccess.GetDataInDataTable(ConstantFieldsModel.PRIMARYCONNECTION, "usp_GetAnalystLeaveReport", CommandType.StoredProcedure, param);
List<string> columnNames = report.Columns.Cast<DataColumn>().Skip(16).Select(cols => cols.ColumnName).ToList();
GetHolidays(GetData(), columnNames,report);
}
The DefaultValue assigned should update the DataTable but it doesn't. What am I missing?
so I am working on my project and I want to write datagridview which is from a CSV file into XML file and I have achieved that but what I want to know if there is any way to sort the order view or change the outcome of XML what I want is to sort Alphabetical order from a specific column. this is my code for the saving XML file.
if (saveFileDialogXml.ShowDialog() == DialogResult.OK)
{
//Xml Alphabetical order code goes here
DataTable dst = new DataTable();
dst = (DataTable)Datagridview1.DataSource;
dst.TableName = "Data";
dst.WriteXml(saveFileDialogXml.FileName);
}
}
but the output of this is
<?xml version="1.0" standalone="yes"?>
<Item_x0020_Code>Item Code</Item_x0020_Code>
<Item_x0020_Description>Item Description</Item_x0020_Description>
<Current_x0020_Count>Current Count</Current_x0020_Count>
<On_x0020_Order>On Order</On_x0020_Order>
as you can see it even put the Hexadecimal and it just throws everything there, so I was wondering if i can reformat it the way I want it to display like removing the x0020. So I tried using LINQ to see if there was a problem with file, but I keep getting another error which says
System.Xml.XmlException: 'The ' ' character, hexadecimal value 0x20, cannot be included in a name.'
This is the LINQ code :
var xmlFile = new XElement("root",
from line in File.ReadAllLines(#"C:\\StockFile\stocklist.csv")
.Where(n => !string.IsNullOrWhiteSpace(n))
where !line.StartsWith(",") && line.Length > 0
let parts = line.Split(',')
select new XElement("Item Code",
new XElement("Test1", parts[0]),
new XElement("Test2", parts[1])
)
);
Also, I am new to C# and my first post here so please excuse the messy writing or placements.
Try following :
DataTable dst = new DataTable();
int startColumn = 5;
for(int i = dst.Columns.Count - 1; i >= startColumn; i--)
{
dst = dst.AsEnumerable().OrderBy(x => dst.Columns[i]).CopyToDataTable();
}
Sorry for the late Reply I kinda figured it out so forgot to close or mark an answer anyway if any of you run to the same thing all I did was this
// Save file dialogue XML file.
if (saveFileDialogXml.ShowDialog() == DialogResult.OK)
{
//try block to catch exception and handle it.
try
{
//Changing Data Table name to stock.
string Stock = ((DataTable)Datagridview1.DataSource).TableName;
}
//Catching the exception and handling it.
catch (Exception)
{
string es = "Please Open The File Before Saving it";
string title = "Error";
MessageBox.Show(es, title);
}
// instatiate new DataTable.
DataTable dt = new DataTable
{
TableName = "Stock"
};
for (int i = 0; i < Datagridview1.Columns.Count; i++)
{
//if (dataGridView1.Columns[i].Visible) // Add's only Visible columns.
//{
string headerText = Datagridview1.Columns[i].HeaderText;
headerText = Regex.Replace(headerText, "[-/, ]", "_");
DataColumn column = new DataColumn(headerText);
dt.Columns.Add(column);
//}
}
foreach (DataGridViewRow DataGVRow in Datagridview1.Rows)
{
DataRow dataRow = dt.NewRow();
// Add's only the columns that I need
dataRow[0] = DataGVRow.Cells["Item Code"].Value;
dataRow[1] = DataGVRow.Cells["Item Description"].Value;
dataRow[2] = DataGVRow.Cells["Current Count"].Value;
dataRow[3] = DataGVRow.Cells["On Order"].Value;
dt.Rows.Add(dataRow); //dt.Columns.Add();
}
DataSet ds = new DataSet();
ds.Tables.Add(dt);
//Finally the save part:
XmlTextWriter xmlSave = new XmlTextWriter(saveFileDialogXml.FileName, Encoding.UTF8)
{
Formatting = Formatting.Indented
};
ds.DataSetName = "Data";
ds.WriteXml(xmlSave);
xmlSave.Close();
I want to read a csv-file into a Datagridview. I would like to have a class and a function which reads the csv like this one:
class Import
{
public DataTable readCSV(string filePath)
{
DataTable dt = new DataTable();
using (StreamReader sr = new StreamReader(filePath))
{
string strLine = sr.ReadLine();
string[] strArray = strLine.Split(';');
foreach (string value in strArray)
{
dt.Columns.Add(value.Trim());
}
DataRow dr = dt.NewRow();
while (sr.Peek() >= 0)
{
strLine = sr.ReadLine();
strArray = strLine.Split(';');
dt.Rows.Add(strArray);
}
}
return dt;
}
}
and call it:
Import imp = new Import();
DataTable table = imp.readCSV(filePath);
foreach(DataRow row in table.Rows)
{
dataGridView.Rows.Add(row);
}
Result of this is-> rows are created but there is no data in the cells!!
First solution using a litle bit of linq
public DataTable readCSV(string filePath)
{
var dt = new DataTable();
// Creating the columns
File.ReadLines(filePath).Take(1)
.SelectMany(x => x.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries))
.ToList()
.ForEach(x => dt.Columns.Add(x.Trim()));
// Adding the rows
File.ReadLines(filePath).Skip(1)
.Select(x => x.Split(';'))
.ToList()
.ForEach(line => dt.Rows.Add(line));
return dt;
}
Below another version using foreach loop
public DataTable readCSV(string filePath)
{
var dt = new DataTable();
// Creating the columns
foreach(var headerLine in File.ReadLines(filePath).Take(1))
{
foreach(var headerItem in headerLine.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries))
{
dt.Columns.Add(headerItem.Trim());
}
}
// Adding the rows
foreach(var line in File.ReadLines(filePath).Skip(1))
{
dt.Rows.Add(x.Split(';'));
}
return dt;
}
First we use the File.ReadLines, that returns an IEnumerable that is a colletion of lines. We use Take(1), to get just the first row, that should be the header, and then we use SelectMany that will transform the array of string returned from the Split method in a single list, so we call ToList and we can now use ForEach method to add Columns in DataTable.
To add the rows, we still use File.ReadLines, but now we Skip(1), this skip the header line, now we are going to use Select, to create a Collection<Collection<string>>, then again call ToList, and finally call ForEach to add the row in DataTable. File.ReadLines is available in .NET 4.0.
Obs.: File.ReadLines doesn't read all lines, it returns a IEnumerable, and lines are lazy evaluated, so just the first line will be loaded two times.
See the MSDN remarks
The ReadLines and ReadAllLines methods differ as follows: When you use ReadLines, you can start enumerating the collection of strings before the whole collection is returned; when you use ReadAllLines, you must wait for the whole array of strings be returned before you can access the array. Therefore, when you are working with very large files, ReadLines can be more efficient.
You can use the ReadLines method to do the following:
Perform LINQ to Objects queries on a file to obtain a filtered set of its lines.
Write the returned collection of lines to a file with the File.WriteAllLines(String, IEnumerable) method, or append them to an existing file with the File.AppendAllLines(String, IEnumerable) method.
Create an immediately populated instance of a collection that takes an IEnumerable collection of strings for its constructor, such as a IList or a Queue.
This method uses UTF8 for the encoding value.
If you still have any doubt look this answer: What is the difference between File.ReadLines() and File.ReadAllLines()?
Second solution using CsvHelper package
First, install this nuget package
PM> Install-Package CsvHelper
For a given CSV, we should create a class to represent it
CSV File
Name;Age;Birthdate;Working
Alberto Monteiro;25;01/01/1990;true
Other Person;5;01/01/2010;false
The class model is
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public DateTime Birthdate { get; set; }
public bool Working { get; set; }
}
Now lets use CsvReader to build the DataTable
public DataTable readCSV(string filePath)
{
var dt = new DataTable();
var csv = new CsvReader(new StreamReader(filePath));
// Creating the columns
typeof(Person).GetProperties().Select(p => p.Name).ToList().ForEach(x => dt.Columns.Add(x));
// Adding the rows
csv.GetRecords<Person>().ToList.ForEach(line => dt.Rows.Add(line.Name, line.Age, line.Birthdate, line.Working));
return dt;
}
To create columns in DataTable e use a bit of reflection, and then use the method GetRecords to add rows in DataTabble
using Microsoft.VisualBasic.FileIO;
I would suggest the following. It should have the advantage at least that ';' in a field will be correctly handled, and it is not constrained to a particular csv format.
public class CsvImport
{
public static DataTable NewDataTable(string fileName, string delimiters, bool firstRowContainsFieldNames = true)
{
DataTable result = new DataTable();
using (TextFieldParser tfp = new TextFieldParser(fileName))
{
tfp.SetDelimiters(delimiters);
// Get Some Column Names
if (!tfp.EndOfData)
{
string[] fields = tfp.ReadFields();
for (int i = 0; i < fields.Count(); i++)
{
if (firstRowContainsFieldNames)
result.Columns.Add(fields[i]);
else
result.Columns.Add("Col" + i);
}
// If first line is data then add it
if (!firstRowContainsFieldNames)
result.Rows.Add(fields);
}
// Get Remaining Rows
while (!tfp.EndOfData)
result.Rows.Add(tfp.ReadFields());
}
return result;
}
}
CsvHelper's Author build functionality in library.
Code became simply:
using (var reader = new StreamReader("path\\to\\file.csv"))
using (var csv = new CsvReader(reader, CultureInfo.CurrentCulture))
{
// Do any configuration to `CsvReader` before creating CsvDataReader.
using (var dr = new CsvDataReader(csv))
{
var dt = new DataTable();
dt.Load(dr);
}
}
CultureInfo.CurrentCulture is used to determine the default delimiter and needs if you want to read csv saved by Excel.
I had the same problem but I found a way to use #Alberto Monteiro's Answer in my own way...
My CSV file does not have a "First-Line-Column-Header", I personally didn't put them there for some reasons, So this is the file sample
1,john doe,j.doe,john.doe#company.net
2,jane doe,j.doe,jane.doe#company.net
So you got the idea right ?
Now in I am going to add the Columns manually to the DataTable. And also I am going to use Tasks to do it asynchronously. and just simply using a foreach loop adding the values into the DataTable.Rows using the following function:
public Task<DataTable> ImportFromCSVFileAsync(string filePath)
{
return Task.Run(() =>
{
DataTable dt = new DataTable();
dt.Columns.Add("Index");
dt.Columns.Add("Full Name");
dt.Columns.Add("User Name");
dt.Columns.Add("Email Address");
// splitting the values using Split() command
foreach(var srLine in File.ReadAllLines(filePath))
{
dt.Rows.Add(srLine.Split(','));
}
return dt;
});
}
Now to call the function I simply ButtonClick to do the job
private async void ImportToGrid_STRBTN_Click(object sender, EventArgs e)
{
// Handling UI objects
// Best idea for me was to put everything a Panel and Disable it while waiting
// and after the job is done Enabling it
// and using a toolstrip docked to bottom outside of the panel to show progress using a
// progressBar and setting its style to Marquee
panel1.Enabled = false;
progressbar1.Visible = true;
try
{
DataTable dt = await ImportFromCSVFileAsync(#"c:\myfile.txt");
if (dt.Rows.Count > 0)
{
Datagridview1.DataSource = null; // To clear the previous data before adding the new ones
Datagridview1.DataSource = dt;
}
}
catch (Exception ex)
{
MessagBox.Show(ex.Message, "Error");
}
progressbar1.Visible = false;
panel1.Enabled = true;
}
I have a DataTable as shown below:
After using below LINQ Expression on above DT:
if (dt.AsEnumerable().All(row => string.IsNullOrEmpty(row.Field<string>("SameReferences"))))
BindOldReferences(dt);
else
{
var grps = from row in dt.AsEnumerable()
let RefID = row.Field<string>("ReferenceID")
let RefDescription = row.Field<string>("ReferenceDescription")
let ReferenceUrl = row.Field<string>("ReferenceUrl")
let SortOrder = row.Field<int>("sortOrder")
group row by new { RefDescription, ReferenceUrl, SortOrder } into groups
select groups;
dt = grps.Select(g =>
{
DataRow first = g.First();
if (first.Field<string>("SameReferences") != null)
{
string duplicate = first.Field<int>("SortOrder").ToString();
first.SetField("SameReferences", string.Format("{0},{1}", duplicate, first.Field<string>("SameReferences")));
}
return first;
}).CopyToDataTable();
}
After applying above LINQ to DT it becomes :
Expected DT as below : eliminate (,) comma when there is single value in column Samereferences. So what changes i have to make to LINQ to get the expected below output.
Please help..!
You can use String.Trim method like this:-
first.SetField("SameReferences", string.Format("{0},{1}", duplicate,
first.Field<string>("SameReferences")).Trim(','));
It will remove all the trailing comma.
Try this:
if (first.Field<string>("SameReferences") != null)
{
string duplicate = first.Field<int>("SortOrder").ToString();
string sameReference = first.Field<string>("SameReferences");
if (String.IsNullOrEmpty(sameReference))
first.SetField("SameReferences", duplicate);
else
first.SetField("SameReferences", string.Format("{0},{1}", duplicate, sameReference));
}
I have two DataTables, e.g. OriginalEntity and Entity.
Interface application modifies Entity dataTable's rows. While saving I want to check DataRows that is modified or different from OrigianlEntity.
But, also I need to exclude few fields while comparing e.g. modified date and other audit fields.
Currently I am looping through each rows of datatable, like this:
List<string> auditFields = new List<string>(){"createdon","modifiedon"};
string IdentityKeyName = "id";
object ent,orgEnt;
foreach(string columnName in columnList) // ColumnList is List of columns available in datatable
{
foreach(DataRow dr in Entity.Rows)
{
ent = dr[columnName];
orgEnt = OriginalEntity.Select(IdentityKeyName + " = " + dr[IdentityKeyName].ToString())[0][columnName];
if(!ent.Equals(orgEnt) && !auditFields.Contains(columnName))
{
isModified = true;
break;
}
}
}
I just want an efficient way to achieve above. Please suggest.
Thanks everyone for your suggestion, and this is my (as I don't have primary key defined)
Solution:
public bool isModified(DataTable dt1, DataTable dt2, string IdentityKeyName)
{
bool isModified = false;
List<string> auditFields = new List<string>() { "createdon", "modifiedon" };
isModified = isModified || (dt1.Rows.Count != dt2.Rows.Count);
if(!isModified)
{
//Approach takes 150 ms to compare two datatable of 10000 rows and 24 columns each
DataTable copyOriginalEntity = dt1.Copy();
DataTable copyEntity = dt2.Copy();
//Exclude field you don't want in your comparison -- It was my main task
foreach(string column in auditFields)
{
copyOriginalEntity.Columns.Remove(column);
copyEntity.Columns.Remove(column);
}
for(int i=0;i<copyOriginalEntity.Rows.Count;i++)
{
var origItems = copyOriginalEntity.Rows[i].ItemArray;
var entityItem = copyEntity.Select(IdentityKeyName + " = " + copyOriginalEntity.Rows[i][dentityKeyName].ToString())[0].ItemArray;
if(string.Concat(origItems) != string.Concat(entityItem)){ isModified = true; break; }
}
}
return isModified;
}
You are going to have to loop though the columns to compare. This compare ent.Equals(orgEnt) in your code is comparing if the object references are the same. This doesn't seem like what you want and you want to compare values.
public bool IsChanged(DataTable original, DataTable source, string idKeyName, params string[] ignoreColumns)
{
// make sure "key" column exist in both
if (!original.Columns.Contains(idKeyName) || !source.Columns.Contains(idKeyName))
{
throw new MissingPrimaryKeyException("Primary key column not found.");
}
// if source rows are not the same as original then something was deleted or added
if (source.Rows.Count != original.Rows.Count)
{
return false;
}
// Get a list of columns ignoring passed in and key (key will have to be equal to find)
var originalColumns =
original.Columns.Cast<DataColumn>()
.Select(c => c.ColumnName)
.Where(n => !ignoreColumns.Contains(n) && n != idKeyName)
.ToArray();
// check to make sure same column count otherwise just fail no need to check
var sourceColumnsCount =
source.Columns.Cast<DataColumn>()
.Select(c => c.ColumnName).Count(originalColumns.Contains);
if (originalColumns.Length != sourceColumnsCount)
{
return false;
}
//Switch to linq
var sourceRows = source.AsEnumerable();
return sourceRows.All(sourceRow =>
{
// use select since not real key
var originalCheck = original.Select(idKeyName + " = " + sourceRow[idKeyName]);
if (originalCheck.Length != 1)
{
// Couldn't find key or multiple matches
return false;
}
var originalRow = originalCheck.First();
//Since using same array we can use linq's SequenceEqual to compare for us
return
originalColumns.Select(oc => sourceRow[oc])
.SequenceEqual(originalColumns.Select(oc => originalRow[oc]));
});
}
There might be some micro optimizations but I think no matter what you will have to check each column.