Convert DataTable to generic List? - c#

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;
}

Related

Using GetField<T> to set values in an object from a DataRow

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.

How to Convert DataRow to an Object

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

C# Filling datagridview

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

How to get the Count property using reflection for Generic types

I have a list of objects, of which I cannot know the type of at compile-time.
I need to identify any of these objects where a 'Count' property exists, and get the value if it does.
This code works for simple Collection types:
PropertyInfo countProperty = objectValue.GetType().GetProperty("Count");
if (countProperty != null)
{
int count = (int)countProperty.GetValue(objectValue, null);
}
The problem is that this doesn't work for generic types, such as IDictionary<TKey,TValue>. In those cases, the 'countProperty' value is returned as null, even though a 'Count' property exists in the instanced object.
All I want to do is identify any collection/dictionary based object and find the size of it, if it has one.
Edit: as requested, here's the entire listing of code that doesn't work
private static void GetCacheCollectionValues(ref CacheItemInfo item, object cacheItemValue)
{
try
{
//look for a count property using reflection
PropertyInfo countProperty = cacheItemValue.GetType().GetProperty("Count");
if (countProperty != null)
{
int count = (int)countProperty.GetValue(cacheItemValue, null);
item.Count = count;
}
else
{
//poke around for a 'values' property
PropertyInfo valuesProperty = cacheItemValue.GetType().GetProperty("Values");
int valuesCount = -1;
if (valuesProperty != null)
{
object values = valuesProperty.GetValue(cacheItemValue, null);
if (values != null)
{
PropertyInfo valuesCountProperty = values.GetType().GetProperty("Count");
if (countProperty != null)
{
valuesCount = (int)valuesCountProperty.GetValue(cacheItemValue, null);
}
}
}
if (valuesCount > -1)
item.Count = valuesCount;
else
item.Count = -1;
}
}
catch (Exception ex)
{
item.Count = -1;
item.Message = "Exception on 'Count':" + ex.Message;
}
}
This works OK on simple collections, but not on an object created from a class I have which is derived from Dictionary<TKey,TValue>. Ie
CustomClass :
Dictionary<TKey,TValue>
CacheItemInfo is just a simple class that contains properties for cache items - ie, key, count, type, expiration datetime
The first thing you should try is casting to ICollection, as this has a very cheap .Count:
ICollection col = objectValue as ICollection;
if(col != null) return col.Count;
The Count for dictionary should work though - I've tested this with Dictionary<,> and it works fine - but note that even if something implements IDictionary<,>, the concrete type (returned via GetType()) doesn't have to have a .Count on the public API - it could use explicit interface implementation to satisfy the interface while not having a public int Count {get;}. Like I say: it works for Dictionary<,> - but not necessarily for every type.
As a last ditch effort if everything else fails:
IEnumerable enumerable = objectValue as IEnumerable;
if(enumerable != null)
{
int count = 0;
foreach(object val in enumerable) count++;
return count;
}
Edit to look into the Dictionary<,> question raised in comments:
using System;
using System.Collections;
using System.Collections.Generic;
public class CustomClass : Dictionary<int, int> { }
public class CacheItemInfo
{
public int Count { get; set; }
public string Message { get; set; }
}
class Program {
public static void Main() {
var cii = new CacheItemInfo();
var data = new CustomClass { { 1, 1 }, { 2, 2 }, { 3, 3 } };
GetCacheCollectionValues(ref cii, data);
Console.WriteLine(cii.Count); // expect 3
}
private static void GetCacheCollectionValues(ref CacheItemInfo item, object cacheItemValue)
{
try
{
ICollection col;
IEnumerable enumerable;
if (cacheItemValue == null)
{
item.Count = -1;
}
else if ((col = cacheItemValue as ICollection) != null)
{
item.Count = col.Count;
}
else if ((enumerable = cacheItemValue as IEnumerable) != null)
{
int count = 0;
foreach (object val in enumerable) count++;
item.Count = count;
}
else
{
item.Count = -1;
}
}
catch (Exception ex)
{
item.Count = -1;
item.Message = "Exception on 'Count':" + ex.Message;
}
}
}
How about adding this after your first check (!untested!) ...
foreach (Type interfaceType in objectValue.GetType().GetInterfaces())
{
countProperty = interfaceType.GetProperty("Count");
//etc.
}

C# Reflection Indexed Properties

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

Categories