deleting rows from datatable causes error - c#

I am trying to remove rows that are not needed from a DataTable. Basically, there may be several rows where the itemID is identical. I want to find the rows where the column "failEmail" = "fail", and using the itemID of those rows, remove all rows from the emails DataTable that have the same itemID.
Here is what I have tried:
System.Diagnostics.Debug.Print(emails.Rows.Count.ToString() + " emails!");
// create a list of the email IDs for records that will be deleted
List<DataRow> rows2Delete = new List<DataRow>();
foreach (DataRow dr in emails.Rows)
{
if (dr["failEmail"].ToString().ToLower() == "fail")
{
rows2Delete.Add(dr);
}
}
foreach (DataRow row in rows2Delete)
{
DataRow[] drRowsToCheck =emails.Select("itemID ='" + row["itemID"].ToString() +"'");
foreach (DataRow drCheck in drRowsToCheck)
{
emails.Rows.RemovedDrCheck);
emails.AcceptChanges();
}
}
Here is the error message I am getting on the second pass:
This row has been removed from a table and does not have any data.
BeginEdit() will allow creation of new data in this row
How can I do what I need to without throwing errors like that? Is there a better way like using a LiNQ query?

The problem is that when the same itemID has multiple entries with 'fail', you are trying to remove them multiple times.
// 1. Find the Unique itemIDs to remove
var idsToRemove = emails.Select("failEmail = 'fail'").Select (x => x["itemID"]).Distinct();
// 2. Find all the rows that match the itemIDs found
var rowsToRemove = emails.Select(string.Format("itemID in ({0})", string.Join(", ", idsToRemove)));
// 3. Remove the found rows.
foreach(var rowToRemove in rowsToRemove)
{
emails.Rows.Remove(rowToRemove);
}
emails.AcceptChanges();

this is what I ended up doing, based on an answer I got from MSDN c# Forums:
create an extension on DataTable to enable LINQ euering of the Datatable:
public static class DataTableExtensions
{
public static IEnumerable<DataRow> RowsAsEnumerable ( this DataTable source )
{
return (source != null) ? source.Rows.OfType<DataRow>() : Enumerable.Empty<DataRow>();
}
}
then modified my code as below:
//Get IDs to delete
var deleteIds = from r in emails.RowsAsEnumerable()
where String.Compare(r["failEmail"].ToString(), "fail", true) == 0
select r["itemID"];
//Get all rows to delete
var rows2Delete = (from r in emails.RowsAsEnumerable()
where deleteIds.Contains(r["itemID"])
select r).ToList();
//Now delete them
foreach (var row in rows2Delete)
emails.Rows.Remove(row);
emails.AcceptChanges();
and now it works, just wish I could do it the normal way successfully.

foreach (DataRow rowFail in emails.Select("failEmail = 'fail'"))
{
DataRow[] rowsItem = emails.Select(String.Format("itemID = '{0}'", rowFail["itemID"]));
for (int i = rowsItem.Length - 1; i >= 0; i--)
{
rowsItem[i].Delete();
}
}
emails.AcceptChanges();
DataTable.Select returns an array of all DataRow objects that match the filter criteria.

Related

How to fill a DataTable with XElement/XAttribute?

