I am trying to convert a List to a datatable with an extension method. Implementation is:
Extension method
public static class list2Dt
{
public static DataTable ToDataTable<T>(List<T> items)
{
DataTable dataTable = new DataTable(typeof(T).Name);
//Get all the properties
PropertyInfo[] Props = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (PropertyInfo prop in Props)
{
//Setting column names as Property names
dataTable.Columns.Add(prop.Name);
}
foreach (T item in items)
{
var values = new object[Props.Length];
for (int i = 0; i < Props.Length; i++)
{
//inserting property values to datatable rows
values[i] = Props[i].GetValue(item, null);
}
dataTable.Rows.Add(values);
}
//put a breakpoint here and check datatable
return dataTable;
}
}
Controller
var noDups = firstTable.AsEnumerable()
.GroupBy(d => new
{
name = d.Field<string>("name"),
date = d.Field<string>("date")
})
.Where(d => d.Count() > 1)
.Select(d => d.First())
.ToList();
DataTable secondTable = new DataTable();
secondTable.Columns.Add("name", typeof(string));
secondTable.Columns.Add("date", typeof(string));
secondTable.Columns.Add("clockIn", typeof(string));
secondTable.Columns.Add("clockOut", typeof(string));
secondTable = list2Dt.ToDataTable(noDups);
I am getting this following error:
An exception of type 'System.Data.DuplicateNameException' occurred in System.Data.dll but was not handled in user code
Additional information: A column named 'Item' already belongs to this DataTable.
Above error is raised on line:
dataTable.Columns.Add(prop.Name);
Can someone find where problem lies.
Your ToDataTable method is expecting a list of objects - most likely a list of simple DTOs or similar.
You are passing it a list of DataRow instances, of which that class has multiple overloads of property Item which means when you're trying to build up the new DataTable it will try to add multiple columns with the name Item which is invalid in a DataTable.
On way around this is to project noDups to a new object, rather than retain the DataRow:
public class MyClass
{
public string Name{get;set;}
public string Date{get;set;}
}
var noDups = firstTable.AsEnumerable()
.GroupBy(d => new
{
name = d.Field<string>("name"),
date = d.Field<string>("date")
})
.Where(d => d.Count() > 1)
.Select(d => {
var first = d.First();
return new MyClass()
{
Name = (string)first["name"],
Date = (string)first["date"]
}
})
.ToList();
Related
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
I have this query:
var smallExchangeReport = from ex in exchangeProgReport
where !string.IsNullOrEmpty(ex.comment)
group ex by new { ex.siteName } into g
select new SummuryReportTraffic
{
siteName = g.Key.siteName,
exchangeCounter = g.Where(x => x.Prog1ToProg2Check == 1).Count(),
descriptions = (from t in g
group t by new { t.comment, t.siteName } into grp
select new Description
{
title = grp.Key.comment,
numbers = grp.Select(x => x.comment).Count()
})
};
At some point I put it to the dataTable using foreach loop:
foreach (var item in smallExchangeReport)
{
dr = smrTable.NewRow();
foreach (var d in item.descriptions)
{
dr[d.title] = d.numbers;
}
smrTable.Rows.Add(dr);
}
But I need to put the LINQ result to dataTable without using foreach loop.
So I made some changes to my code above according to this link:
DataTable dt = new DataTable();
DataRow dr = dt.NewRow();
IEnumerable<DataRow> smallExchangeReport = from ex in exchangeProgReport.AsEnumerable()
where !string.IsNullOrEmpty(ex.comment)
group ex by new { ex.siteName } into g
select new
{
siteName = g.Key.siteName,
exchangeCounter = g.Where(x => x.Prog1ToProg2Check == 1).Count(),
descriptions = (from t in g.AsEnumerable()
group t by new { t.comment, t.siteName } into grp
select new
{
title = grp.Key.comment,
numbers = grp.Select(x => x.comment).Count()
})
};
// Create a table from the query.
DataTable boundTable = smallExchangeReport.CopyToDataTable<DataRow>();
But on changed LINQ query I get this error:
Cannot implicitly convert type:'System.Collections.Generic.IEnumerable<<anonymous type: string siteName, int exchangeCounter>>' to
'System.Collections.Generic.IEnumerable<System.Data.DataRow>'. An explicit conversion exists (are you missing a cast?)
My question is how to cast the query to make it work?I tryed to cast to(DataRow) the result of the LINQ but it didn't worked.
In your LINQ query, you are trying to get IEnumerable<DataRow> as the result, but actually you select new objects of an anonymous type: select new { siteName = .... }. This cannot work because your anonymous type cannot be cast to DataRow.
What you need to do is use a function that would populate a DataRow like this:
DataRow PopulateDataRow(
DataTable table,
string siteName,
int exchangeCounter,
IEnumerable<Description> descriptions
{
var dr = table.NewRow();
// populate siteName and exchangeCounter
// (not sure how your data row is structured, so I leave it to you)
foreach (var d in descriptions)
{
dr[d.title] = d.numbers;
}
return dr;
}
then in your LINQ query, use it as follows:
IEnumerable<DataRow> smallExchangeReport =
from ex in exchangeProgReport.AsEnumerable()
where !string.IsNullOrEmpty(ex.comment)
group ex by new { ex.siteName } into g
select PopulateDataRow(
smrTable,
siteName: g.Key.siteName,
exchangeCounter: g.Where(x => x.Prog1ToProg2Check == 1).Count(),
descriptions: (from t in g.AsEnumerable()
group t by new { t.comment, t.siteName } into grp
select new Description {
title = grp.Key.comment,
numbers = grp.Select(x => x.comment).Count()
}
)
);
This solution gets rid of one foreach (on rows) and leaves the other one (on descriptions).
If removing the second foreach is important... I would still leave it inside PopulateDataRow. I don't see an elegant way to remove it. You can call a method from LINQ query which reads like a deterministic function, but actually creates the side effect of setting a column value on a data row, but it doesn't feel right to me.
this is can help you.
defining table structure.
DataTable tbl = new DataTable();
tbl.Columns.Add("Id");
tbl.Columns.Add("Name");
and we need to create datarow from anonymous type.
Func<object, DataRow> createRow = (object data) =>
{
var row = tbl.NewRow();
row.ItemArray = data.GetType().GetProperties().Select(a => a.GetValue(data)).ToArray();
return row;
};
test with fake query:
var enumarate = Enumerable.Range(0, 10);
var rows = from i in enumarate
select createRow( new { Id = i, Name = Guid.NewGuid().ToString() });
var dataTable = rows.CopyToDataTable<DataRow>();
You can use this method:
private DataTable ListToDataTable<T>(List<T> objs, string tableName) {
var table = new DataTable(tableName);
var lists = new List<List<object>>();
// init columns
var propertyInfos = new List<PropertyInfo>();
foreach (var propertyInfo in typeof(T).GetProperties()) {
propertyInfos.Add(propertyInfo);
if(propertyInfo.PropertyType.IsEnum || propertyInfo.PropertyType.IsNullableEnum()) {
table.Columns.Add(propertyInfo.Name, typeof(int));
} else {
table.Columns.Add(propertyInfo.Name, Nullable.GetUnderlyingType(propertyInfo.PropertyType) ?? propertyInfo.PropertyType);
}
table.Columns[table.Columns.Count - 1].AllowDBNull = true;
}
// fill rows
foreach(var obj in objs) {
var list = new List<object>();
foreach(var propertyInfo in propertyInfos) {
object currentValue;
if(propertyInfo.PropertyType.IsEnum || propertyInfo.PropertyType.IsNullableEnum()) {
var val = propertyInfo.GetValue(obj);
if(val == null) {
currentValue = DBNull.Value;
} else {
currentValue = (int)propertyInfo.GetValue(obj);
}
} else {
var val = propertyInfo.GetValue(obj);
currentValue = val ?? DBNull.Value;
}
list.Add(currentValue);
}
lists.Add(list);
}
lists.ForEach(x => table.Rows.Add(x.ToArray()));
return table;
}
Edit:
this extension method is used:
public static bool IsNullableEnum(this Type t) {
var u = Nullable.GetUnderlyingType(t);
return u != null && u.IsEnum;
}
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();
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?
I've just learned about Generics and I'm wondering whether I can use it to dynamically build datatables from my classes.
Or I might be missing the point here.
Here is my code, what I'm trying to do is create a datatable from my existing class and populate it. However I'm getting stuck in my thought process.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Data;
namespace Generics
{
public class Dog
{
public string Breed { get; set; }
public string Name { get; set; }
public int legs { get; set; }
public bool tail { get; set; }
}
class Program
{
public static DataTable CreateDataTable(Type animaltype)
{
DataTable return_Datatable = new DataTable();
foreach (PropertyInfo info in animaltype.GetProperties())
{
return_Datatable.Columns.Add(new DataColumn(info.Name, info.PropertyType));
}
return return_Datatable;
}
static void Main(string[] args)
{
Dog Killer = new Dog();
Killer.Breed = "Maltese Poodle";
Killer.legs = 3;
Killer.tail = false;
Killer.Name = "Killer";
DataTable dogTable = new DataTable();
dogTable = CreateDataTable(Dog);
//How do I continue from here?
}
}
}
Now At the DataTable point it errors.
Also, being new to reflection and Generics, how will I actually populate the data with the Killer class?
Building up on all the previous answers, here is a version that creates a DataTable from any collection:
public static DataTable CreateDataTable<T>(IEnumerable<T> list)
{
Type type = typeof(T);
var properties = type.GetProperties();
DataTable dataTable = new DataTable();
dataTable.TableName = typeof(T).FullName;
foreach (PropertyInfo info in properties)
{
dataTable.Columns.Add(new DataColumn(info.Name, Nullable.GetUnderlyingType(info.PropertyType) ?? info.PropertyType));
}
foreach (T entity in list)
{
object[] values = new object[properties.Length];
for (int i = 0; i < properties.Length; i++)
{
values[i] = properties[i].GetValue(entity);
}
dataTable.Rows.Add(values);
}
return dataTable;
}
Here is a more compact version of David's answer that is also an extension function. I've posted the code in a C# project on Github.
public static class Extensions
{
public static DataTable ToDataTable<T>(this IEnumerable<T> self)
{
var properties = typeof(T).GetProperties();
var dataTable = new DataTable();
foreach (var info in properties)
dataTable.Columns.Add(info.Name, Nullable.GetUnderlyingType(info.PropertyType)
?? info.PropertyType);
foreach (var entity in self)
dataTable.Rows.Add(properties.Select(p => p.GetValue(entity)).ToArray());
return dataTable;
}
}
I have found that this works very well in conjunction with code to write a DataTable to CSV.
my favorite homemade function. it create and populate all at same time. throw any object.
public static DataTable ObjectToData(object o)
{
DataTable dt = new DataTable("OutputData");
DataRow dr = dt.NewRow();
dt.Rows.Add(dr);
o.GetType().GetProperties().ToList().ForEach(f =>
{
try
{
f.GetValue(o, null);
dt.Columns.Add(f.Name, f.PropertyType);
dt.Rows[0][f.Name] = f.GetValue(o, null);
}
catch { }
});
return dt;
}
The error can be resolved by changing this:
dogTable = CreateDataTable(Dog);
to this:
dogTable = CreateDataTable(typeof(Dog));
But there are some caveats with what you're trying to do. First, a DataTable can't store complex types, so if Dog has an instance of Cat on it, you won't be able to add that as a column. It's up to you what you want to do in that case, but keep it in mind.
Second, I would recommend that the only time you use a DataTable is when you're building code that knows nothing about the data its consuming. There are valid use cases for this (e.g. a user-driven data mining tool). If you already have the data in the Dog instance, just use it.
Another little tidbit, this:
DataTable dogTable = new DataTable();
dogTable = CreateDataTable(Dog);
can be condensed to this:
DataTable dogTable = CreateDataTable(Dog);
Here is a little bit modified code, which fixed time zone issue for datatime fields:
public static DataTable ToDataTable<T>(this IList<T> data)
{
PropertyDescriptorCollection props =
TypeDescriptor.GetProperties(typeof(T));
DataTable table = new DataTable();
for (int i = 0; i < props.Count; i++)
{
PropertyDescriptor prop = props[i];
table.Columns.Add(prop.Name, prop.PropertyType);
}
object[] values = new object[props.Count];
foreach (T item in data)
{
for (int i = 0; i < values.Length; i++)
{
if (props[i].PropertyType == typeof(DateTime))
{
DateTime currDT = (DateTime)props[i].GetValue(item);
values[i] = currDT.ToUniversalTime();
}
else
{
values[i] = props[i].GetValue(item);
}
}
table.Rows.Add(values);
}
return table;
}
Here's a VB.Net version that creates a data table from a generic list passed to the function as an object. There is also a helper function (ObjectToDataTable) that creates a data table from an object.
Imports System.Reflection
Public Shared Function ListToDataTable(ByVal _List As Object) As DataTable
Dim dt As New DataTable
If _List.Count = 0 Then
MsgBox("The list cannot be empty. This is a requirement of the ListToDataTable function.")
Return dt
End If
Dim obj As Object = _List(0)
dt = ObjectToDataTable(obj)
Dim dr As DataRow = dt.NewRow
For Each obj In _List
dr = dt.NewRow
For Each p as PropertyInfo In obj.GetType.GetProperties
dr.Item(p.Name) = p.GetValue(obj, p.GetIndexParameters)
Next
dt.Rows.Add(dr)
Next
Return dt
End Function
Public Shared Function ObjectToDataTable(ByVal o As Object) As DataTable
Dim dt As New DataTable
Dim properties As List(Of PropertyInfo) = o.GetType.GetProperties.ToList()
For Each prop As PropertyInfo In properties
dt.Columns.Add(prop.Name, prop.PropertyType)
Next
dt.TableName = o.GetType.Name
Return dt
End Function
Using the answer provided by #neoistheone I've changed the following sections. Works fine now.
DataTable dogTable = new DataTable();
dogTable = CreateDataTable(typeof(Dog));
dogTable.Rows.Add(Killer.Breed, Killer.Name,Killer.legs,Killer.tail);
foreach (DataRow row in dogTable.Rows)
{
Console.WriteLine(row.Field<string>("Name") + " " + row.Field<string>("Breed"));
Console.ReadLine();
}
you can convert the object to xml then load the xml document to a dataset, then extract the first table out of the data set. However i dont see how this be practical as it infers creating streams, datasets & datatables and using converstions to create the xml document.
I guess for proof of concept i can understand why. Here is an example, but somewhat hesitant to use it.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Data;
using System.Xml.Serialization;
namespace Generics
{
public class Dog
{
public string Breed { get; set; }
public string Name { get; set; }
public int legs { get; set; }
public bool tail { get; set; }
}
class Program
{
public static DataTable CreateDataTable(Object[] arr)
{
XmlSerializer serializer = new XmlSerializer(arr.GetType());
System.IO.StringWriter sw = new System.IO.StringWriter();
serializer.Serialize(sw, arr);
System.Data.DataSet ds = new System.Data.DataSet();
System.Data.DataTable dt = new System.Data.DataTable();
System.IO.StringReader reader = new System.IO.StringReader(sw.ToString());
ds.ReadXml(reader);
return ds.Tables[0];
}
static void Main(string[] args)
{
Dog Killer = new Dog();
Killer.Breed = "Maltese Poodle";
Killer.legs = 3;
Killer.tail = false;
Killer.Name = "Killer";
Dog [] array_dog = new Dog[5];
Dog [0] = killer;
Dog [1] = killer;
Dog [2] = killer;
Dog [3] = killer;
Dog [4] = killer;
DataTable dogTable = new DataTable();
dogTable = CreateDataTable(array_dog);
// continue here
}
}
}
look the following example here
If you want to set columns order/ Include only some columns/ exclude some columns try this:
private static DataTable ConvertToDataTable<T>(IList<T> data, string[] fieldsToInclude = null,
string[] fieldsToExclude = null)
{
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(typeof(T));
DataTable table = new DataTable();
foreach (PropertyDescriptor prop in properties)
{
if ((fieldsToInclude != null && !fieldsToInclude.Contains(prop.Name)) ||
(fieldsToExclude != null && fieldsToExclude.Contains(prop.Name)))
continue;
table.Columns.Add(prop.Name, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType);
}
foreach (T item in data)
{
var atLeastOnePropertyExists = false;
DataRow row = table.NewRow();
foreach (PropertyDescriptor prop in properties)
{
if ((fieldsToInclude != null && !fieldsToInclude.Contains(prop.Name)) ||
(fieldsToExclude != null && fieldsToExclude.Contains(prop.Name)))
continue;
row[prop.Name] = prop.GetValue(item) ?? DBNull.Value;
atLeastOnePropertyExists = true;
}
if(atLeastOnePropertyExists) table.Rows.Add(row);
}
if (fieldsToInclude != null)
SetColumnsOrder(table, fieldsToInclude);
return table;
}
private static void SetColumnsOrder(DataTable table, params String[] columnNames)
{
int columnIndex = 0;
foreach (var columnName in columnNames)
{
table.Columns[columnName].SetOrdinal(columnIndex);
columnIndex++;
}
}