Disclaimer: I know its asked at so many places at SO.
My query is a little different.
Coding Language: C# 3.5
I have a DataTable named cardsTable that pull data from DB and I have a class Cards which have only some properties(no constructor)
public class Cards
{
public Int64 CardID { get; set; }
public string CardName { get; set; }
public Int64 ProjectID { get; set; }
public Double CardWidth { get; set; }
public Double CardHeight { get; set; }
public string Orientation { get; set; }
public string BackgroundImage { get; set; }
public string Background { get; set; }
}
I want to insert the cardsTable data to an object of type List.
My data will be having null fields in it and so the method should not error when i convert the data. Is the below method the best way?
DataTable dt = GetDataFromDB();
List<Cards> target = dt.AsEnumerable().ToList().ConvertAll(x => new Cards { CardID = (Int64)x.ItemArray[0] });
You could actually shorten it down considerably. You can think of the Select() extension method as a type converter. The conversion could then be written as this:
List<Cards> target = dt.AsEnumerable()
.Select(row => new Cards
{
// assuming column 0's type is Nullable<long>
CardID = row.Field<long?>(0).GetValueOrDefault(),
CardName = String.IsNullOrEmpty(row.Field<string>(1))
? "not found"
: row.Field<string>(1),
}).ToList();
I think all the solutions can be improved and make the method more general if you use some conventions and reflection. Let's say you name your columns in the datatable the same name as the properties in your object, then you could write something that look at all your properties of your object and then look up that column in the datatable to map the value.
I did the opposite, that is... from IList to datatable, and the code I wrote can be seen at: http://blog.tomasjansson.com/convert-datatable-to-generic-list-extension/
It shouldn't be that hard to go the other way, and it should be that hard to overload the functions so you can provide information of which properties you want to include or exclude.
EDIT:
So the code to make it work is:
public static class DataTableExtensions
{
private static Dictionary<Type,IList<PropertyInfo>> typeDictionary = new Dictionary<Type, IList<PropertyInfo>>();
public static IList<PropertyInfo> GetPropertiesForType<T>()
{
var type = typeof(T);
if(!typeDictionary.ContainsKey(typeof(T)))
{
typeDictionary.Add(type, type.GetProperties().ToList());
}
return typeDictionary[type];
}
public static IList<T> ToList<T>(this DataTable table) where T : new()
{
IList<PropertyInfo> properties = GetPropertiesForType<T>();
IList<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, IList<PropertyInfo> properties) where T : new()
{
T item = new T();
foreach (var property in properties)
{
property.SetValue(item, row[property.Name], null);
}
return item;
}
}
If you have a DataTable you can just write yourTable.ToList<YourType>() and it will create the list for you. If you have more complex type with nested objects you need to update the code. One suggestion is to just overload the ToList method to accept an params string[] excludeProperties which contains all your properties that shouldn't be mapped. Of course you can add some null checking in the foreach loop of the CreateItemForRow method.
UPDATE: Added static dictionary to store the result from the reflection operation to make it a little bit faster. I haven't compiled the code, but it should work :).
Just a little simplification. I don't use ItemArray:
List<Person> list = tbl.AsEnumerable().Select(x => new Person
{
Id = (Int32) (x["Id"]),
Name = (string) (x["Name"] ?? ""),
LastName = (string) (x["LastName"] ?? "")
}).ToList();
The .ToList() is in the wrong place, and if some fields can be null you'll have to deal with these as they wont convert to Int64 if they're null
DataTable dt = GetDataFromDB();
List<Cards> target = dt.AsEnumerable().Select(
x => new Cards { CardID = (Int64)(x.ItemArray[0] ?? 0) }).ToList();
well its the one line solution
it depends on whether or not you know the data in the database is all valid and will not contain anything that will break the above
eg a nullable field whenre you dont expect it - maybe due to a left join int eh sql that genertates the data.
So if you have validated the data before then yeah - I was goign to suggest some linq - but you got tht down.
If you need some validation however you should probably just loop through the datarows, generate your object as above and add it to the collection ... this will also allow you to handle errors in one row and still process the rest.
Thats the way i see it anyway
(damn i came on to downvote something so my rep was 1024)
You can map Data Table to model class using a Generic class like below.
Generic class
public static class DataTableMappingtoModel
{
/// <summary>
/// Maps Data Table values to coresponded model propertise
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="dt"></param>
/// <returns></returns>
public static List<T> MappingToEntity<T>(this DataTable dt)
{
try
{
var lst = new List<T>();
var tClass = typeof (T);
PropertyInfo[] proInModel = tClass.GetProperties();
List<DataColumn> proInDataColumns = dt.Columns.Cast<DataColumn>().ToList();
T cn;
foreach (DataRow item in dt.Rows)
{
cn = (T) Activator.CreateInstance(tClass);
foreach (var pc in proInModel)
{
var d = proInDataColumns.Find(c => string.Equals(c.ColumnName.ToLower().Trim(), pc.Name.ToLower().Trim(), StringComparison.CurrentCultureIgnoreCase));
if (d != null)
pc.SetValue(cn, item[pc.Name], null);
}
lst.Add(cn);
}
return lst;
}
catch (Exception e)
{
throw e;
}
}
}
Model class
public class Item
{
public string ItemCode { get; set; }
public string Cost { get; set; }
public override string ToString()
{
return "ItemCode : " + ItemCode + ", Cost : " + Cost;
}
}
Create DataTable
public DataTable getTable()
{
DataTable dt = new DataTable();
dt.Columns.Add(new DataColumn("ItemCode", typeof(string)));
dt.Columns.Add(new DataColumn("Cost", typeof(string)));
DataRow dr;
for (int i = 0; i < 10; i++)
{
dr = dt.NewRow();
dr[0] = "ItemCode" + (i + 1);
dr[1] = "Cost" + (i + 1);
dt.Rows.Add(dr);
}
return dt;
}
Now we can convert this DataTable to List like below:
DataTable dt = getTable();
List<Item> lst = dt.ToCollection<Item>();
foreach (Item cn in lst)
{
Response.Write(cn.ToString() + "<BR/>");
}
Hope will help you
Here is a simple way to convert to generic list in c# with Where condition
List<Filter> filter = ds.Tables[0].AsEnumerable()
.Where(x => x.Field<int>("FilterID") == 5)
.Select(row => new Filter
{
FilterID = row.Field<int>("FilterID"),
FilterName = row.Field<string>("FilterName")
}).ToList();
First Define properties and use as per
public class Filter
{
public int FilterID { get; set; }
public string FilterName { get; set; }
}
Put Package:
using System.Linq;
using System.Collections.Generic;
Here is the fastest loop free solution to convert DataTable to a generic type list.
public static List<T> ConvertDataTable<T>(DataTable SourceData, Func<DataRow, T> RowConverter)
{
List<T> list = new List<T>();
if (SourceData == null || SourceData.Rows.Count < 1)
return list;
IEnumerable<T> enumerable = SourceData.AsEnumerable().Select(RowConverter);
if (enumerable == null)
return list;
return new List<T>(enumerable);
}
And this is the implementation of this function.
public static List<T> ExecuteListOfObject<T>(DataTable SourceData)
{
return ConvertDataTable(SourceData, ConvertRecord<T>);
}
public static T ConvertRecord<T>(DataRow drData)
{
if (drData == null || drData[0] == DBNull.Value)
return default(T);
return (T)drData[0];
}
I built on top of Tomas Jansson's logic to include an "Ignore" attribute. This allows me to add other attribute's to the class being loaded without breaking the DataTable-To-Class loading itself.
Alternatively I also considered adding a separate parameter that holds the actual column name to be read from in the DataTable. In that case instead of using "row[property.Name]" then you'd use row[attribute.Name]" or something like that for that particular property.
public static class DataTableExtensions
{
[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = true)]
public sealed class IgnoreAttribute : Attribute { public IgnoreAttribute() { } }
private static Dictionary<Type, IList<PropertyInfo>> typeDictionary = new Dictionary<Type, IList<PropertyInfo>>();
public static IList<PropertyInfo> GetPropertiesForType<T>()
{
var type = typeof(T);
if (!typeDictionary.ContainsKey(typeof(T)))
typeDictionary.Add(type, type.GetProperties().ToList());
return typeDictionary[type];
}
public static IList<T> ToList<T>(this DataTable table) where T : new()
{
IList<PropertyInfo> properties = GetPropertiesForType<T>();
IList<T> result = new List<T>();
foreach (var row in table.Rows)
result.Add(CreateItemFromRow<T>((DataRow)row, properties));
return result;
}
private static T CreateItemFromRow<T>(DataRow row, IList<PropertyInfo> properties) where T : new()
{
T item = new T();
foreach (var property in properties)
{
// Only load those attributes NOT tagged with the Ignore Attribute
var atr = property.GetCustomAttribute(typeof(IgnoreAttribute));
if (atr == null)
property.SetValue(item, row[property.Name], null);
}
return item;
}
}
Coming late but this can be useful. Can be called using:
table.Map();
or call with a Func to filter the values.
You can even change the mapping name between the type property and DataColumn header by setting the attributes on the property.
[AttributeUsage(AttributeTargets.Property)]
public class SimppleMapperAttribute: Attribute
{
public string HeaderName { get; set; }
}
public static class SimpleMapper
{
#region properties
public static bool UseDeferredExecution { get; set; } = true;
#endregion
#region public_interface
public static IEnumerable<T> MapWhere<T>(this DataTable table, Func<T, bool> sortExpression) where T:new()
{
var result = table.Select().Select(row => ConvertRow<T>(row, table.Columns, typeof(T).GetProperties())).Where((t)=>sortExpression(t));
return UseDeferredExecution ? result : result.ToArray();
}
public static IEnumerable<T> Map<T>(this DataTable table) where T : new()
{
var result = table.Select().Select(row => ConvertRow<T>(row, table.Columns, typeof(T).GetProperties()));
return UseDeferredExecution ? result : result.ToArray();
}
#endregion
#region implementation_details
private static T ConvertRow<T>(DataRow row, DataColumnCollection columns, System.Reflection.PropertyInfo[] p_info) where T : new()
{
var instance = new T();
foreach (var info in p_info)
{
if (columns.Contains(GetMappingName(info))) SetProperty(row, instance, info);
}
return instance;
}
private static void SetProperty<T>(DataRow row, T instance, System.Reflection.PropertyInfo info) where T : new()
{
string mp_name = GetMappingName(info);
object value = row[mp_name];
info.SetValue(instance, value);
}
private static string GetMappingName(System.Reflection.PropertyInfo info)
{
SimppleMapperAttribute attribute = info.GetCustomAttributes(typeof(SimppleMapperAttribute),true).Select((o) => o as SimppleMapperAttribute).FirstOrDefault();
return attribute == null ? info.Name : attribute.HeaderName;
}
#endregion
}
Related
I noticed that I sort of do the same stuff over and over again and wanted to create a more generic method for converting the rows from a datatable to a specific class.
Since I have no experiance in using generic types I find it hard to know how to search for what I want but I'll try to explain.
I have created this class:
public class Class1
{
public Guid? Id{ get; set; }
public string Text1{ get; set; }
public string Text2{ get; set; }
public int TheNumber{ get; set; }
}
Then I fetch data from a database which represents the Class1. I get back the result in the form of a DataTable. The names in the columns of the datarows are exactly the same as in my class so I though I could use them somehow in my generic method to create instances of Class1.
At this point in time I create the instance like this:
public static Class1 AssembleClass1(DataTable dtInfo)
{
Class1 obj = null;
if (dtInfo != null)
{
DataRow dr = dtInfo.Rows[0];
obj = new StrategicObjectives();
obj.Id= (Guid)dr["Id"];
obj.Text1 = dr["Id"].ToString();
obj.Text2 = dr["Id"].ToString();
obj.TheNumber = (int)dr["TheNumber"];
}
return obj;
}
Now, what I want to do is to create a generic method that takes the dataTable and the type of Class1 (since I have many other classes in my project I want to be able to do the same).
It should then create a list of the type of Class1 and iterate the parameters of Class1, find the corresponding datacolumn in the row(s) of the datatable and assign the correct datarow value to the correct parameter of Class1.
I guess it also needs to check the type of each parameter of Class1 to convert the datarow column type correctly.
I only got this far myself but I don't know if it's correct and I don't know how to continue.. :
public static List<T> AssembleItem<T>(DataTable dtItems)
{
List<T> items = null;
if (dtItems != null)
{
items = new List<T>();
foreach (DataRow dr in dtItems.Rows)
{
object item = (T)Activator.CreateInstance(typeof(T));
}
}
return items;
}
I hope someone can help me with this, please ask me if you need more information.
To answer the question in part, it is not necessary to use reflection to instatiate a type; you can use type constraints to require a parameterless constructor as follows.
public static List<T> AssembleItem<T>(DataTable dtItems) where T : new()
{
List<T> items = null;
if (dtItems != null)
{
items = new List<T>();
foreach (DataRow dr in dtItems.Rows)
{
T item = new T();
// populate item from dr
items.Add(item);
}
}
return items;
}
You could use reflection to get the property names of the class and populate them.
public static List<T> AssembleItem<T>(DataTable dtItems) where T : new()
{
List<T> items = null;
if (dtItems != null)
{
items = new List<T>();
foreach (DataRow dr in dtItems.Rows)
{
T item = new T();
foreach (DataRow dr in dtItems.Rows)
{
T item = new T();
Type t = typeof (T);
foreach (var property in t.GetProperties(BindingFlags.Public))
{
var propertyname = property.Name;
//var data = ...get data from row in database with same column name as propertyname
property.SetValue(item,data);
}
// populate item from dr
items.Add(item);
}
}
return items;
}
}
I am thinking about making a custom attribute so that when we are using multiple data readers [SqldataReader] on different objects/tables, we could use the attribute to get the type of the property, and the "columnName" of the property. This way, we could then have a method that takes the data reader as a param, and from there could reflect the attributes to read in the columns. An example of what is currently being done is below, and then an example of what I am trying to accomplish. The problem I am having, is how to manage how to tell it what the (Type) is.
private static App GetAppInfo(SqlDataReader dr)
{
App app = new App();
app.ID = MCCDBUtility.GetDBValueInt(dr, "APPLICATION_ID");
app.Name = MCCDBUtility.GetDBValueString(dr, "APPNAME");
app.Desc = MCCDBUtility.GetDBValueString(dr, "APPDESCRIPTION");
app.Version = MCCDBUtility.GetDBValueString(dr, "APP_VERSION");
app.Type = MCCDBUtility.GetDBValueString(dr, "APPLICATIONTYPEID");
app.AreaName = MCCDBUtility.GetDBValueString(dr, "AREANAME");
return app;
}
What I am thinking though, so if I had a class for example like so:
[DataReaderHelper("MethodNameToGetType", "ColumnName")]
public string APPNAME {get;set;}
How could I go about this?
Fist of all, this is possible and if you like I could add a code sample.
But: This is not a good idea.
Why, you ask?
First - DataReader provides you with a method GetSchemaTable() which contains a property DataType which is a System.Type object. So basically you could create a MCCDBUtility.GetValue(dr, "columnName") that does the logic for your.
Second - What about you have a int property on your object but your datareader returns a decimal. For that case you can use Convert.ChangeType(value, type)
If you combine that you can achive what you want with
instance.Id = MCCDBUtility.GetValue<int>(dr, "columnName")
public T GetValue<T>(IDataReader reader, string columnName)
{
object value GetValue(reader, columnName);
return Convert.ChangeType(value, typeof(T));
}
private object GetValue(IDataReader reader, string columnName)
{
var schmema = reader.GetSchemaTable();
var dbType = typeof(object);
foreach(DataRowView row in schema.DefaultView)
if (row["columnName"].ToString().Equals(columnName, StringComparer.OrdinalIgnoreCase))
return row["ColumnType"];
if (dbType.Equals(typeof(int))
return GetInt(reader, columnName)
... // you get the point
else
return GetObject(reader, columnName);
}
And Third - Don't do this anyway there are great tools for mapping your query to your business objects. I don't want to name them all but a very lightweight and easy to understand is Dapper.NET, give it a try. https://github.com/StackExchange/dapper-dot-net
In combination with https://github.com/tmsmith/Dapper-Extensions you can easily map your database queries to your pocos
Update
As promised, here is the code for implementing on your own. Just create a Visual Studio Test project, insert the code and let it run. For readablity I omitted the unused IReadReader interface implementations, so you have to let intellisense create them for you.
Run the test and enjoy.
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestMethod1()
{
var values = new Dictionary<string, object>();
values.Add("ProductId", 17);
values.Add("ProductName", "Something");
values.Add("Price", 29.99M);
var reader = new FakeDataReader(values);
var product1 = new Product();
reader.SetValue(product1, p => p.Id);
reader.SetValue(product1, p => p.Name);
reader.SetValue(product1, p => p.Price);
Assert.AreEqual(17, product1.Id);
Assert.AreEqual("Something", product1.Name);
Assert.AreEqual(29.99M, product1.Price);
var product2 = new Product();
reader.SetAllValues(product2);
Assert.AreEqual(17, product2.Id);
Assert.AreEqual("Something", product2.Name);
Assert.AreEqual(29.99M, product2.Price);
}
}
public class Product
{
[Mapping("ProductId")]
public int Id { get; set; }
[Mapping("ProductName")]
public string Name { get; set; }
public decimal Price { get; set; }
}
[AttributeUsage(AttributeTargets.Property, AllowMultiple=false)]
public class MappingAttribute : Attribute
{
public MappingAttribute(string columnName)
{
this.ColumnName = columnName;
}
public string ColumnName { get; private set; }
}
public static class IDataReaderExtensions
{
public static void SetAllValues(this IDataReader reader, object source)
{
foreach (var prop in source.GetType().GetProperties())
{
SetValue(reader, source, prop);
}
}
public static void SetValue<T, P>(this IDataReader reader, T source, Expression<Func<T, P>> pe)
{
var property = (PropertyInfo)((MemberExpression)pe.Body).Member;
SetValue(reader, source, property);
}
private static void SetValue(IDataReader reader, object source, PropertyInfo property)
{
string propertyName = property.Name;
var columnName = propertyName;
var mapping = property.GetAttribute<MappingAttribute>();
if (mapping != null) columnName = mapping.ColumnName;
var value = reader.GetValue(reader.GetOrdinal(columnName));
var value2 = Convert.ChangeType(value, property.PropertyType);
property.SetValue(source, value2, null);
}
}
public static class ICustomFormatProviderExtensions
{
public static T GetAttribute<T>(this ICustomAttributeProvider provider)
{
return (T)provider.GetCustomAttributes(typeof(T), true).FirstOrDefault();
}
}
public class FakeDataReader : IDataReader
{
private Dictionary<string, object> values;
public FakeDataReader(Dictionary<string, object> values)
{
this.values = values;
}
public int GetOrdinal(string name)
{
int i = 0;
foreach (var key in values.Keys)
{
if (key.Equals(name, StringComparison.OrdinalIgnoreCase)) return i;
i++;
}
return -1;
}
public object GetValue(int i)
{
return values.Values.ToArray()[i];
}
}
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'm trying to sort a List<T>, without using OrderBy, OrderByDescending, where T is a custom class.
Code:
class Something
{
public string Category { get; set; }
public int Fingers { get; set; }
public DateTime Creation { get; set; }
}
The list order it's based on any property of T.
class BigRoom
{
var Room = new Room(new List<Something>());
}
class Room<T> where T: class, new()
{
List<T> baseList;
public Room(List<T> listPar)
{
baseList = listPar;
var prop = /* get any property from T with reflection... */
// How to set a comparer here, if we know prop (type, value...)
baseList.Sort(...);
// go do something with reordered list
}
}
I can do it knowing T and its properties, using lambda expressions or delegates.
list.Sort((x, y) => x.CompareTo(y));
But when getting prop values, it returns an object, which it doesn't implement CompareTo(), is there any way of achieving this, if so I'll be grateful.
Your Room constructor can be implemented like this(note i add a random for example purposes you can have the property chosen how you like it):
using System.ComponentModel;
public Room(List<T> listPar)
{
Random r = new Random(Environment.TickCount);
baseList = listPar;
var props = TypeDescriptor.GetProperties(typeof(T));
PropertyDescriptor prop = props[r.Next(props.Count)];
// How to set a comparer here, if we know prop (type, value...)
baseList.Sort((x, y) => prop.GetValue(x).ToString().CompareTo(prop.GetValue(y).ToString()));
// go do something with reordered list
}
So if the propertydescriptor is pointing to the Fingers property for example it will sort by those values,using the compareTo of the string class.
This should get you started. You'll need to actually clean up the Compare method to handle if the values are null i.e. either weren't set or are not IComparable which is required to actually be able to do comparisons.
PropertyInfo myPropertyFromReflection = GetMyPropertySomehow();
myList.Sort(new MyComparer<TransactionRequest>(myPropertyFromReflection));
public class MyComparer<T> : IComparer<T>
{
PropertyInfo _sortBy;
public MyComparer(PropertyInfo sortBy)
{
_sortBy = sortBy;
}
public int Compare(T x, T y)
{
var xValue = _sortBy.GetValue(x) as IComparable;
var yValue = _sortBy.GetValue(y) as IComparable;
return xValue.CompareTo(yValue);
}
}
objectlist is a list of objects you want to sort based on Status, then by Customer Name, then by Company Name, then by Billing Address
Assume entries in the object list have the following properties:
Status,
Customer Name,
Company Name,
Billing Address
objectlist.Sort(delegate(Object a, Object b)
{
if (String.CompareOrdinal(a.Status, b.Status) == 0)
{
return String.CompareOrdinal(a.CustomerName, b.CustomerName) == 0 ? String.CompareOrdinal(a.CompanyName, b.CompanyName) : String.CompareOrdinal(a.BillingAddress, b.BillingAddress);
}
if (a.Status.Equals("Very Important!")) { return -1; }
if (b.Status.Equals("Very Important!")) { return 1; }
if (a.Status.Equals("Important")) { return -1; }
if (b.Status.Equals("Important")) { return 1; }
if (a.Status.Equals("Not Important")){ return -1; }
return 1;
});
Hope this helps. =)
I have a datatable like this
Name| Value
----|------
NA | VA
NB | VB
NC | VC1
NC | VC2
ND | VD1
ND | VD2
and a class like this
Class NVMapping {
List<string> NC { get; set; }
List<string> ND { get; set; }
string NA { get; set; }
string NB { get; set; }
}
How to use linq or other way to transfer the datatable to this type ?
I think I need to emphasize one thing here. This kinda mapping will be a lot in my application.
Somehow I think using reflection can make this function be generic to handle all these kinda mapping.
So if possible, I would prefer a generic function like using reflection to achieve this.
If possible, it will even better just transfering datatable into an object like above transformation.
Thanks !
May I suggest writing a generic method that uses reflection. The following method uses reflection to populate a class's public properties from a DataRow in a DataTable (or a List<> of classes, one from each DataRow in the DataTable) where the ColumnName matches the name of the public property in the class exactly (case-sensitive).
If the DataTable has extra columns that don't match up to a property in the class, they are ignored. If the DataTable is missing columns to match a class property, that property is ignored and left at the default value for that type (since it is a property).
public static IList<T> DatatableToClass<T>(DataTable Table) where T : class, new()
{
if (!Helper.IsValidDatatable(Table))
return new List<T>();
Type classType = typeof(T);
IList<PropertyInfo> propertyList = classType.GetProperties();
// Parameter class has no public properties.
if (propertyList.Count == 0)
return new List<T>();
List<string> columnNames = Table.Columns.Cast<DataColumn>().Select(column => column.ColumnName).ToList();
List<T> result = new List<T>();
try
{
foreach (DataRow row in Table.Rows)
{
T classObject = new T();
foreach (PropertyInfo property in propertyList)
{
if (property != null && property.CanWrite) // Make sure property isn't read only
{
if (columnNames.Contains(property.Name)) // If property is a column name
{
if (row[property.Name] != System.DBNull.Value) // Don't copy over DBNull
{
object propertyValue = System.Convert.ChangeType(
row[property.Name],
property.PropertyType
);
property.SetValue(classObject, propertyValue, null);
}
}
}
}
result.Add(classObject);
}
return result;
}
catch
{
return new List<T>();
}
}
If you interested in going the other way, and fill out a DataTable from a class's public properties, I cover that and more on my C# blog, CSharpProgramming.tips/Class-to-DataTable
Here it is:
IEnumerable<DataRow> rows = table.AsEnumerable();
string naValue = null;
var naRow = rows.FirstOrDefault(r => r.Field<string>("Name") == "NA");
if(naRow != null)
naValue = naRow.Field<string>("Value");
string nbValue = null;
var nbRow = rows.FirstOrDefault(r => r.Field<string>("Name") == "NB");
if(nbRow != null)
nbValue = nbRow.Field<string>("Value");
NVMapping map = new NVMapping {
NC = rows.Where(r => r.Field<string>("Name") == "NC")
.Select(r => r.Field<string>("Value")).ToList(),
ND = rows.Where(r => r.Field<string>("Name") == "ND")
.Select(r => r.Field<string>("Value")).ToList(),
NA = naValue,
NB = nbValue
};