I'm creating a generic application to get all the data from different SQL Server tables.
I have a generic function that convert the SqlDataReader to a list:
public static List<T> MapToList<T>(this SqlDataReader dr) where T : new()
{
List<T> RetVal = null;
var Entity = typeof(T);
var PropDict = new Dictionary<string, PropertyInfo>();
try
{
if (dr != null && dr.HasRows)
{
RetVal = new List<T>();
var Props = Entity.GetProperties(BindingFlags.Instance | BindingFlags.Public);
PropDict = Props.ToDictionary(p => p.Name.ToUpper(), p => p);
while (dr.Read())
{
T newObject = new T();
for (int Index = 0; Index < dr.FieldCount; Index++)
{
if (PropDict.ContainsKey(dr.GetName(Index).ToUpper()))
{
var Info = PropDict[dr.GetName(Index).ToUpper()];
if ((Info != null) && Info.CanWrite)
{
var Val = dr.GetValue(Index);
Info.SetValue(newObject, (Val == DBNull.Value) ? null : Val, null);
}
}
}
RetVal.Add(newObject);
}
}
}
catch (Exception)
{
throw;
}
return RetVal;
}
Now suppose I have this class for my data:
public partial class User
{
public int Id { get; set; }
public string Name { get; set; }
}
I can fetch my data from the table something like this:
const string GetAreasQuery = "select id, name from dbo.user";
SqlDataReader dr = DoQueryToDB(GetAreasQuery);
List<User> userList = dr.MapToList<User>();
Now, I have n different classes like User (Classroom and so on) and I don't want to write the code above for each class I have. I would like to create a generic GetData to retrieve those information:
public List<T> GetData<T> (string Query_)
{
SqlDataReader dr = DataReader(Query_);
List<T> data = new List<T>();
data = dr.MapToList<T>();
return data;
}
where T can be User, Classroom and so on...
I tried this solution but I always have to specify the type:
public object GetData(string Query_, Type type)
{
SqlDataReader dr = DataReader(Query_);
if (type == typeof(User))
{
List<User> data = new List<User>();
data = dr.MapToList<User>();
return data;
}
else if (..)
{}
return null;
}
I'm trying different possibilities but I always obtain an error in GetData<T> function. More precisely in MapToList<T> like: T should be a non abstract type or public constructor without parameters.
You should add a contraint to the method GetData in order to achieve the same constraint level that is found in MapToList, which requires T to have a empty constructor
public List<T> GetData<T>(string Query_) where T : new()
{
SqlDataReader dr = DataReader(Query_);
return dr.MapToList<T>();
}
Related
I am using vs2022 and C# language and oracle as backend. oracle procedure gets input as facility type and based on type it returns the list of data and each facility type have common columns with few additional columns per type. on the below code, MaptoList is an extension class method which maps the reader column and value to class. the below logic is not working and says cannot convert from non generic list to generic. please help me to solve the compile error.
public class Common
{
public string Name { get; set; }
public string Address { get; set; }
public string State{ get; set; }
}
public class Employee : Common
{
public string SSN{ get; set; }
}
public class Vendor : Common
{
public string TaxNumber{ get; set; }
}
public async Task<List<T>> GetFacility(string Facility_Type)
{
using (EMPDB dbConnect = new EMPDB())
{
DbCommand dbCommand = dbConnect.Database.GetDbConnection().CreateCommand();
dbCommand.CommandType = CommandType.StoredProcedure;
dbCommand.CommandText = "GetFacilityReport";
dbCommand.BuildSqlParameter("Facility_Type", OracleDbType.Varchar2, Facility_Type, ParameterDirection.Input);
List<T> lstFacility_Response= new List<T>();
if (dbCommand.Connection.State != ConnectionState.Open)
{
dbCommand.Connection.Open();
}
using (var reader = await dbCommand.ExecuteReaderAsync())
{
switch (Facility_Type)
{
case "Employee":
lstFacility_Response= reader.MapToList<Employee>();
break;
case "Vendor":
lstFacility_Response= reader.MapToList<Vendor>();
break;
}
}
}
return lstFacility_Response
}
public static List<T> MapToList<T>(this DbDataReader dr) where T : new()
{
List<T> RetVal = null;
var Entity = typeof(T);
var PropDict = new Dictionary<string, PropertyInfo>();
try
{
if (dr != null && dr.HasRows)
{
RetVal = new List<T>();
var Props = Entity.GetProperties(BindingFlags.Instance | BindingFlags.Public);
PropDict = Props.ToDictionary(p => p.Name.ToUpper(), p => p);
while (dr.Read())
{
T newObject = new T();
for (int Index = 0; Index < dr.FieldCount; Index++)
{
if (PropDict.ContainsKey(dr.GetName(Index).ToUpper()))
{
var Info = PropDict[dr.GetName(Index).ToUpper()];
if ((Info != null) && Info.CanWrite)
{
var Val = dr.GetValue(Index);
Info.SetValue(newObject, (Val == DBNull.Value) ? null : Val, null);
}
}
}
RetVal.Add(newObject);
}
}
}
catch (Exception)
{
throw;
}
return RetVal;
}
If generics are appropriate here then this:
public async Task<List<T>> GetFacility(string Facility_Type)
should be this:
public async Task<List<T>> GetFacility<T>()
and then your switch statement should be replaced with this:
lstFacility_Response = reader.MapToList<T>();
You would then call it something like so:
var list = await GetFacility<Employee>();
When declaring the method, the method itself has to be generic and then you should be able to simply use the generic type parameter as a type in the method. If you can't do that then the method almost certainly shouldn't be generic in the first place.
If you want to limit T to be only type Common or its derived types then you can add a constraint:
public async Task<List<T>> GetFacility<T>() where T : Common
Given that your MapToList method has the new constraint, this method would have to as well, in order to call MapToList<T>():
public async Task<List<T>> GetFacility<T>() where T : Common, new()
I am trying to return derived class, but for some implementations I have to provide a little bit more setup, so I have if, but the compiler is not smart enough to know that once it enters the if, it should be able to cast T to Derived1. Is there any other way to do it?
Cannot convert type 'Derived1' to type T
public T GetData<T>(string name) where T : Data
{
if (_cache.ContainsKey(name))
return (T)_cache[name];
using (var db = new LiteDatabase(_dbPath))
{
if (typeof(T) is ChartData)
{
var collection = db.GetCollection<ChartData>("Data");
var chunksColl = db.GetCollection<ValuesChunk>(nameof(ValuesChunk));
var data = collection.Include(d => d.Series).FindOne(Query.EQ(nameof(ChartData.Name), name));
foreach (var series in data.Series)
{
for (int index = 0; index < series.Chunks.Count; index++)
{
var chunk = chunksColl.FindById(series.Chunks[index].ChunkId);
series.ChangeChunk(index, chunk);
}
}
_cache.Add(name, data);
return (Data)data;
}
else
{
var collection = db.GetCollection<T>();
return collection.FindOne(Query.EQ(nameof(ChartData.Name), name));
}
}
}
public abstract class Data {/*properties*/}
public class ChartData : Data {/*properties*/}
public class ScriptData : Data {/*properties*/}
Couple of mistakes in the above, this works (whether its the best design is a different question):
public T Get<T>() where T : Base
{
if (typeof(T) == typeof(Derived1)) //correction to this line
{
var derived = new Derived1();
derived.Property1 = 5;
return derived as T; //correction to this line
}
else
{
// return (T)myMethod;
}
return null;
}
or with your edited version:
public T GetData<T>(string name) where T : Data
{
if (_cache.ContainsKey(name))
return (T)_cache[name];
using (var db = new LiteDatabase(_dbPath))
{
if (typeof(T) == typeof( ChartData)) //<======CORRECTION HERE
{
var collection = db.GetCollection<ChartData>("Data");
var chunksColl = db.GetCollection<ValuesChunk>(nameof(ValuesChunk));
var data = collection.Include(d => d.Series).FindOne(Query.EQ(nameof(ChartData.Name), name));
foreach (var series in data.Series)
{
for (int index = 0; index < series.Chunks.Count; index++)
{
var chunk = chunksColl.FindById(series.Chunks[index].ChunkId);
series.ChangeChunk(index, chunk);
}
}
_cache.Add(name, data);
return data as Data;//<======CORRECTION HERE
}
else
{
var collection = db.GetCollection<T>();
return collection.FindOne(Query.EQ(nameof(ChartData.Name), name));
}
}
}
I'm writing an SQL Data Mapper that would map queries to dynamic types (I've already experienced mapping sql to strongly typed objects through reflection)
This time I want to make a sql call then return an Ienumerable.
This is what I have so far:
public class DynamicMapper
{
private class ReaderColumnMap
{
public string Column { get; }
public Type Type { get; }
public ReaderColumnMap(string column, Type type)
{
Column = column;
Type = type;
}
}
private Func<IDataReader, dynamic> MappingMethod;
public DynamicMapper(IDataReader schemaSource)
{
MappingMethod = CreateMapper(schemaSource);
}
private Func<IDataReader, dynamic> CreateMapper(IDataReader reader)
{
IDictionary<int, ReaderColumnMap> propertyMappingsCache = new Dictionary<int, ReaderColumnMap>();
//The schema of sql table involves the column name and data type for each columns.
DataTable schema = reader.GetSchemaTable();
DataColumnCollection dataColumns = schema.Columns;
//Gets the column name ordinal and data type ordinal
int nameColumn = dataColumns["ColumnName"].Ordinal;
int typeColumn = dataColumns["DataType"].Ordinal;
int ordinalColumn = dataColumns["ColumnOrdinal"].Ordinal;
DataRowCollection schemaRows = schema.Rows;
int schemaRowCount = schemaRows.Count - 1;
//Populates the property mappings
for (int i = schemaRowCount; i != -1; --i)
{
DataRow row = schemaRows[i];
string name = row[nameColumn].ToString();
Type type = Type.GetType(row[typeColumn].ToString());
int ordinal = Convert.ToInt32(row[ordinalColumn]);
propertyMappingsCache.Add(ordinal, new ReaderColumnMap(name, type));
}
//Use expression tree here to create dynamic object (new() { Prop1 = "prop1", Prop2 = prop2... })
//Dictionary sample:
//Id = System.Int64
//Name = System.String
//Birthdate = System.DateTime
//Target Expression:
//dynamic instance = new (){
// Id = (System.Int64)reader[0];
// Name = (System.String)reader[1];
// Birthdate = (System.DateTime)reader[2];
//}
var expressions = new List<Expression>();
var fxParameterExpression = Expression.Parameter(typeof(IDataReader), "reader");
//Compiler error: the typeof operator cannot be used on the dynamic type
var variableExpression = Expression.Variable(typeof(dynamic), "instance"); //dynamic instance = new ()
//Compiler error: the typeof operator cannot be used on the dynamic type
var instantiationExpression = Expression.New(typeof(dynamic));
var assignExpression = Expression.Assign(variableExpression, instantiationExpression);
var indexer = typeof(IDataReader).GetProperty("Item", new[] { typeof(int) }); // []
expressions.Add(assignExpression);
int propertyCount = propertyMappingsCache.Count - 1;
for(int i = propertyCount; i != -1; --i)
{
//Get property details from mappingsCache (string property name and Type of property)
//to create property
}
return null;
}
public dynamic CreateMappedInstance(IDataReader reader)
{
return MappingMethod.Invoke(reader);
}
}
But the compiler is not letting me to instantiate dynamic type: the typeof operator cannot be used on the dynamic type
I plan to call it in sql module like:
public class SqlCaller
{
private readonly ISqlProvider sqlProvider;
public SqlCaller(ISqlProvider sqlProvider)
{
this.sqlProvider = sqlProvider ?? throw new ArgumentNullException("sqlProvider");
}
public DataTable Query(string queryString)...
public DataTable Query(DbCommand command)...
public DataTable GetSchema(string queryString)...
public int ExecuteNonQuery(DbCommand command)...
public int ExecuteNonQuery(string commandString)...
public object ExecuteScalar(string queryString)...
public object ExecuteScalar(DbCommand command)...
public bool ExecuteTransaction(IEnumerable<DbCommand> commands)...
[Obsolete]
public IEnumerable<T> Get<T>(Func<IDataReader, List<T>> mappingMethod, string query)...
[Obsolete]
public IEnumerable<T> Get<T>(Func<IDataReader, List<T>> mappingMethod, DbCommand command)...
public IEnumerable<T> Get<T>(string query) where T : class, new()...
public IEnumerable<T> Get<T>(DbCommand command) where T : class, new()...
public IEnumerable<T> Get<T>(IDataMapper<T> dataMapper, DbCommand command) where T : class, new()...
public IEnumerable<dynamic> GetDynamic(string commandString)
{
return GetDynamic(sqlProvider.CreateCommand(commandString));
}
public IEnumerable<dynamic> GetDynamic(DbCommand command)
{
List<dynamic> temp = new List<dynamic>();
using (DbConnection connection = sqlProvider.CreateConnection())
{
command.Connection = connection;
try
{
command.Connection.Open();
IDataReader reader = command.ExecuteReader();
var watch = Stopwatch.StartNew();
DynamicMapper mapper = new DynamicMapper(reader);
while(reader.Read()) temp.Add(mapper.CreateMappedInstance(reader));
watch.Stop();
}
catch
{
throw;
}
finally
{
command.Connection.Close();
}
}
return temp;
}
}
How to instantiate a dynamic type inside an expression tree
How to set property values of a dynamic type inside an expression tree
I am adding new features to an old asp.net application which uses n-tier architecture. A basic example can be given as
Object
public class Test
{
public int ID{get;set;}
public int name{get;set;}
}
Data Access Layer
public static List<Test> GetTests()
{
List<Test> list = new List<Test>();
try
{
//codes
SqlDataReader dr = com.ExecuteReader();
while(dr.Read())
list.Add(FillTestRecord(dr))
//codes
}
catch{}
return list;
}
private static Test FillTestRecord(IDataRecord dr)
{
Test test = new Test();
try{test.ID = Convert.ToInt32(dr["ID"]);}
catch{}
try{test.Name = Convert.ToInt32(dr["Name"]);}
catch{}
return test;
}
The development requires me to add new fields to object classes and for re-usability i use only one Fill*Record Method for each type of object. This method can be called by many other DAL methods whose IDataRecord might not contain all the columns of the object. Hence I put try-catch block for every property separately. This ensures that all the available columns in IDataRecord are parsed properly.
My question is that is there any better way of doing it? And what are the best practices in this type of architecture?
UPDATE
After reading the comment/answer of David L and Anup I have tried another way to do it using Extension Method. The method is as follows
public static bool TryGetOrdinal(this IDataRecord dr, string column, out int ordinal)
{
try
{
ordinal = dr.GetOrdinal(column);
}
catch(Exception ex)
{
ordinal = -1; //Just setting a value that GetOrdinal doesn't return
return false;
}
return true;
}
so the FillTestRecord method will be
private static Test FillTestRecord(IDataRecord dr)
{
Test test = new Test();
int ordinal = default(int);
if(dr.TryGetOrdinal("ID",out ordinal))
test.ID = Convert.ToInt32(dr.GetValue(ordinal));
if(dr.TryGetOrdinal("Name",out ordinal))
test.Name = Convert.ToString(dr.GetValue(ordinal));
return test;
}
any suggestion on this is highly appreciated.
UPDATE 03-02-2016
during debugging i found that the try-catch takes a big toll on performance if GetOrdinal throws error when the supplied column name is not found in the DataRecord. So I wrote a new method that gets the column names in the DataReader and replaced GetOrdinal with Array.IndexOf.
public static bool TryGetOrdinal(this IDataRecord dr, string[] columnNames, string column, out int ordinal)
{
ordinal = Array.IndexOf(columnNames, column);
return ordinal >= 0;
}
And my FillTestRecord becomes -
private static Test FillTestRecord(IDataRecord dr, string[] columnNames)
{
Test test = new Test();
int ordinal = default(int);
if(dr.TryGetOrdinal(columnNames, "id",out ordinal))
test.ID = Convert.ToInt32(dr.GetValue(ordinal));
if(dr.TryGetOrdinal(columnNames, "name",out ordinal))
test.Name = Convert.ToString(dr.GetValue(ordinal));
return test;
}
column names are passed to Fill method like this -
using (var dr = com.ExecuteReader())
{
string[] colNames = dr.GetColumnNames();
while (dr.Read())
list.Add(FillTestRecord(dr, colNames));
}
'GetColumnNames' is the new extension method -
public static string[] GetColumnNames(this IDataReader dr)
{
string[] columnNames = new string[dr.FieldCount];
for (int i = 0; i < dr.FieldCount; i++)
{
columnNames[i] = dr.GetName(i).ToLower();
}
return columnNames;
}
Seems to me that you are in the right direction.
As long as the parsing is being done in a centralized location which is re-used by all upper level classes, this is looks like a good solution.
The only thing I would change is replacing the try-catch statements with checking if the data exists in the columns. surely there is a way to tell (column does not exist? DB-Null value?)
You could implement that using a something similar to the TryParse methods.
private static Test FillTestRecord(IDataRecord dr)
{
Test test = new Test();
int tempId;
if (TryParseDataRow<int>(dr, "ID", out tempId))
{
test.Id = tempId;
}
return test;
}
private static bool TryParseDataRow<T>(IDataRecord record, string column, out T value)
{
value = default(T);
bool success = true;
if (record == null)
{
//nothing you can do with a null object
success = false;
}
else if (!record.HasColumn(column)) //not sure if this will throw exeption or return null. you can check in your project
{
success = false;
}
else if (record[column] != typeof(T))
{
//object was of an unexpected type
success = false;
}
else
{
//cast the value into the output parameter
value = (T)record[column];
}
return success;
}
And of course you will have to implement the HasColumn method (Implemented here as extension):
/// <summary>
/// Determines whether the specified record has column.
/// </summary>
/// <param name="record">The record.</param>
/// <param name="columnName">Name of the column.</param>
/// <returns>true if column exist, false otherwise</returns>
public static bool HasColumn(this IDataRecord record, string columnName)
{
for (int i = 0; i < record.FieldCount; i++)
{
if (record.GetName(i).Equals(columnName, StringComparison.InvariantCultureIgnoreCase))
return true;
}
return false;
}
Here's some code I put together for mapping IDataRecord's to properties
public static T ParseRecord<T>(this IDataRecord reader) where T : new()
{
var model = new T();
var type = typeof(T);
for (int i = 0; i < reader.FieldCount; i++) {
var fieldType = reader.GetFieldType(i);
var fieldName = reader.GetName(i);
var val = reader.GetValue(i);
var prop = type.GetProperty(fieldName);
// handle or throw instead here if needed
if (prop == null)
continue;
var propType = prop.PropertyType;
// HACK: remove this if you don't want to coerce to strings
if (propType == typeof(string))
prop.SetValue(model, val.ToString());
else if (fieldType != propType)
throw new Exception($"Type mismatch field {fieldType} != prop {propType}");
else
prop.SetValue(model, val);
}
return model;
}
I am using the following code to map properties of different objects. It uses reflection to get the properties of the source and target objects, but you can easily change it to work with IDataRecord:
public static T MapDTO<T>(object dto) where T : new()
{
T Result = new T();
if (dto == null)
return Result;
dto.GetType().GetProperties().ToList().ForEach(p =>
{
PropertyInfo prop = Result.GetType().GetProperty(p.Name);
if (prop != null && prop.CanWrite)
{
try
{
var convertedVal = Convert.ChangeType(p.GetValue(dto, null), prop.PropertyType);
prop.SetValue(Result, convertedVal, null);
}
catch (Exception ex)
{
try
{
prop.SetValue(Result, p.GetValue(dto, null), null);
}
catch (Exception ex1) { }
}
}
});
return Result;
}
The key here is that the source and dest properties should have same names.
Here is my c# code
Employee objEmp = new Employee();
List<Employee> empList = new List<Employee>();
foreach (DataRow dr in ds.Tables[0].Rows)
{
empList.Add(new Employee { Name = Convert.ToString(dr["Name"]), Age = Convert.ToInt32(dr["Age"]) });
}
It uses a loop to create a List from a dataset.Is there any direct method or shorter method or one line code to convert dataset to list
Try something like this:
var empList = ds.Tables[0].AsEnumerable()
.Select(dataRow => new Employee
{
Name = dataRow.Field<string>("Name")
}).ToList();
Here's extension method to convert DataTable to object list:
public static class Extensions
{
public static List<T> ToList<T>(this DataTable table) where T : new()
{
IList<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, IList<PropertyInfo> properties) where T : new()
{
T item = new T();
foreach (var property in properties)
{
if (property.PropertyType == typeof(System.DayOfWeek))
{
DayOfWeek day = (DayOfWeek)Enum.Parse(typeof(DayOfWeek), row[property.Name].ToString());
property.SetValue(item,day,null);
}
else
{
if(row[property.Name] == DBNull.Value)
property.SetValue(item, null, null);
else
{
if (Nullable.GetUnderlyingType(property.PropertyType) != null)
{
//nullable
object convertedValue = null;
try
{
convertedValue = System.Convert.ChangeType(row[property.Name], Nullable.GetUnderlyingType(property.PropertyType));
}
catch (Exception ex)
{
}
property.SetValue(item, convertedValue, null);
}
else
property.SetValue(item, row[property.Name], null);
}
}
}
return item;
}
}
usage:
List<Employee> lst = ds.Tables[0].ToList<Employee>();
#itay.b
CODE EXPLAINED:
We first read all the property names from the class T using reflection
then we iterate through all the rows in datatable and create new object of T,
then we set the properties of the newly created object using reflection.
The property values are picked from the row's matching column cell.
PS: class property name and table column names must be same
var myData = ds.Tables[0].AsEnumerable().Select(r => new Employee {
Name = r.Field<string>("Name"),
Age = r.Field<int>("Age")
});
var list = myData.ToList(); // For if you really need a List and not IEnumerable
Use the code below:
using Newtonsoft.Json;
string JSONString = string.Empty;
JSONString = JsonConvert.SerializeObject(ds.Tables[0]);
Try this....modify the code as per your needs.
List<Employee> target = dt.AsEnumerable()
.Select(row => new Employee
{
Name = row.Field<string?>(0).GetValueOrDefault(),
Age= row.Field<int>(1)
}).ToList();
DataSet ds = new DataSet();
ds = obj.getXmlData();// get the multiple table in dataset.
Employee objEmp = new Employee ();// create the object of class Employee
List<Employee > empList = new List<Employee >();
int table = Convert.ToInt32(ds.Tables.Count);// count the number of table in dataset
for (int i = 1; i < table; i++)// set the table value in list one by one
{
foreach (DataRow dr in ds.Tables[i].Rows)
{
empList.Add(new Employee { Title1 = Convert.ToString(dr["Title"]), Hosting1 = Convert.ToString(dr["Hosting"]), Startdate1 = Convert.ToString(dr["Startdate"]), ExpDate1 = Convert.ToString(dr["ExpDate"]) });
}
}
dataGridView1.DataSource = empList;
Add a new class named as "Helper" and change the property of the class to "public static"
public static class Helper
{
public static List<T> DataTableToList<T>(this DataTable table) where T : class, new()
{
try
{
List<T> list = new List<T>();
foreach (var row in table.AsEnumerable())
{
T obj = new T();
foreach (var prop in obj.GetType().GetProperties())
{
try
{
PropertyInfo propertyInfo = obj.GetType().GetProperty(prop.Name);
propertyInfo.SetValue(obj, Convert.ChangeType(row[prop.Name], propertyInfo.PropertyType), null);
}
catch
{
continue;
}
}
list.Add(obj);
}
return list;
}
catch
{
return null;
}
}
}
and access this class in your code behind as like below
DataTable dtt = dsCallList.Tables[0];
List<CallAssignment> lstCallAssignement = dtt.DataTableToList<CallAssignment>();
Fill the dataset with data from, say a stored proc command
DbDataAdapter adapter = DbProviderFactories.GetFactory(cmd.Connection).CreateDataAdapter();
adapter.SelectCommand = cmd;
DataSet ds = new DataSet();
adapter.Fill(ds);
Get The Schema,
string s = ds.GetXmlSchema();
save it to a file say: datasetSchema.xsd. Generate the C# classes for the Schema: (at the VS Command Prompt)
xsd datasetSchema.xsd /c
Now, when you need to convert the DataSet data to classes you can deserialize (the default name given to the generated root class is NewDataSet):
public static T Create<T>(string xml)
{
XmlSerializer serializer = new XmlSerializer(typeof(T));
using (StringReader reader = new StringReader(xml))
{
T t = (T)serializer.Deserialize(reader);
reader.Close();
return t;
}
}
var xml = ds.GetXml();
var dataSetObjects = Create<NewDataSet>(xml);
I couldn't get Nitin Sawant's answer to work, but I was able to modify his code to work for me. Essentially I needed to use GetRuntimeFields instead of GetProperties. Here's what I ended up with:
public static class Extensions
{
public static List<T> ToList<T>(this DataTable table) where T : new()
{
IList<FieldInfo> fields = typeof(T).GetRuntimeFields().ToList();
List<T> result = new List<T>();
if (row.Table.Columns.Contains(field.Name))
{
foreach (var row in table.Rows)
{
var item = CreateItemFromRow<T>((DataRow)row, fields);
result.Add(item);
}
}
return result;
}
private static T CreateItemFromRow<T>(DataRow row, IList<FieldInfo> fields) where T : new()
{
T item = new T();
foreach (var field in fields)
{
if (row[field.Name] == DBNull.Value)
field.SetValue(item, null);
else
field.SetValue(item, row[field.Name]);
}
return item;
}
}
Try the above which will run with any list type.
public DataTable ListToDataTable<T>(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++)
{
values[i] = props[i].GetValue(item);
}
table.Rows.Add(values);
}
return table;
}
List<GSTEntity.gst_jobwork_to_mfgmaster> ListToGetJwToMfData = new List<GSTEntity.gst_jobwork_to_mfgmaster>();
DataSet getJwtMF = new DataSet();
getJwtMF = objgst_jobwork_to_mfgmaster_BLL.GetDataJobWorkToMfg(AssesseeId, PremiseId, Fyear, MonthId, out webex);
if(getJwtMF.Tables["gst_jobwork_to_mfgmaster"] != null)
{
ListToGetJwToMfData = (from master in getJwtMF.Tables["gst_jobwork_to_mfgmaster"].AsEnumerable() select new GSTEntity.gst_jobwork_to_mfgmaster { Partygstin = master.Field<string>("Partygstin"), Partystate =
master.Field<string>("Partystate"), NatureOfTransaction = master.Field<string>("NatureOfTransaction"), ChallanNo = master.Field<string>("ChallanNo"), ChallanDate=master.Field<int>("ChallanDate"), OtherJW_ChallanNo=master.Field<string>("OtherJW_ChallanNo"), OtherJW_ChallanDate = master.Field<int>("OtherJW_ChallanDate"),
OtherJW_GSTIN=master.Field<string>("OtherJW_GSTIN"),
OtherJW_State = master.Field<string>("OtherJW_State"),
InvoiceNo = master.Field<string>("InvoiceNo"),
InvoiceDate=master.Field<int>("InvoiceDate"),
Description =master.Field<string>("Description"),
UQC= master.Field<string>("UQC"),
qty=master.Field<decimal>("qty"),
TaxValue=master.Field<decimal>("TaxValue"),
Id=master.Field<int>("Id")
}).ToList();