I'm new to C#, I never worked with a DataTable before.
I want a DataGridView with specific names.
DataTable table = new DataTable();
List<string> bla = new List<string>();
XDocument config = XDocument.Load(configFile);
Dictionary<string, string> dict = config.Descendants("Columns").FirstOrDefault().Elements()
.GroupBy(x => (string)x.Attribute("XPath"), y => (string)y.Attribute("Name"))
.ToDictionary(x => x.Key, y => y.FirstOrDefault());
//I dont know if I need this:
foreach (string key in dict.Keys)
{
table.Columns.Add(key, typeof(string));
}
foreach (XElement position in positions.Where(e => e.HasAttributes))
{
foreach (XAttribute attribute in position.Attributes().Where(a => dict.ContainsKey($"#{a.Name.LocalName}")))
{
string name = attribute.Name.LocalName;
string value = (string)attribute;
string xName = dict["#" + name];
bla.Add(xName);
}
The columns should have the name from xName.
How can I do this?
I've tried this:
foreach (var item in bla)
{
DataRow row = table.NewRow();
row.SetField<string>(item); //this didn't work.
//foreach (string key in dict.Keys)
//{
// row.SetField<string>(key, item[key]);
//}
}
Just want the names from xName as my heading for the output.
Example für xName: Position, Status, Order, Number, ...
As my heading.
And under that the values.
if i understand you correctly, you've got your list of column names ok, but dont know how to create a datatable with the correct column names.
Below is an example of how to add a column and row to a datatable with a specific column header name.
As discussed in the comments, I've demonstrated a process to get the data you need into a structure that allows you to populate your table.
//Class to hold data
public class MyRecordContent
{
public MyRecordContent()
{
//initialise list
RecordsColumns = new List<string>();
}
//Holds a list of strings for each column of the record.
//It starts at position 0 to however many columns you have
public List<string> RecordsColumns { get; set; }
}
//This creates an empty table with the columns
var myTable = new DataTable("Table1");
foreach (var item in bla)
{
if (!myTable.Columns.Contains(item))
{
myTable.Columns.Add(new DataColumn(item, typeof(string)));
}
}
//Here you build up a list of all records and their field content from your xml.
foreach (var xmlNode in yourXMLRecordCollection)
{
var thisRecord = new MyRecordContent();
foreach (var xmlCol in xmlNode.Elements)//Each column value
{
thisRecord.RecordsColumns.Add(xmlCol.GetValue());
}
myListOfRecords.Add(thisRecord);
}
foreach (MyRecordContent record in myListOfRecords)
{
var row = myTable.NewRow();
//Here we set each row column values in the datatable.
//Map each rows column value to be the value in the list at same position.
for (var colPosition = 0; colPosition <= myTable.Columns.Count - 1;) //Number of columns added.
{
row[colPosition] = record.RecordsColumns[colPosition];
}
myTable.Rows.Add(row);
}
In the above, itterate through your list of column names and add each column to the table. You may want to add a switch statement to the loop to change the datatype of the column based upon name if required. Then create of new row off that table and set each fields value accordingly.
Finally, add the new row to the datatable.
Hope that helps.
Then

Get changes after merging datasets

Hello I have two Datasets with the same schemas and i need to get changes between two of them.
Datasets can be created using code below:
DataSet First = new DataSet("DSStore");
DataTable Footer = new DataTable("Footer");
DataColumn Column = new DataColumn("Value", Type.GetType("System.Int32"), "");
DataColumn[] PK = new DataColumn[1];
PK[0] = Column;
DataSet changes;
First.Tables.Add(Footer);
Footer.Columns.Add(Column);
Footer.PrimaryKey = PK;
//Clone to create second one
changes = First.Clone();
now just fill both with data:
for (int i = 0; i < 10; i++)
{
var row2 = changes.Tables["Footer"].NewRow();
row2["Value"] = i;
changes.Tables["Footer"].Rows.Add(row2);
}
var firstRow = First.Tables["Footer"].NewRow();
firstRow["Value"] = 8;
First.Tables["Footer"].Rows.Add(firstRow);
First.AcceptChanges();
changes.AcceptChanges();
Now when we have all data prepared we can get to what I tried:
I tried merging both of them:
First.Merge(changes);
if (First.HasChanges())
Console.WriteLine("has changes");
else
Console.WriteLine("Doesnt");
but unfortunately merge do not change row status so after rows being accepted hasChanges returns false and getchanges is null.
I tried another way:
IEnumerable<DataRow> added = changes.Tables["Footer"].AsEnumerable().Except(First.Tables["Footer"].AsEnumerable(),DataRowComparer.Default);
Console.WriteLine("Added:");
foreach (var row in added)
{
Console.WriteLine(row["Value"]);
}
Now i received some results but it is printing all 9 lines correct. So I tried to insert changes to the first dataset:
foreach(var row in added)
{
changes2.Tables["Footer"].Rows.Add(row);
}
if (changes2.HasChanges())
Console.WriteLine("has changes");
else
Console.WriteLine("Doesnt");
But after trying to add rows I am receving ArgumentException
I needed to change one line:
foreach(var row in added)
{
changes2.Tables["Footer"].Rows.Add(row.ItemArray);
}
now its ok. Adding lines creates changes

Group and Count using LINQ

I have a sql db that every time a device goes bad, it creates a record. Based on what happens, a different FaultCode is assigned.
I want to group and count the number of times an FaultCode exists.
Example:
FaultCode Count
1 6
2 20
I've written most of the code, I can query the db and execute a linq query. However, I can only return a list of Fault codes of the Counts. But not both.
Here is the code:
private static DataTable FaultCodeByCluster(DataTable referenceDt)
{
DataTable output = new DataTable();
foreach (DataColumn dtColum in (InternalDataCollectionBase)referenceDt.Columns)
output.Columns.Add(new DataColumn(dtColum.ColumnName, dtColum.DataType));
var query = from results in referenceDt.AsEnumerable()
group results by new
{
FaultCode = results.Field<int>("FaultCode"),
}
into newFaultCodes
orderby newFaultCodes.Key.FaultCode
select newFaultCodes.Count(); <--- count of fault codes
//select newFaultCodes.Key.FaultCode; <--- list out fault codes by group
foreach (var newFaultCodes in query)
{
Console.WriteLine("Value is {0}", newFaultCodes);
}
return output;
}
I haven't tested it, but try using something like this as your select statement:
select new { FaultCode = newFaultcodes.Key, Count = newFaultcodes.Count()};
Another way using Linq..
Adding assemblySystem.Data.DataSetExtensions, you can convert DataTable to List
var listOfFaultCode = dt1.Rows
.OfType<DataRow>()
.Select(dr => dr.Field<int>("FaultCode")).ToList();
var faultCodeGroupedByCount = listOfFaultCode.GroupBy(x => x);
foreach (var item in faultCodeGroupedByCount)
{
Console.WriteLine("FaultCode:" + item.Key + " FaultCount:" + item.Count());
}

Outter joins in LINQ query

This code works good it takes and matches all the unit_no and vehiclename together but it only shows the matches i also need the ones that dont match on.
I am loading a datagrid in WPF with SQLserver data and another Datagrid from oracle data.
private void GetSQLOraclelinqData()
{
var TstarData = GetTrackstarTruckData();
var M5Data = GetM5Data();
DataTable ComTable = new DataTable();
foreach (DataColumn OraColumn in M5Data.Columns)
{
ComTable.Columns.Add(OraColumn.ColumnName, OraColumn.DataType);
}
foreach (DataColumn SQLColumn in TstarData.Columns)
{
if (SQLColumn.ColumnName == "VehicleName")
ComTable.Columns.Add(SQLColumn.ColumnName + "2", SQLColumn.DataType);
else
ComTable.Columns.Add(SQLColumn.ColumnName, SQLColumn.DataType);
}
var results = M5Data.AsEnumerable().Join(TstarData.AsEnumerable(),
a => a.Field<String>("Unit_No"),
b => b.Field<String>("VehicleName"),
(a, b) =>
{
DataRow row = ComTable.NewRow();
row.ItemArray = a.ItemArray.Concat(b.ItemArray).ToArray();
ComTable.Rows.Add(row);
return row;
}).ToList();

Changing the DateTimeMode of a few columns in a datatable after it has been populated with data

I need to change the DateTimeMode of some columns in an already populated dataset. (I don't want to change it before it gets populated as it would mean making changes in several methods throughtout the application.)
Here's the stmt I am using (for a single column):
copy.Tables[0].Columns["DateColName"].DateTimeMode = DataSetDateTime.Utc;
However, it throws an error that you can't change the DateTimeMode if the dataset contains data. So the solution I am thinking is creating a clone of the dataset, changing the DateTimeMode of required columns and then re-loading data back.
DataSet copy = dsdata.Clone();
copy.Tables[0].Columns["DateColName"].DateTimeMode = DataSetDateTime.Utc;
copy.Load(dsdata.CreateDataReader(), LoadOption.OverwriteChanges, "TableName");
Is there a better way of doing this??
try this, cheers
private void SetUtcDateTime()
{
var ds = new DataSet { Locale = CultureInfo.InvariantCulture };
foreach (DataTable source in DataSet.Tables)
{
bool containsDate = false;
var target = source.Clone();
foreach (DataColumn col in target.Columns)
{
if (col.DataType == System.Type.GetType("System.DateTime"))
{
col.DateTimeMode = DataSetDateTime.Utc;
containsDate = true;
}
}
if (containsDate)
{
foreach (DataRow row in source.Rows)
target.ImportRow(row);
ds.Tables.Add(target);
}
else
{
ds.Tables.Add(source.Copy());
}
}
DataSet.Tables.Clear();
DataSet = ds;
}
where 'DataSet' is a public property on your object.
I just ran into this issue also. The DateTimeMode cannot be changed once the dataset has been filled, so the only solution I could find is to re-create the column with the correct DateTimeMode.
This code might help, you don't have to clone the entire dataset, just remove the column, modify it and add it back to the table.
private static void SetDateTimeMode(DataTable table, DataColumn col, DataSetDateTime mode)
{
var rowValues = new object[table.Rows.Count];
for (int i = 0; i < rowValues.Length; i++)
{
// ignore deleted rows
if (table.Rows[i].RowState == DataRowState.Deleted) continue;
rowValues[i] = table.Rows[i][col];
}
// we must remove and re-add the row because DateTimeMode cannot be
// changed on a column that has data.
table.Columns.Remove(col);
col.DateTimeMode = mode;
table.Columns.Add(col);
// write back each row value
for (int i = 0; i < rowValues.Length; i++)
{
// ignore deleted rows
if (table.Rows[i].RowState == DataRowState.Deleted) continue;
var rowState = table.Rows[i].RowState;
table.Rows[i][col] = rowValues[i];
// preserve unchanged rowstate
if (rowState == DataRowState.Unchanged)
table.Rows[i].AcceptChanges();
}
}
You just have to be careful, when you copy the row values back to the column to preserve the RowState.

Categories