Class to DataSet / DataSet to class - c#

Right now I'm trying to create a database handler that handles databases dynamically. Say I have a class of fieldtypes "String, int, bool, String" and want to turn this class into a table and all the fieldtypes into "fields" of a dataset?
How can I do that?
Also, can I create a few classes that inherits: "System.Data.DataSet", "System.Data.DataTable", "System.Data.DataRow", and some sort of Adapter to handle it.
I know when you go into design mode and create a dataset the code is really hard to make out when you look at it. But shouldn't it be possible by using these objects and creating classes that handle them to be able to "create" database dynamically. It wouldn't get a "Designer View" but, Database.AddTable(new Table("Field1", "Field2, "Field3")) should do the same as you do graphically in Designer mode.
Any ideas?
The main idea is that it's flexible and I can take whatever class and turn it into a table of rows with each object of this class' field values as database fields.
UPDATE
This is just a simple class that I made in the last hour, am I thinking the right way? Still need to be able to add DataSet to the *.mdf / *.sdf file, how do I do that?
public class DataAccess
{
SqlConnection connection;
DataSet dataSet;
public DataAccess(String databaseName, String connectionStr)
{
dataSet = new DataSet(databaseName);
connection = new SqlConnection(connectionStr);
}
public void AddTable<T>(String tableName)
{
dataSet.Tables.Add(new DBTable<T>(tableName));
}
public void AddRow<T>(String tableName, T row)
{
((DBTable<T>)dataSet.Tables[tableName]).AddRow<T>(row);
}
public List<C> GetRows<T, C>(String tableName, String columnName)
{
return ((DBTable<T>)dataSet.Tables[tableName]).GetRow<C>(columnName);
}
public String GetXML()
{
return dataSet.GetXml();
}
#region METHOD PROPERTIES
public int ColumnCount(String tableName)
{
for (int i = 0; i < dataSet.Tables.Count; i++)
{
if (dataSet.Tables[i].TableName == tableName)
{
return dataSet.Tables[i].Columns.Count;
}
}
return 0;
}
#endregion
}
public class DBTable<T> : System.Data.DataTable
{
public DBTable(String tableName) : base(tableName)
{
PropertyInfo[] properties = typeof(T).GetProperties();
for (int i = 0; i < properties.Length; i++)
{
try
{
AddColumn(properties[i].Name, properties[i].PropertyType);
}
catch { }
}
}
private void AddColumn(String name, Type t)
{
this.Columns.Add(new DataColumn(name, t, null, MappingType.Attribute));
}
public void AddRow<T>(T obj)
{
PropertyInfo[] properties = typeof(T).GetProperties();
if (properties.Length == this.Columns.Count)
{
bool valid = true;
for (int i = 0; i < properties.Length; i++)
{
if (properties[i].PropertyType != this.Columns[i].DataType)
{
valid = false;
}
}
if (valid)
{
object[] p = new object[this.Columns.Count];
for (int i = 0; i < properties.Length; i++)
{
p[i] = properties[i].GetValue(obj, null);
}
this.Rows.Add(p);
}
}
}
public List<T> GetRow<T>(String columnName)
{
List<T> objects = new List<T>();
for (int i = 0; i < this.Rows.Count; i++)
{
objects.Add((T)this.Rows[i][this.Columns[columnName]]);
}
return objects;
}
}

The Petapoco library is what you are looking for. You can save and retrieve data from database to POCO objects and vice-versa very easily.
You might have to take a look about generic lists and some bits of LINQ : the Petapoco approach does not use DataSet nor DataTable objects.
As leppie stated, this is not quite the answer to the question. So, creating a DataTable object that matches a class could be achieved this way :
public static DataTable CreateDataTable<T>()
{
var dt = new DataTable();
var propList = typeof(T).GetMembers(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
foreach(MemberInfo info in propList)
{
if(info is PropertyInfo)
dt.Columns.Add(new DataColumn(info.Name, (info as PropertyInfo).PropertyType));
else if(info is FieldInfo)
dt.Columns.Add(new DataColumn(info.Name, (info as FieldInfo).FieldType));
}
return dt;
}
If you wish to fill this table afterwards :
public static void FillDataTable<T>(DataTable dt, List<T> items)
{
var propList = typeof(T).GetMembers(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
foreach(T t in items)
{
var row = dt.NewRow();
foreach(MemberInfo info in propList)
{
if (info is PropertyInfo)
row[info.Name] = (info as PropertyInfo).GetValue(t, null);
else if (info is FieldInfo)
row[info.Name] = (info as FieldInfo).GetValue(t);
}
dt.Rows.Add(row);
}
}
You could also mix both features in a single function if you wish your DataTable object to be created and filled in the same time.

Related

Generic function to get data from SqlDataReader

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

SetValue with generic type T

I have this function:
the variable c obtains all the properties of my class <T>
in this case:
c ->
Id
Key
Value
public List<T> ReadStoreProceadure<T>(string storeName)
{
var result = new List<T>();
var instance = (T) Activator.CreateInstance(typeof (T), new object[] {});
var c = typeof (T);
var data = DataReader.ReadStoredProceadures(_factibilidad, storeName); // This part is returning verified data and it's ok
while (data.Read())
{
if (data.HasRows)
{
foreach (var item in c.GetProperties())
{
//item.SetValue(c, item.Name, null);
}
}
}
}
How I can add these values to my instance instance and add it to my result variable?
It's possible?
I've created an extension method for IDataReader that does essentially what I believe you're trying to do:
public static List<T> ToList<T>(this IDataReader dr) where T: new()
{
var col = new List<T>();
var type = typeof(T);
var props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
while (dr.Read())
{
var obj = new T();
for (int i = 0; i < dr.FieldCount; i++)
{
string fieldName = dr.GetName(i);
var prop = props.FirstOrDefault(x => x.Name.ToLower() == fieldName.ToLower());
if (prop != null)
{
if (dr[i] != DBNull.Value)
{
prop.SetValue(obj, dr[i], null);
}
}
}
col.Add(obj);
}
dr.Close();
return col;
}
However, you'll notice I've chosen to work the from the other way around. Instead of iterating the type's properties and fetching them from the DataReader, I iterate the DataReader columns and check for a matching property on the type. You should be able to quickly modify this to fit your data retrieval scheme.

What are the best practices to parse data into objects in data access layer?

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.

Convert DataSet to List

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

Convert DataTable to IEnumerable<T>

I am trying to convert a DataTable to an IEnumerable. Where T is a custom type I created. I know I can do it by creating a List<T> but I was thinking if there is a slicker way to do it using IEnumerable. Here is what I have now:
private IEnumerable<TankReading> ConvertToTankReadings(DataTable dataTable)
{
var tankReadings = new List<TankReading>();
foreach (DataRow row in dataTable.Rows)
{
var tankReading =
new TankReading
{
TankReadingsID = Convert.ToInt32(row["TRReadingsID"]),
TankID = Convert.ToInt32(row["TankID"]),
ReadingDateTime = Convert.ToDateTime(row["ReadingDateTime"]),
ReadingFeet = Convert.ToInt32(row["ReadingFeet"]),
ReadingInches = Convert.ToInt32(row["ReadingInches"]),
MaterialNumber = row["MaterialNumber"].ToString(),
EnteredBy = row["EnteredBy"].ToString(),
ReadingPounds = Convert.ToDecimal(row["ReadingPounds"]),
MaterialID = Convert.ToInt32(row["MaterialID"]),
Submitted = Convert.ToBoolean(row["Submitted"]),
};
tankReadings.Add(tankReading);
}
return tankReadings.AsEnumerable();
}
The key part being I am creating a List<T> then returning it using AsEnumerable().
There's also a DataSetExtension method called "AsEnumerable()" (in System.Data) that takes a DataTable and returns an Enumerable. See the MSDN doc for more details, but it's basically as easy as:
dataTable.AsEnumerable()
The downside is that it's enumerating DataRow, not your custom class. A "Select()" LINQ call could convert the row data, however:
private IEnumerable<TankReading> ConvertToTankReadings(DataTable dataTable)
{
return dataTable.AsEnumerable().Select(row => new TankReading
{
TankReadingsID = Convert.ToInt32(row["TRReadingsID"]),
TankID = Convert.ToInt32(row["TankID"]),
ReadingDateTime = Convert.ToDateTime(row["ReadingDateTime"]),
ReadingFeet = Convert.ToInt32(row["ReadingFeet"]),
ReadingInches = Convert.ToInt32(row["ReadingInches"]),
MaterialNumber = row["MaterialNumber"].ToString(),
EnteredBy = row["EnteredBy"].ToString(),
ReadingPounds = Convert.ToDecimal(row["ReadingPounds"]),
MaterialID = Convert.ToInt32(row["MaterialID"]),
Submitted = Convert.ToBoolean(row["Submitted"]),
});
}
Nothing wrong with that implementation. You might give the yield keyword a shot, see how you like it:
private IEnumerable<TankReading> ConvertToTankReadings(DataTable dataTable)
{
foreach (DataRow row in dataTable.Rows)
{
yield return new TankReading
{
TankReadingsID = Convert.ToInt32(row["TRReadingsID"]),
TankID = Convert.ToInt32(row["TankID"]),
ReadingDateTime = Convert.ToDateTime(row["ReadingDateTime"]),
ReadingFeet = Convert.ToInt32(row["ReadingFeet"]),
ReadingInches = Convert.ToInt32(row["ReadingInches"]),
MaterialNumber = row["MaterialNumber"].ToString(),
EnteredBy = row["EnteredBy"].ToString(),
ReadingPounds = Convert.ToDecimal(row["ReadingPounds"]),
MaterialID = Convert.ToInt32(row["MaterialID"]),
Submitted = Convert.ToBoolean(row["Submitted"]),
};
}
}
Also the AsEnumerable isn't necessary, as List<T> is already an IEnumerable<T>
Simple method using System.Data.DataSetExtensions:
table.AsEnumerable().Select(row => new TankReading{
TankReadingsID = Convert.ToInt32(row["TRReadingsID"]),
TankID = Convert.ToInt32(row["TankID"]),
ReadingDateTime = Convert.ToDateTime(row["ReadingDateTime"]),
ReadingFeet = Convert.ToInt32(row["ReadingFeet"]),
ReadingInches = Convert.ToInt32(row["ReadingInches"]),
MaterialNumber = row["MaterialNumber"].ToString(),
EnteredBy = row["EnteredBy"].ToString(),
ReadingPounds = Convert.ToDecimal(row["ReadingPounds"]),
MaterialID = Convert.ToInt32(row["MaterialID"]),
Submitted = Convert.ToBoolean(row["Submitted"]),
});
Or:
TankReading TankReadingFromDataRow(DataRow row){
return new TankReading{
TankReadingsID = Convert.ToInt32(row["TRReadingsID"]),
TankID = Convert.ToInt32(row["TankID"]),
ReadingDateTime = Convert.ToDateTime(row["ReadingDateTime"]),
ReadingFeet = Convert.ToInt32(row["ReadingFeet"]),
ReadingInches = Convert.ToInt32(row["ReadingInches"]),
MaterialNumber = row["MaterialNumber"].ToString(),
EnteredBy = row["EnteredBy"].ToString(),
ReadingPounds = Convert.ToDecimal(row["ReadingPounds"]),
MaterialID = Convert.ToInt32(row["MaterialID"]),
Submitted = Convert.ToBoolean(row["Submitted"]),
};
}
// Now you can do this
table.AsEnumerable().Select(row => return TankReadingFromDataRow(row));
Or, better yet, create a TankReading(DataRow r) constructor, then this becomes:
table.AsEnumerable().Select(row => return new TankReading(row));
If you are producing the DataTable from an SQL query, have you considered simply using Dapper instead?
Then, instead of making a SqlCommand with SqlParameters and a DataTable and a DataAdapter and on and on, which you then have to laboriously convert to a class, you just define the class, make the query column names match the field names, and the parameters are bound easily by name. You already have the TankReading class defined, so it will be really simple!
using Dapper;
// Below can be SqlConnection cast to DatabaseConnection, too.
DatabaseConnection connection = // whatever
IEnumerable<TankReading> tankReadings = connection.Query<TankReading>(
"SELECT * from TankReading WHERE Value = #value",
new { value = "tank1" } // note how `value` maps to `#value`
);
return tankReadings;
Now isn't that awesome? Dapper is very optimized and will give you darn near equivalent performance as reading directly with a DataAdapter.
If your class has any logic in it at all or is immutable or has no parameterless constructor, then you probably do need to have a DbTankReading class (as a pure POCO/Plain Old Class Object):
// internal because it should only be used in the data source project and not elsewhere
internal sealed class DbTankReading {
int TankReadingsID { get; set; }
DateTime? ReadingDateTime { get; set; }
int ReadingFeet { get; set; }
int ReadingInches { get; set; }
string MaterialNumber { get; set; }
string EnteredBy { get; set; }
decimal ReadingPounds { get; set; }
int MaterialID { get; set; }
bool Submitted { get; set; }
}
You'd use that like this:
IEnumerable<TankReading> tankReadings = connection
.Query<DbTankReading>(
"SELECT * from TankReading WHERE Value = #value",
new { value = "tank1" } // note how `value` maps to `#value`
)
.Select(tr => new TankReading(
tr.TankReadingsID,
tr.ReadingDateTime,
tr.ReadingFeet,
tr.ReadingInches,
tr.MaterialNumber,
tr.EnteredBy,
tr.ReadingPounds,
tr.MaterialID,
tr.Submitted
});
Despite the mapping work, this is still less painful than the data table method. This also lets you perform some kind of logic, though if the logic is any more than very simple straight-across mapping, I'd put the logic into a separate TankReadingMapper class.
If you want to convert any DataTable to a equivalent IEnumerable vector function.
Please take a look at the following generic function, this may help your needs (you may need to include write cases for different datatypes based on your needs).
/// <summary>
/// Get entities from DataTable
/// </summary>
/// <typeparam name="T">Type of entity</typeparam>
/// <param name="dt">DataTable</param>
/// <returns></returns>
public IEnumerable<T> GetEntities<T>(DataTable dt)
{
if (dt == null)
{
return null;
}
List<T> returnValue = new List<T>();
List<string> typeProperties = new List<string>();
T typeInstance = Activator.CreateInstance<T>();
foreach (DataColumn column in dt.Columns)
{
var prop = typeInstance.GetType().GetProperty(column.ColumnName, BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public);
if (prop != null)
{
typeProperties.Add(column.ColumnName);
}
}
foreach (DataRow row in dt.Rows)
{
T entity = Activator.CreateInstance<T>();
foreach (var propertyName in typeProperties)
{
if (row[propertyName] != DBNull.Value)
{
string str = row[propertyName].GetType().FullName;
if (entity.GetType().GetProperty(propertyName).PropertyType == typeof(System.String))
{
object Val = row[propertyName].ToString();
entity.GetType().GetProperty(propertyName, BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public).SetValue(entity, Val, BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public, null, null, null);
}
else if (entity.GetType().GetProperty(propertyName).PropertyType == typeof(System.Guid))
{
object Val = Guid.Parse(row[propertyName].ToString());
entity.GetType().GetProperty(propertyName, BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public).SetValue(entity, Val, BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public, null, null, null);
}
else
{
entity.GetType().GetProperty(propertyName, BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public).SetValue(entity, row[propertyName], BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public, null, null, null);
}
}
else
{
entity.GetType().GetProperty(propertyName, BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public).SetValue(entity, null, BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public, null, null, null);
}
}
returnValue.Add(entity);
}
return returnValue.AsEnumerable();
}
Universal extension method for DataTable. May be somebody be interesting. Idea creating dynamic properties I take from another post: https://stackoverflow.com/a/15819760/8105226
public static IEnumerable<dynamic> AsEnumerable(this DataTable dt)
{
List<dynamic> result = new List<dynamic>();
Dictionary<string, object> d;
foreach (DataRow dr in dt.Rows)
{
d = new Dictionary<string, object>();
foreach (DataColumn dc in dt.Columns)
d.Add(dc.ColumnName, dr[dc]);
result.Add(GetDynamicObject(d));
}
return result.AsEnumerable<dynamic>();
}
public static dynamic GetDynamicObject(Dictionary<string, object> properties)
{
return new MyDynObject(properties);
}
public sealed class MyDynObject : DynamicObject
{
private readonly Dictionary<string, object> _properties;
public MyDynObject(Dictionary<string, object> properties)
{
_properties = properties;
}
public override IEnumerable<string> GetDynamicMemberNames()
{
return _properties.Keys;
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
if (_properties.ContainsKey(binder.Name))
{
result = _properties[binder.Name];
return true;
}
else
{
result = null;
return false;
}
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
if (_properties.ContainsKey(binder.Name))
{
_properties[binder.Name] = value;
return true;
}
else
{
return false;
}
}
}
I wrote an article on this subject over here.
I think it could help you.
Typically it's doing something like that:
static void Main(string[] args)
{
// Convert from a DataTable source to an IEnumerable.
var usersSourceDataTable = CreateMockUserDataTable();
var usersConvertedList = usersSourceDataTable.ToEnumerable<User>();
// Convert from an IEnumerable source to a DataTable.
var usersSourceList = CreateMockUserList();
var usersConvertedDataTable = usersSourceList.ToDataTable<User>();
}
PagedDataSource objPage = new PagedDataSource();
DataView dataView = listData.DefaultView;
objPage.AllowPaging = true;
objPage.DataSource = dataView;
objPage.PageSize = PageSize;
TotalPages = objPage.PageCount;
objPage.CurrentPageIndex = CurrentPage - 1;
//Convert PagedDataSource to DataTable
System.Collections.IEnumerator pagedData = objPage.GetEnumerator();
DataTable filteredData = new DataTable();
bool flagToCopyDTStruct = false;
while (pagedData.MoveNext())
{
DataRowView rowView = (DataRowView)pagedData.Current;
if (!flagToCopyDTStruct)
{
filteredData = rowView.Row.Table.Clone();
flagToCopyDTStruct = true;
}
filteredData.LoadDataRow(rowView.Row.ItemArray, true);
}
//Here is your filtered DataTable
return filterData;

Categories

Resources