How to fill a DataTable with XElement/XAttribute? - c#

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

Related

How can I delete all child rows and the parents in a DataSet?

So, I have a DataRelation as is shown below:
Oraciones1 = new DataRelation("Antecedentes",
dsPubs.Tables["Consecuente"].Columns["Id"],
dsPubs.Tables["Antecedentes"].Columns["Id"]);
dsPubs.Relations.Add(Oraciones1);
where dsPubs is dsPubs = new DataSet() and that DataSet has two tables which Both have a data relation. Then; I am iterating over the parents to get the childs, just to get some task done as is shown next:
foreach (DataRow rowAuthor in dsPubs.Tables["Consecuente"].Rows) {
foreach (DataRow rowTitle in rowAuthor.GetChildRows(Oraciones1)) {
}
}
and what I want to do is to remove all the child and the parent from an a specified value which is passed through a function(i.e).
public void function(string value ){
foreach (DataRow rowAuthor in dsPubs.Tables["Consecuente"].Rows){
foreach (DataRow rowTitle in rowAuthor.GetChildRows(Oraciones1){
if(value==rowTitle["id_d"].ToString()){
//remove all the child and the parent from the specified variable "value"
}
}
}
}
I tried to get it done by using rowTitle.Delete() and rowAuthor.Delete() method but it seemed to not be working, because I think it removes the whole table, and a the time The foreach wanted to continue to grab another value from the table "Consecuente" it crashed.
Thank you a lot!!
var innerRows = new List<DataRow>();
var outerRows = new List<DataRow>();
foreach (DataRow rowAuthor in dsPubs.Tables["Consecuente"].Rows)
{
foreach (DataRow rowTitle in rowAuthor.GetChildRows(Oraciones1))
{
if (value == rowTitle["id_d"].ToString())
{
//remove all the child and the parent from the specified variable "value"
innerRows.Add(rowTitle);
outerRows.Add(rowAuthor);
}
}
}
foreach(DataRow row in innerRows)
{
row.Delete();
}
foreach (DataRow row in outerRows)
{
row.Delete();
}

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();

System.Data.DuplicateNameException: A column named 'Item' already belongs to this DataTable

I have an extension method to turn an IEnumerable into a data table and it is throwing this exception. System.Data.DuplicateNameException: A column named 'Item' already belongs to this DataTable. What's confusing is that there is not a column named 'Item' in this table, or at least there shouldn't be because the SqlTable it was created from does not have an 'Item' column.
public static DataTable ToDataTable<T>(this IEnumerable<T> items)
{
var dataTable = new DataTable(typeof(T).Name);
var props = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var prop in props)
{
dataTable.Columns.Add(prop.Name, prop.PropertyType);
}
foreach (var item in items)
{
var values = new object[props.Length];
for (var i = 0; i < props.Length; i++)
{
values[i] = props[i].GetValue(item, null);
}
dataTable.Rows.Add(values);
}
return dataTable;
}
This extention method has been working except until now when I use it with a method that takes in a sql table and makes it a datatable, then turns the data table into and IEnumerable to update one column and then uses the extenstion method on the Ienumerable to turn it back into a datatable.
var strSqlQuery = "Select * FROM " + strTableName;
var dataTable = Database.CreateDataTableFromDatabaseTable(strSqlQuery);
var ocRows = dataTable.AsEnumerable(); // turn the data table into an enumerable we can sqlbulkcopy dump later
foreach (var dataRow in ocRows)
{
if (dataRow[4].ToString().Contains("COMPLETED")) continue;
// Generate and Save Needed Hashes for this Node
// This is the algorithm to generate the hash
var md5Hash = MD5.Create();
var profileId = Database.GetProfileIdByEndPage(dataRow[3].ToString(), false);
var endPageId = Database.GetEndPageId(navigationProfileId);
var strValuesForObjectHash = string.IsNullOrEmpty(dataRow[6].ToString()) // if the "id" is null or empty use xpath
? endPageId + "," + dataRow[7] // use the xpath for the object hash, not the "id"
: endPageId + "," + dataRow[6]; // if the id has a value use the id for the object hash
var currentObjectHash = strValuesForObjectHash.GetMd5Hash(md5Hash);
dataRow[1] = currentObjectHash;
}
I tried in the extension method to check if the column had already been added but then got
System.Reflection.TargetParameterCountException: Parameter count mismatch.
foreach (var prop in props)
{
if (dataTable.Columns.Contains(prop.Name))
dataTable.Columns.Add(prop.Name, prop.PropertyType);
}
How can I use the DataTable as enumerable to update the one column for each row and make it into a DataTable again?

deleting rows from datatable causes error

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.

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