When I fill datagridview with list of objects
I can not sort columns
However, I fill the same datagridview with datatable
I can sort columns
How i can sort it when i work with both of them?
Best way I know to do this is to derive from BindingList<T> and implement the sorting functionality. I've been using a variation of the class in the following link for some time. Works great!
http://social.msdn.microsoft.com/forums/en-US/winformsdatacontrols/thread/12eb59d3-e687-4e36-93ab-bf6741954d39/
You can convert it to a DataTable. Probably not as clean and efficient as implementing BindingList<T>, but it works. Taken from... Lord knows where; not original. Refactored a bit.
To use:
List<MyObject> myObjects = GetFromDatabase(); // fake method of your choosing
DataTable dataTable = ToDataTable(myObjects);
yourDataGridView.DataSource = dataTable;
ToDataTable and other methods:
public static DataTable ToDataTable<T>(IEnumerable<T> items)
{
var tb = new DataTable(typeof (T).Name);
PropertyInfo[] props = typeof (T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (PropertyInfo prop in props)
{
Type t = GetCoreType(prop.PropertyType);
tb.Columns.Add(prop.Name, t);
}
foreach (T item in items)
{
var values = new object[props.Length];
for (int i = 0; i < props.Length; i++)
{
values[i] = props[i].GetValue(item, null);
}
tb.Rows.Add(values);
}
return tb;
}
public static Type GetCoreType(Type t)
{
if (t != null && IsNullable(t))
{
if (!t.IsValueType)
{
return t;
}
else
{
return Nullable.GetUnderlyingType(t);
}
}
else
{
return t;
}
}
public static bool IsNullable(Type t)
{
return !t.IsValueType || (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>));
}
Related
Currently I am using this approach to convert datatables to POCO (Plain Old CLR Object).
public static List<T> Mapper<T>(DataTable dt) {
List<T> list = new();
T obj = default;
foreach (DataRow item in dt.Rows) {
obj = Activator.CreateInstance<T>();
foreach (PropertyInfo prop in obj.GetType().GetProperties()) {
if (item.Table.Columns.Contains(prop.Name)) {
prop.SetValue(
obj,
(item[prop.Name] != DBNull.Value ? Convert.ChangeType(item[prop.Name], prop.PropertyType) : null),
null
);
}
else {
prop.SetValue(
obj,
null,
null
);
}
}
list.Add(obj);
}
return list;
}
Works as intended but the problem is performance. Is there any faster alternatives to convert datatable to POCO? Or is there a way to improve the code above to be faster? I can't use dapper for a specific reason.
Well, you could even improve your reflection approach. You are doing some things multiple times for no reason like obj.GetType().GetProperties(). So maybe this improves it already noticeably:
public static List<T> Mapper<T>(DataTable dt)
{
List<T> list = new();
Dictionary<PropertyInfo, DataColumn> propColumns = typeof(T).GetProperties()
.Where(p => dt.Columns.Contains(p.Name))
.ToDictionary(p => p, p => dt.Columns[p.Name]);
foreach (DataRow row in dt.Rows)
{
T obj = Activator.CreateInstance<T>();
foreach (var propCol in propColumns)
{
object value = row[propCol.Value];
propCol.Key.SetValue(
obj,
value != DBNull.Value ? Convert.ChangeType(value, propCol.Key.PropertyType) : null,
null
);
}
list.Add(obj);
}
return list;
}
This also uses a dictionary that is initialized once at the beginning for the mapping of the properties and columns(which exist).
Here's what I came up with. It's similar to the other answer but I'd already written it so I figured I'd post it, as it does add a little something:
public static List<T> Map<T>(DataTable table) where T : new()
{
var list = new List<T>();
var properties = typeof(T).GetProperties();
var propertiesByColumn = new Dictionary<DataColumn, PropertyInfo>();
foreach (DataColumn column in table.Columns)
{
var property = properties.FirstOrDefault(pi => pi.Name == column.ColumnName);
if (property != null)
{
propertiesByColumn.Add(column, property);
}
}
foreach (var row in table.AsEnumerable())
{
var item = new T();
foreach (var column in propertiesByColumn.Keys)
{
propertiesByColumn[column].SetValue(item,
row.IsNull(column)
? null
: row[column]);
}
list.Add(item);
}
return list;
}
I also improved the name. Method names should be verb-based, not noun-based.
How about this way:
static List<T> Mapper<T>(DataTable dt)
{
var json = JsonConvert.SerializeObject(dt);
return JsonConvert.DeserializeObject<List<T>>(json);
}
POCO:
class User
{
public int Id { get; set; }
public string Name { get; set; }
}
Test:
var dt = new DataTable();
dt.Columns.Add("id", typeof(int));
dt.Columns.Add("name", typeof(string));
dt.Rows.Add(1, "A");
dt.Rows.Add(2, "B");
dt.Rows.Add(3, "C");
var users = Mapper<User>(dt);
foreach (var user in users)
Console.WriteLine(user.Id + " " + user.Name);
I'm trying to write a generic method to return values from columns in a DataRow.
protected static T GetField<T>(DataRow row, string name, T defaultValue)
{
if (row == null)
{
throw new ArgumentNullException("row");
}
T result = defaultValue;
if (row.Table.Columns.Contains(name) && !row.IsNull(name))
{
result = row.Field<T>(name);
}
return result;
}
When trying to assign values to specific tests I get 'Specified cast is not valid.' exception.
var rule = new MyObj
{
AString = GetField(row, "AnswerId", "test"),
AnInt = GetField(row, "Decline", 0),
ADecimal = GetField(row, "LoadFactor", 1M),
};
I'm trying to avoid writing an overload for each type.
Can this be done?
You can use these extension Methods to convert whole DataTable to List, instead of writing method for DataRow:
public static class DataTableExtensions
{
public static List<T> ToList<T>(this DataTable table) where T : new()
{
List<PropertyInfo> properties = typeof(T).GetProperties().ToList();
List<T> result = new List<T>();
foreach (var row in table.Rows)
{
var item = CreateItemFromRow<T>((DataRow)row, properties);
result.Add(item);
}
return result;
}
private static T CreateItemFromRow<T>(DataRow row, List<PropertyInfo> properties) where T : new()
{
T item = new T();
foreach (var property in properties)
{
if (row.Table.Columns.Contains(property.Name))
{
if (row[property.Name] != DBNull.Value)
property.SetValue(item, row[property.Name], null);
}
}
return item;
}
}
and use it like this:
List<SomeType> list = SomeDataTable.ToList<SomeType>();
But you have to make sure that name of columns that are selected in query should match the class properties and datatypes should also match.
I created a DataRow on my project:
DataRow datarow;
I want to convert this DataRow to any Type of Object.
How could I do it?
This is a pretty cool way I use it.
public static T ToObject<T>(this DataRow dataRow)
where T : new()
{
T item = new T();
foreach (DataColumn column in dataRow.Table.Columns)
{
PropertyInfo property = GetProperty(typeof(T), column.ColumnName);
if (property != null && dataRow[column] != DBNull.Value && dataRow[column].ToString() != "NULL")
{
property.SetValue(item, ChangeType(dataRow[column], property.PropertyType), null);
}
}
return item;
}
private static PropertyInfo GetProperty(Type type, string attributeName)
{
PropertyInfo property = type.GetProperty(attributeName);
if (property != null)
{
return property;
}
return type.GetProperties()
.Where(p => p.IsDefined(typeof(DisplayAttribute), false) && p.GetCustomAttributes(typeof(DisplayAttribute), false).Cast<DisplayAttribute>().Single().Name == attributeName)
.FirstOrDefault();
}
public static object ChangeType(object value, Type type)
{
if (type.IsGenericType && type.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
{
if (value == null)
{
return null;
}
return Convert.ChangeType(value, Nullable.GetUnderlyingType(type));
}
return Convert.ChangeType(value, type);
}
I Have found one solution for my application.
// function that creates an object from the given data row
public static T CreateItemFromRow<T>(DataRow row) where T : new()
{
// create a new object
T item = new T();
// set the item
SetItemFromRow(item, row);
// return
return item;
}
public static void SetItemFromRow<T>(T item, DataRow row) where T : new()
{
// go through each column
foreach (DataColumn c in row.Table.Columns)
{
// find the property for the column
PropertyInfo p = item.GetType().GetProperty(c.ColumnName);
// if exists, set the value
if (p != null && row[c] != DBNull.Value)
{
p.SetValue(item, row[c], null);
}
}
}
This will map your DataRow to ViewModel, Like below.
Your_ViewModel model = CreateItemFromRow<Your_ViewModel>(row);
class Person{
public string FirstName{get;set;}
public string LastName{get;set;}
}
Person person = new Person();
person.FirstName = dataRow["FirstName"] ;
person.LastName = dataRow["LastName"] ;
or
Person person = new Person();
person.FirstName = dataRow.Field<string>("FirstName");
person.LastName = dataRow.Field<string>("LastName");
Similar to some of the previous approaches, I created this extension method for DataRow which takes an argument object to be populated. Main difference is that in addition to populating object's Properties, it also populates Fields of given object. This should also work for simpler structures (Though I only tested on objects).
public static T ToObject<T>( this DataRow dataRow )
where T : new() {
T item = new T();
foreach( DataColumn column in dataRow.Table.Columns ) {
if( dataRow[column] != DBNull.Value ) {
PropertyInfo prop = item.GetType().GetProperty( column.ColumnName );
if( prop != null ) {
object result = Convert.ChangeType( dataRow[column], prop.PropertyType );
prop.SetValue( item, result, null );
continue;
}
else {
FieldInfo fld = item.GetType().GetField( column.ColumnName );
if( fld != null ) {
object result = Convert.ChangeType( dataRow[column], fld.FieldType );
fld.SetValue( item, result );
}
}
}
}
return item;
}
You can put this code in your current class or in a global static class.
It needs following namespaces...
using System;
using System.Data;
using System.Reflection;
Usage is as simple as...
MyClassName obj = dataRow.ToObject<MyClassName>()
Here is an extension method that would allow you to convert a DataRow to a given object.
public static class DataRowExtensions
{
public static T Cast<T>(this DataRow dataRow) where T : new()
{
T item = new T();
IEnumerable<PropertyInfo> properties = item.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Where(x => x.CanWrite);
foreach (DataColumn column in dataRow.Table.Columns)
{
if (dataRow[column] == DBNull.Value)
{
continue;
}
PropertyInfo property = properties.FirstOrDefault(x => column.ColumnName.Equals(x.Name, StringComparison.OrdinalIgnoreCase));
if (property == null)
{
continue;
}
try
{
Type t = Nullable.GetUnderlyingType(property.PropertyType) ?? property.PropertyType;
object safeValue = (dataRow[column] == null) ? null : Convert.ChangeType(dataRow[column], t);
property.SetValue(item, safeValue, null);
}
catch
{
throw new Exception($"The value '{dataRow[column]}' cannot be mapped to the property '{property.Name}'!");
}
}
return item;
}
}
And you can use the above extension method like so
foreach (DataRow row in dataTable.Rows)
{
SomeClassType obj = row.Cast<SomeClassType>();
// do something with your object
}
Given Converter<TIn, TOut> is a delegate, then the following should work:
List<Person> personList = new List<Person>();
personList = ConvertDataRowToList(ds, (row) => {
return new Person
{
FirstName = row["FirstName"],
LastName = row["LastName"]
// Rest of properties should assign here...
};
});
https://learn.microsoft.com/en-us/dotnet/api/system.converter-2
Apart from the manual method Avi shows, you can use a mapping system like AutoMapper to do the transformation for you. This is particularly useful in the case where you have a lot of columns/properties to map.
Check out this article on how to use AutoMapper to convert a DataTable to a list of objects.
DataRow has a property ItemArray, which contains an array of object values. You can work with this array and create any custom type with the values from your DataRow.
With less complications ;), two steps will solve the task:
1. cast to dictionary (ToDictionary).
2. map dictionary to entity (MapToEntity).
public static IDictionary<string, object> ToDictionary(
this DataRow content
)
{
var values = content.ItemArray;
var columns = content
.Table
.Columns
.Cast<DataColumn>()
.Select(x => x.ColumnName);
return values
.Select((v, m) => new { v, m })
.ToDictionary(
x => columns.ElementAt(x.m)
, x => (x.v == DBNull.Value ? null : x.v)
);
}
public static T MapToEntity<T>(
this IDictionary<string, object> source
)
where T : class, new()
{
// t - target
T t_object = new T();
Type t_type = t_object.GetType();
foreach (var kvp in source)
{
PropertyInfo t_property = t_type.GetProperty(kvp.Key);
if (t_property != null)
{
t_property.SetValue(t_object, kvp.Value);
}
}
return t_object;
}
...and the usage would be:
DataRow dr = getSomeDataRow(someArgs);
ABC result = dr.ToDictionary()
.MapToEntity<ABC>();
You could convert the whole Data table into a list Object like the code below. Of course, you can take the specific object which you want with the index or the field value.
/// <summary>
/// convert a datatable to list Object
/// </summary>
/// <typeparam name="T">object model</typeparam>
/// <param name="dataTable"></param>
/// <returns>ex ussage: List<User> listTbl = CommonFunc.convertDatatblToListObj<User>(dataTable);</returns>
public static List<T> convertDatatableToListObject<T>(DataTable dataTable)
{
List<T> res = new List<T>();
try
{
string tblJson = JsonConvert.SerializeObject(dataTable);
res = JsonConvert.DeserializeObject<List<T>>(tblJson);
}
catch (Exception ex)
{
string exStr = ex.Message;
}
return res;
}
With these changes worked fine for me, for fields int, long, int? and long?
// function that creates an object from the given data row
public static T CreateItemFromRow<T>(DataRow row) where T : new()
{
// create a new object
T item = new T();
// set the item
SetItemFromRow(item, row);
// return
return item;
}
public static void SetItemFromRow<T>(T item, DataRow row) where T : new()
{
// go through each column
foreach (DataColumn c in row.Table.Columns)
{
// find the property for the column
PropertyInfo p = item.GetType().GetProperty(c.ColumnName);
// if exists, set the value
if (p != null && row[c] != DBNull.Value)
{
if (p.PropertyType.Name == "Int64")
{
p.SetValue(item, long.Parse(row[c].ToString()), null);
}
else if (p.PropertyType.Name == "Int32")
{
p.SetValue(item, int.Parse(row[c].ToString()), null);
}
else if (p.PropertyType.FullName.StartsWith("System.Nullable`1[[System.Int32"))
{
p.SetValue(item, (int?)int.Parse(row[c].ToString()), null);
}
else if (p.PropertyType.FullName.StartsWith("System.Nullable`1[[System.Int64"))
{
p.SetValue(item, (long?)long.Parse(row[c].ToString()), null);
}
else
{
p.SetValue(item, row[c], null);
}
}
}
}
public static IList<T> ConvertTo<T>(DataTable table)
{
if (table == null)
{
return null;
}
List<DataRow> rows = new List<DataRow>();
foreach (DataRow row in table.Rows)
{
rows.Add(row);
}
return ConvertTo<T>(rows);
}
public static T ConvertItem<T>(DataTable table)
{
T obj = default(T);
if (table != null && table.Rows.Count > 0)
{
obj = CreateItem<T>(table.Rows[0]);
}
return obj;
}
public static T CreateItem<T>(DataRow row)
{
T obj = default(T);
if (row != null)
{
obj = Activator.CreateInstance<T>();
Type entityType = typeof(T);
PropertyInfo[] properties = entityType.GetProperties();
for (int i = 0; i < properties.Length; i++)
{
object[] customAttributes = properties[i].GetCustomAttributes(typeof(ColumnAttributes), false);
ColumnAttributes dataField = null;
if (null != customAttributes && customAttributes.Length > 0 && null != (dataField = customAttributes[0] as ColumnAttributes))
{
if (row.Table.Columns.Contains(dataField.FieldName) && !row[dataField.FieldName].GetType().FullName.Equals("System.DBNull"))
{
properties[i].SetValue(obj, row[dataField.FieldName], null);
}
}
}
}
return obj;
}
Thats the only thing we can think of right now is that we must be doing something where we need to Garbage collect Ourselves?
Thoughts?
Why we think there might be a leak?:
We are getting Out of Memory Errors. If a Page does not require business logic to use this type of conversion, the II6 process does not grow, but when we hit a page that uses it, it grows.
We are currently getting ANTS Profiler to give us more details.
That won't be an actual leak, but it could be stressing things unnecessarily...
How many rows are you working over?
Note that reflection is a pain, and that every call to things like GetCustomAttributes can return a new array (so you want to do that once, not once per-property-per-row).
Personally, I'd pre-construct the work I intend to do... something like below.
Note that if I was doing this lots, I'd either switch to HyperDescriptor, or if .NET 3.5 was an option, maybe a compiled Expression. Since DataTable isn't strongly typed, HyperDescriptor would be a logical next step (for performance) after the below...
sealed class Tuple<T1, T2>
{
public Tuple() {}
public Tuple(T1 value1, T2 value2) {Value1 = value1; Value2 = value2;}
public T1 Value1 {get;set;}
public T2 Value2 {get;set;}
}
public static List<T> Convert<T>(DataTable table)
where T : class, new()
{
List<Tuple<DataColumn, PropertyInfo>> map =
new List<Tuple<DataColumn,PropertyInfo>>();
foreach(PropertyInfo pi in typeof(T).GetProperties())
{
ColumnAttribute col = (ColumnAttribute)
Attribute.GetCustomAttribute(pi, typeof(ColumnAttribute));
if(col == null) continue;
if(table.Columns.Contains(col.FieldName))
{
map.Add(new Tuple<DataColumn,PropertyInfo>(
table.Columns[col.FieldName], pi));
}
}
List<T> list = new List<T>(table.Rows.Count);
foreach(DataRow row in table.Rows)
{
if(row == null)
{
list.Add(null);
continue;
}
T item = new T();
foreach(Tuple<DataColumn,PropertyInfo> pair in map) {
object value = row[pair.Value1];
if(value is DBNull) value = null;
pair.Value2.SetValue(item, value, null);
}
list.Add(item);
}
return list;
}
I am writing a Clone method using reflection. How do I detect that a property is an indexed property using reflection? For example:
public string[] Items
{
get;
set;
}
My method so far:
public static T Clone<T>(T from, List<string> propertiesToIgnore) where T : new()
{
T to = new T();
Type myType = from.GetType();
PropertyInfo[] myProperties = myType.GetProperties();
for (int i = 0; i < myProperties.Length; i++)
{
if (myProperties[i].CanWrite && !propertiesToIgnore.Contains(myProperties[i].Name))
{
myProperties[i].SetValue(to,myProperties[i].GetValue(from,null),null);
}
}
return to;
}
if (propertyInfo.GetIndexParameters().Length > 0)
{
// Property is an indexer
}
Sorry, but
public string[] Items { get; set; }
is not an indexed property, it's merely of an array type!
However the following is:
public string this[int index]
{
get { ... }
set { ... }
}
What you want is the GetIndexParameters() method. If the array that it returns has more than 0 items, that means it's an indexed property.
See the MSDN documentation for more details.
If you call property.GetValue(obj,null), and the property IS indexed, then you will get a parameter count mismatch exception. Better to check whether the property is indexed using GetIndexParameters() and then decide what to do.
Here is some code that worked for me:
foreach (PropertyInfo property in obj.GetType().GetProperties())
{
object value = property.GetValue(obj, null);
if (value is object[])
{
....
}
}
P.S. .GetIndexParameters().Length > 0) works for the case described in this article: http://msdn.microsoft.com/en-us/library/b05d59ty.aspx
So if you care about the property named Chars for a value of type string, use that, but it does not work for most of the arrays I was interested in, including, I am pretty sure, a string array from the original question.
You can convert the indexer to IEnumerable
public static IEnumerable<T> AsEnumerable<T>(this object o) where T : class {
var list = new List<T>();
System.Reflection.PropertyInfo indexerProperty = null;
foreach (System.Reflection.PropertyInfo pi in o.GetType().GetProperties()) {
if (pi.GetIndexParameters().Length > 0) {
indexerProperty = pi;
break;
}
}
if (indexerProperty.IsNotNull()) {
var len = o.GetPropertyValue<int>("Length");
for (int i = 0; i < len; i++) {
var item = indexerProperty.GetValue(o, new object[]{i});
if (item.IsNotNull()) {
var itemObject = item as T;
if (itemObject.IsNotNull()) {
list.Add(itemObject);
}
}
}
}
return list;
}
public static bool IsNotNull(this object o) {
return o != null;
}
public static T GetPropertyValue<T>(this object source, string property) {
if (source == null)
throw new ArgumentNullException("source");
var sourceType = source.GetType();
var sourceProperties = sourceType.GetProperties();
var properties = sourceProperties
.Where(s => s.Name.Equals(property));
if (properties.Count() == 0) {
sourceProperties = sourceType.GetProperties(BindingFlags.Instance | BindingFlags.NonPublic);
properties = sourceProperties.Where(s => s.Name.Equals(property));
}
if (properties.Count() > 0) {
var propertyValue = properties
.Select(s => s.GetValue(source, null))
.FirstOrDefault();
return propertyValue != null ? (T)propertyValue : default(T);
}
return default(T);
}