EDIT: Solved this myself - obviously won't work as sorting the dataTable doesn't sort the underlying data - created a dataView from the table, works fine.
I have a datatable which I am sorting and then iterating through to remove duplicate values in one column, however the output is not as expected.
Datatable structure:
infoRow["Title"]
infoRow["QuickLink"]
infoRow["Description"]
infoRow["Date"]
infoRow["MonthName"]
I'm sorting like this, which works fine, and produces a table ordered in ascending month order:
dataTable = dataTable.DefaultView.ToTable(true);
dataTable.DefaultView.Sort = "Date asc";
After the sort, I'm using the code below to compare each row to the previous, and if the MonthName value is the same, replaced it with an empty string:
string prevMonthName = "";
foreach (DataRow row in dtEvents.Rows)
{
string strMonthName = row["MonthName"].ToString();
if (strMonthName == prevMonthName)
{
row["MonthName"] = "";
row.AcceptChanges();
}
prevMonthName = strMonthName;
}
So, the problem I'm having is that even when I run the MonthName loop after the sort, it appears to be running against the unsorted data. It's like DefaultView.Sort only affects the rendered output without physically reordering the table, hence the second part of the code doesn't produce the result I need. Should I maybe be using DataView or am I just way off track...
I was actually having a similar, but slightly different problem and your question gave me an idea. As it turns out, your code was incredibly close to what you (and I) need. All you need to do is flip those two lines of sorting code like so:
dataTable.DefaultView.Sort = "Date ASC";
dataTable = dataTable.DefaultView.ToTable(true);
Now, the first line of code sorts the DefaultView. This would be enough for your DataGridView or ComboBox or whatever you're using for display, because they make use of the DefaultView. However, the DataTable, itself, remains unsorted. Therefore, the second line sets the DataTable to look exactly like the sorted DefaultView.
I just noticed your edit at the top which says you've solved it. That 'solution' seems to be more of a workaround. Seeing as how you had the right code but in the wrong order, I figured you would be interested in this answer.
Assuming that dtEvents is referencing the same object as datatable, you could try this:
string prevMonthName = "";
foreach (DataRowView row in dtEvents.DefaultView)
{
string strMonthName = row["MonthName"].ToString();
if (strMonthName == prevMonthName)
{
row["MonthName"] = "";
row.AcceptChanges();
}
prevMonthName = strMonthName;
}
Just for fun I figured out how to do this using Linq to SQL (assuming I had a sql table with your above schema). Since I spent the time figuring it out, I thought I might as well share it.
// Order the table and add an index column
var ordered = MonthTests.OrderBy(mt => mt.Date)
.AsEnumerable()
.Select((mt, index) => new
{
OrderId = index,
Record = mt
});
// Select out what we want
var query = from item in ordered
let prev = ordered.FirstOrDefault (q => q.OrderId == (item.OrderId-1))
select new
{
Title = item.Record.Title,
QuickLink = item.Record.QuickLink,
Date = item.Record.Date,
MonthName = (prev != null && prev.Record.MonthName == item.Record.MonthName) ? "" : item.Record.MonthName
};
Have fun.
Related
Iterating through a group of DataRows and filling the DataColumn in question with the value is taking too much time.
I have a DataTable with various DataColumns, one of the particular columns is ("DieID", typeof(string).
I have an array DataRow[] binningRows of DataRows of this DataTable that have certain DieIDs in that column. This array is the result of a Table Filter Expression.
I have to fill a different column (not "DieID") of all of these DataRows in binningRows with the same value, but this takes a long time because of the number of tables and their number of DataRows. Is there a way to do this with LINQ instead of a for/foreach loop? Willing to sacrifice more readability
Currently I am doing this sequence: Get List of DieIDs from filter -> Use List of DieIDs to get array
DataRows[] binningRows with proper DieIDs -> Iterate through Array of binningRows and fill proper column with value.
Code Sample:
List<string> dieIDs = table.Select(filter)
.AsEnumerable()
.Select(x => x["DieID"].ToString())
.Distinct()
.ToList();
foreach (string die in dieIDs)
{
DataRow[] binningRows = table.Select("DieID = '" + die + "'");
if (binningRows.Length == 1)
{
binningRows[0][paramIndex] = int.Parse(binList.Key);
}
//else handle duplicate error **Was ruled out as cause of slowing**
}
This is clearly inefficient even though it is only hitting each row once.
I am going to begin exploring default List values as an option. I am already filling other columns with defaults and adding row by row (a bit different requirements but it is much faster), but struggled with it initially here because of issues with ordering and the fact that paramIndex is different columns each time
Potentially I could do something like
Dictionary<string, List<string>> paramDefaults;
foreach (string param in paramNames)
{
paramDefaults.Key = param;
paramDefaults.Value.AddRange(Enumerable.Repeat(string.Join(",", DieID,binList.Key)), count).ToList();
}
// code
// code
// code
for (int i = 0; i < numDieTested; i++)
{
wafer.Value.waferData.Tables["Binning"].Rows.Add(waferID[i], dieID[i], devices[i],
paramDefaults[param1].Value, paramDefaults[param2].Value, paramDefaults[param3].Value
);
}
However, I dont think this will quite work due to paramDefaults[param1].Value correct? Anything close to this?
Hello sir i am not native english, new here in stackoverflow and new in programming but i will try my best to share my problem with you:
I added some comments in my code so i hope you can better see what the problems are
i am trying to make something like a temporary datatable that gets informations (only the rows matter) from 1 specific datatable(there will be more see in the code) and the "temporarydatatable" gives these to a list<> i tried it with linq. ofc i have my own mind and tried to change it a way i understand (LINQ query on a DataTable this wasnt really helpful for me :X ) and i tried some other things as well but i dont want to smash 10 links here :P
so here comes the code:
public MainWindow()
{
InitializeComponent();
datatable1();
}
public void datatable1()
{
/*This Table should get the informations from datatable_1 or
another one (there will be some more tables and the viewtable will
get the informations from the table where the
Type ==(i guess it will be a combobox) selected Type */
DataTable viewtable = new DataTable();
viewtable.Columns.Add("Typ", typeof(string));
viewtable.Columns.Add("Name", typeof(string));
viewtable.Columns.Add("Anzahl", typeof(string));
viewtable.Columns.Add("Zeit", typeof(string));
/*here is the main problem i have*/
viewtable.Rows =from _Row1 in datatable_1 where "Typ" =="Una";
/*it "worked" like this so i get the informations in my list*/
viewtable.Rows.Add("Una", "Testschrank2", "9000", "0:20:30");
//this table is a example table holding the informations
DataTable datatable_1 = new DataTable();
datatable_1.Clear();
datatable_1.Columns.Add("Typ");
datatable_1.Columns.Add("Name");
datatable_1.Columns.Add("Anzahl");
datatable_1.Columns.Add("Zeit");
DataRow _Row1 = datatable_1.NewRow();
datatable_1.Rows.Add("Una", "Testschrank2", "9000", "0:20:30");
// _Row1["Zeit"] = (4, 30, 0);
datatable_1.Rows.Add(_Row1);`
}
well i guess i added too much code but like i said i am really new to this so its a bit difficult for me to point on my problem with little code excuse me sir
and
thanks for your help o/
To get the value from the first DataTable you have to pull all DataRows from it as Lei Yang suggested in his comment.
DataRow temp = datatable_1.Rows.OfType<DataRow>()
.SingleOrDefault(x=>x["Typ"].ToString() == "Una");
1: You cannot assign to the property Rows since it is readonly.
2: You cannot just use simply viewtable.Rows.Add(temp) because this row already belongs to another table. This will result in a System.ArgumentException
So you need to import the row:
if (temp != null)
{
viewtable.ImportRow(temp);
}
EDIT:
If you intend to capture more than one row using the where clause you can use a List<DataRow> to save them temporarily and import each row afterwards in a loop:
List<DataRow> temp = datatable_1.Rows.OfType<DataRow>()
.Where(x => x["Typ"].ToString() == "Una").ToList();
if (temp.Count > 0)
{
foreach (var row in temp)
{
viewtable.ImportRow(row);
}
}
EDIT 2:
Here are some sources for further research:
How to: Locate a Specific Row in a DataTable
In this example you can use also the Select method to get the desired rows. This would look like this:
DataRow [] temp2 = datatable_1.Select("Typ ='Una'", "Name DESC", DataViewRowState.Added);
// or the short version:
DataRow [] temp2 = datatable_1.Select("Typ ='Una'");
the outcome will be the same. This version is from a an answer to a similar question.
I have a datatable DTgraph, that datatable has a column named Campaign. that column could have one of three unique values, which are IVR, City, City2`. So the rows are like this:
I have a datatable has data like this format
........ IVR........
.........IVR........
**.........IVR........**
.........City1......
.........City1......
**.........City1......**
.........City2......
.........City2......
**.........City2......**
I want to take the last row of each unique value for that column, In other words, I want to take the rows that are bold. I did almost everything like this:
var cRows = new Dictionary<string, DataRow>(StringComparer.InvariantCultureIgnoreCase);
foreach (DataRow oRow in DTgraph.Rows)
{
var sKey = oRow["Campaign"].ToString();
if (!cRows.ContainsKey(sKey))
{
cRows.Add(sKey, oRow);
}
else
{
cRows[sKey] = oRow;
}
}
var oNewTable = DTgraph.Clone();
foreach (var oRow in cRows.Values)
{
oNewTable.Rows.Add(oRow);
}
As you see, I put the data in dictionary and transferred the dictionary to a datatable at the end.
My problem is that on this line:
cRows.Add(sKey, oRow);
I get an error:
The row is already belongs to another datatable
Note: I need to solve that exception, I don't need a new way of doing my goal
Note: I was wrong, the exception is on this line
oNewTable.Rows.Add(oRow);
To be honest I don't 100% understand your question, however to fix the exception:
The row is already belongs to another datatable.
Change:
oNewTable.Rows.Add(oRow);
To:
oNewTable.ImportRow(oRow);
Alternatively create a new row and clone the ItemArray.
foreach (var oRow in cRows.Values)
{
var newRow = oNewTable.NewRow();
newRow.ItemArray = oRow.ItemArray.Clone() as object[];
oNewTable.Rows.Add(newRow);
}
Use NewRow() function of the new table and then use oRow.ItemArray property to get values from the source row and copy them the newly created row's ItemArray. An example would be:
Array.Copy(oRow.ItemArray, oNewTable.NewRow().ItemArray, oRow.ItemArray.Length)
However, remember that this would not preserve original values and current state from the source row (which I don't think you're using here anyway). If those things matter, go for ImportRow() solution which preserves source row's state when copying.
I have a datagridview that contains list of subjects populated from Subject table from database.Columns include
Select(checkbox),
SubjectId,
SubjectName,
SubjectGroup.
Now I want if a user Selects on any of the desired rows, the corresponding SubjectId's should be added to a List. I have made and inserted into the desired table in the database.
The problem is that the new column of checkboxes I have added to this datagridview is not being detected.
My code is:
foreach (DataGridViewRow row in gvSubjectsOpted.Rows)
{
if (Convert.ToBoolean(gvSubjectsOpted.SelectedRows[0].Cells["SelectId"].Value=true))
{
olist.Add(gvSubjectsOpted.SelectedRows[0].Cells["SubjectId"].Value.ToString());
}
}
Late to the party. I had the same issue with trying to get the checkbox column by name, use the index instead. Here is a linq example assuming the checkbox is column 0 and the stored values for TrueValue and FalseVale are true and false respectively.
var checkedRows = from DataGridViewRow r in gvSubjectsOpted.Rows
where Convert.ToBoolean(r.Cells[0].Value) == true
select r;
foreach (var row in checkedRows)
{
olist.Add(row.Cells["SubjectId"].Value.ToString());
}
I realise this is an old post but I came across it and didn't think it was really answered in an efficient way so I thought I would add my method.
I have a similar block in my windows app. I read the values from the grid when the user clicks a button, and I want to know which rows they checked. As the checkboxes are in Cell 0 and the data I want is in Cell 1, I use the following code. Note the cast: it is important as it allows us the use the Where clause and therefore just a single line of code to get the collection of data. I could use the name of the cells instead of magic index numbers but then it would not fit your app so I put numbers instead (you should use names)
var checkedRows = dataGridView
.Rows
.Cast<DataGridViewRow>()
.Where(x => x.Cells[0].Value.ToString() == "1")
.Select(x => x.Cells[1]);
Note that this will give you an IEnumerable of type DataGridViewCell. If you want you can either add something like .Value.ToString() to the select or do this when you use your collection.
You question is similar to another SO question.
Check the answer of this Datagridview checkboxcolumn value and functionality.
Try this
foreach(GridViewRow r in gvSubjectsOpted.Rows)
{
GridViewCheckBoxColumn c = r.cells[0].Controls[0] as GridViewCheckBoxColumn;
if(c.Checked)
{
//Do something.
}
}
private void button1_Click(object sender, EventArgs e)
{
string subjId;
List<string> lines = new List<string>();
for (int i = 0; i < gvSubjectsList.Rows.Count; i++)
{
bool Ischecked =Convert.ToBoolean(gvSubjectsList.Rows[i].Cells["Select"].Value);
if (Ischecked == true)
{
subjId = gvSubjectsList.Rows[i].Cells["SubjectId"].Value.ToString();
lines.Add(subjId);
}
}
comboBox1.DataSource = lines;
}
//the most important thing is to set 'true' and 'false' values against newly added checkboxcolumn instead of '0' and '1'...that is,
CBColumn.FalseValue = "false";
CBColumn.TrueValue = "true";
Now I use Select and use a criteria which select only new rows. But is there any kind of GetInsertedRows-method. If I remember correctly there is status for each row so naturally one can loop through them all but that's not elegant.
-Cheers -Matti
I like TypeT's answer but it may help to know that you always bind through a DataView to a DataTable and you can set it to filter on the rows state:
myDataSet.myTable.DefaultView.RowStateFilter = DataViewRowState.Added;
You can also create an additional DataView to look at the new or deleted rows, and then those Views won't byte each other:
var addedView = new DataView(myDataSet.myTable);
addedView.RowStateFilter = DataViewRowState.Added;
I came across this issue myself a while ago, however there's no nice way of pulling out the added rows. I've just trawled my repositories for you and found the DataTable implementation I used to use:
public class AdvancedDataTable : DataTable
{
public IEnumerable<DataRow> InsertedRowList
{
get
{
foreach (DataRow row in this.Rows)
{
if (row.RowState == System.Data.DataRowState.Added)
{
yield return row;
}
}
}
}
}
It's still doing an iteration, but it's nicely wrapped as an IEnumerable and you won't have to write the code more than once.