Creating Business Class for a DataRow with unknown number of columns - c#

I have a stored procedure which returns pivoted columns along with columns from tables. I get the DataTable after executing the stored procedure. Now i want to convert this DataTable to a List<'MyClass'>. The DataTable has some known columns ( coming from tables ) and some unknown number of columns as a result of pivot.
How do i create a Class which really represents one DataRow. The idea i have is following:
public class TableColumns
{
public int TableColumn1 { get;set; }
public string TableColumn2 { get;set; }
public float TableColumn1 { get;set; }
//additional columns if any
}
public class PivotColumns
{
public string ColumnName { get;set; }
public string Value { get;set; }
//additional columns if any
}
public class MyClass
{
public TableColumns tableColumns { get;set; }
public List<PivotColumns> pivotedColumns { get;set; }
//overload the [] operator with real implementation
public string this[string pivotedColumnName] { get;set; }
}
and then a helper class to do the conversion:
public static class ConversionHelper
{
public static MyClass ConvertDataRowToMyClass(DataRow dataRow)
{
// some implementation
}
public static DataRow ConvertMyClassToDataRow(MyClass myClass)
{
// some implementation
}
}
How good is the approach i mentioned above? Please share ideas / alternates
Thanks

I would have done the below for myself.
public class TableColumns
{
public int TableColumn1 { get;set; }
public string TableColumn2 { get;set; }
public float TableColumn3 { get;set; }
//additional columns if any
}
public class PivotColumns
{
public string PivotColumn1 { get;set; }
public int PivotColumn2 { get;set; }
public float PivotColumn3 { get;set; }
//additional columns if any
}
public class MyClass : TableColumns, PivotColumns{ }
public static class ConversionHelper
{
public static List<MyClass> ConvertDataRowToMyClass(DataTable dt)
{
// some implementation
List<MyClass> ltMyClass = (from dr in dataTable.AsEnumerable()
select new MyClass
{
TableColumn1 = dr["TableColumn1"] == DBNull.Value || dr["TableColumn1"] == null ? default(int) : dr.Field<int>("TableColumn1"),
PivotColumn2 = dr.Field<int>("PivotColumn2"),
TableColumn2 = dr.Field<string>("TableColumn2")
}).ToList<MyClass>();
}
public static DataTable ConvertMyClassToDataRow(List<MyClass> lstMyClass)
{
// some implementation
PropertyDescriptorCollection properties =
TypeDescriptor.GetProperties(typeof(MyClass));
DataTable table = new DataTable();
foreach (PropertyDescriptor prop in properties)
table.Columns.Add(prop.Name, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType);
foreach (T item in data)
{
DataRow row = table.NewRow();
foreach (PropertyDescriptor prop in properties)
row[prop.Name] = prop.GetValue(item) ?? DBNull.Value;
table.Rows.Add(row);
}
return table;
}
}
I have copied the list to datatable conversion logic from here.

Related

Can I access a static member of abstract class in a generic?

I'm trying to create a generic data exporter, where I can feed a bunch of rows and this can then get exported as excel, csv, etc...
To make it quick to specify I was planning on having the "headers" of the report specified on the same file as the rows, using static fields.
But this mess of having abstract classes with static members and the attempt of ussing them in a generic class is messy.
Is there a clean way to do this?
my attempt was having an abstract class with the base "interface" and a common method
public abstract class ReportRow
{
public static readonly string[] ColumnNames;
public static int ColumnCount => ColumnNames.Length;
public string GetColumnValueAsString(int index)
{
var value = GetColumnValue(index);
if (value is DateTime)
{
return value.ToString("dd/MM/yyyy");
}
return value.ToString();
}
}
then I have a couple of class implementations, I leave this one as an example:
public class OrdersReportRow : ReportRow
{
public new static readonly string[] ColumnNames =
{
"Date", "StoreId", "StoreName", "SkuId", "Quantity"
};
public DateTime Date { get; set; }
public int StoreId { get; set; }
public string StoreName { get; set; }
public int SkuId { get; set; }
public decimal Quantity { get; set; }
public new dynamic GetColumnValue(int index)
{
return index switch
{
0 => Date,
1 => StoreId,
2 => StoreName,
3 => SkuId,
4 => Quantity,
_ => throw new IndexOutOfRangeException(),
};
}
}
And the exporter class I wanted to be something like this:
public class ReportExporter<TReportRowType> where TReportRowType : ReportRow
{
public void Export(IEnumerable<TReportRowType> reportEntries)
{
//code to add headers
for (int i = 0; i < TReportRowType.ColumnCount; i++) // fails here because ColumnCount is not "visible"
{
AddColumn(TReportRowType.ColumnNames[i]); // also fails
}
foreach(var entry in reportEntries) {
for (int i = 0; i < TReportRowType.ColumnCount; i++) // fails here because ColumnCount is not "visible"
{
AddValue(entry.GetColumnValue(i));
CommitLine()
}
}
}
}
Those column names should be on a collection or rows level in my opinion. So that I would try something like this.
Note that the column names are automatically populated using reflection.
public abstract class ReportRow
{
public abstract dynamic GetColumnValue(int index);
public string GetColumnValueAsString(int index)
{
var value = GetColumnValue(index);
if (value is DateTime)
{
return value.ToString("dd/MM/yyyy");
}
return value.ToString();
}
}
public abstract class ReportRows<T> : IEnumerable<T> where T : ReportRow
{
public string[] ColumnNames { get => _cols.ToArray(); }
public int ColumnCount { get => _cols.Count; }
List<string> _cols;
List<T> _rows;
public ReportRows() {
_rows = new List<T>();
_cols = new List<string>();
var tt = typeof(T);
foreach(var p in tt.GetProperties()) {
_cols.Add(p.Name);
}
}
public void Add(T row) => _rows.Add(row);
public IEnumerator<T> GetEnumerator() => _rows.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
public class OrdersReportRow : ReportRow
{
public DateTime Date { get; set; }
public int StoreId { get; set; }
public string StoreName { get; set; }
public int SkuId { get; set; }
public decimal Quantity { get; set; }
public override dynamic GetColumnValue(int index)
{
return index switch
{
0 => Date,
1 => StoreId,
2 => StoreName,
3 => SkuId,
4 => Quantity,
_ => throw new IndexOutOfRangeException(),
};
}
}
public class OrdersReportRows : ReportRows<OrdersReportRow> { }
public class ReportExporter
{
public void Export<T>(ReportRows<T> reportEntries) where T : ReportRow
{
//code to add headers
for (int i = 0; i < reportEntries.ColumnCount; i++) // fails here because ColumnCount is not "visible"
{
AddColumn(reportEntries.ColumnNames[i]); // also fails
}
foreach(var entry in reportEntries) {
for (int i = 0; i < reportEntries.ColumnCount; i++) // fails here because ColumnCount is not "visible"
{
AddValue(entry.GetColumnValue(i));
CommitLine();
}
}
}
public void AddColumn(string s) {}
public void AddValue(object s) {}
public void CommitLine() {}
}
to call:
var or = new OrdersReportRows();
or.Add(new OrdersReportRow { Date = DateTime.Now });
var re = new ReportExporter();
re.Export(or);
I'd avoid using "new" for a method/property/field signature, I'd avoid the static public members (in combination with new). You can still use a static private field for implementation, but it's cleaner and easier to make the accessors for that field, i.e. the column names array and column count, non-static and abstract. At least "ColumnNames" should be abstract.
Sth like (untested):
public abstract class ReportRow
{
public abstract string[] ColumnNames { get; }
public int ColumnCount => ColumnNames.Length;
public string GetColumnValueAsString(int index)
{
//as is
}
}
public class OrdersReportRow : ReportRow
{
private static readonly string[] _columnNames =
{
"Date", "StoreId", "StoreName", "SkuId", "Quantity"
};
public override string[] ColumnNames => _columnNames;
//no other changes ...
}
public class ReportExporter<TReportRowType> where TReportRowType : ReportRow
{
public void Export(IEnumerable<TReportRowType> reportEntries)
{
var firstEntry = reportEntries.FirstOrDefault();
if (firstEntry == null) return;
for (int i = 0; i < firstEntry.ColumnCount; i++) // shouldn't fail any longer, because ColumnCount is now "visible"
{
AddColumn(firstEntry.ColumnNames[i]); // also should work
}
foreach(var entry in reportEntries) {
for (int i = 0; i < entry.ColumnCount; i++) // shouldn't fail any longer, because ColumnCount is now "visible"
{
AddValue(entry.GetColumnValue(i));
CommitLine()
}
}
}

c# Loop List<T> row and trim column values

public class Person {
string Name
string Address
int Age
.. 100+ more columns
}
var result = new List<Person>();
foreach (var item in result )
{
//loop column and trim the values.
}
I want the simplest way to loop the columns (assuming 100+ columns) where datatype is string then trim the value.
To rephrase in more C# terms: I want to update all properties and fields of an object that are of type string with trimmed value as item.StringProp = item.StringProp.Trim(). I don't want to manually write update for each property.
You could use reflection and Linq for filtering the properties of type string. From the OP, it looks like you are using Fields instead of properties. Please note it is unclear whether the Properties/Fields are public from OP, if you need to use public fields/properties, please use BindingFlags.Public
public List<T> TrimList<T>(List<T> source)
{
foreach(var property in typeof(T).GetFields(BindingFlags.NonPublic | BindingFlags.Instance).Where(x=>x.FieldType== typeof(string)))
{
foreach(var personItem in source)
property.SetValue(personItem,Convert.ToString(property.GetValue(personItem)).Trim());
}
return source;
}
If properties, you could use
public List<T> TrimList<T>(List<T> source)
{
foreach(var property in typeof(T).GetProperties(BindingFlags.NonPublic | BindingFlags.Instance).Where(x=>x.PropertyType== typeof(string)))
{
foreach(var personItem in source)
property.SetValue(personItem,Convert.ToString(property.GetValue(personItem)).Trim());
}
return source;
}
Demo Code
Note: prior to .NET 4.5 you need to pass null as a second argument:
public List<T> TrimList<T>(List<T> source)
{
foreach(var property in typeof(T).GetProperties(BindingFlags.NonPublic | BindingFlags.Instance).Where(x=>x.PropertyType== typeof(string)))
{
foreach(var personItem in source)
property.SetValue(personItem,Convert.ToString(property.GetValue(personItem,null)).Trim());
}
return source;
}
As well as reflection, another way is to make it the responsibility of the Person class.
public class Person {
public string Name { get; set; }
public string Address { get; set; }
public int Age { get; set; }
.. 100+ more columns
public void DoTrim()
{
this.Name = this.Name.Trim();
this.Address = this.Address.Trim();
... still need to code 100+ properties
}
}
The advantage is that you can call it like this
var result = new List<Person>();
...
for(int i=0; i < result.Count(); i++)
{
result[i].DoTrim();
}
Or you can control your data in the Person class when you set it and use local private variables.
public class Person {
private string name;
public string Name
{
get { return name; }
set { name = value.Trim(); }
}
private string address;
public string Address
{
get { return address; }
set { address= value.Trim(); }
}
....
This is how I would implement it:
class Program
{
static void Main(string[] args)
{
var obj = new Person
{
MyProperty = " A",
MyProperty1 = " A ",
MyProperty2 = "A ",
MyProperty3 = "A A A",
};
TrimStrings(obj);
}
public static void TrimStrings(object obj)
{
Type stringType = typeof(string);
var properties = obj.GetType().GetProperties().Where(x => x.PropertyType == stringType);
foreach(var property in properties)
{
string value = (string)property.GetValue(obj);
property.SetValue(obj, value?.Trim());
}
}
}
public class Person
{
public string MyProperty { get; set; }
public string MyProperty1 { get; set; }
public string MyProperty2 { get; set; }
public string MyProperty3 { get; set; }
}
Output:
{"MyProperty":"A","MyProperty1":"A","MyProperty2":"A","MyProperty3":"A
A A"}
You can use This Nuget Package
.After Install use it as bellow:
result.ForEach(x => x.AdjustString());

How to access a class member through a variable

I have a class that I've created
public class DataRecord
{
public string PayerAccount { get; set; }
public string GlobalEntityType { get; set; }
public string GlobalEntityNumber { get; set; }
}
I am now trying to access this DataRecord in a different method through the use of a variable
public List<DataTest> CountAndFrequency(IEnumerable records, string ColumnName, int numResults)
{
foreach (DataRecord record in records)
{
record.ColumnName = record.ColumnName.ToUpper();
}
}
I am getting the error that DataRecord does not contain a definition for ColumnName, which of course makes sense. The question is, how do I combat this issue? I've been scouring the internet to no avail and would appreciate any help.
Thanks in advance!
You can use reflection for this. Try this
public static List<DataTest> CountAndFrequency(IEnumerable<DataRecord> records, string ColumnName, int numResults)
{
foreach (DataRecord record in records)
{
var prop = typeof(DataRecord).GetProperty(ColumnName);
var value = prop.GetValue(record).ToString().ToUpper();
prop.SetValue(record, value);
}
}
If you want to access data via a string name, you store the data in a Dictionary<string,string>.
public class DataRecord
{
private readonly Dictionary<string, string> data;
public DataRecord()
{
this.data = new Dictionary<string, string>();
}
// Access data with names
public string this[string columnName]
{
get{ return data[columnName]; }
set{ data[columnName] = value;}
}
// Fake properties
public string PayerAccount
{
get => data[nameof(PayerAccount)];
set => data[nameof(PayerAccount)] = value;
}
public string GlobalEntityType
{
get => data[nameof(GlobalEntityType)];
set => data[nameof(GlobalEntityType)] = value;
}
public string GlobalEntityNumber
{
get => data[nameof(GlobalEntityNumber)];
set => data[nameof(GlobalEntityNumber)] = value;
}
}
class Program
{
static void Main(string[] args)
{
var record = new DataRecord
{
PayerAccount = "10024",
GlobalEntityType = "QXT",
GlobalEntityNumber = "509382"
};
var number = record["GlobalEntityNumber"];
// 509382
}
}

How to apply Linq to Observable Collection of Generic Property?

How to apply the linq concept to Generic property Collection. I have converted the Data Table to Collection as like Converter
I have the DataTable and then convert it to Collection as like
class Program
{
static void Main(string[] args)
{
DataTable dtResult = new DataTable();
dtResult.Columns.Add("ID", typeof(int));
dtResult.Columns.Add("Name", typeof(string));
DataRow dtRow = dtResult.NewRow();
dtRow["ID"] = 1;
dtRow["Name"] = "Bala";
dtResult.Rows.Add(dtRow);
dtRow = dtResult.NewRow();
dtRow["ID"] = 2;
dtRow["Name"] = "Bala1";
dtResult.Rows.Add(dtRow);
var Collection = Convert(dtResult);
// var property = Collection.Where(a=>a.Properties.Where(x => (x as GenericProperty).Name.Equals("ID") && (x as GenericProperty).Value.Equals(1));
// I would like to get the ID matching 2 Record How to get this using linq query
}
private static ObservableCollection<GenericObject> Convert(DataTable toConvert)
{
ObservableCollection<GenericObject> _result = new ObservableCollection<GenericObject>();
foreach (DataRow _row in toConvert.Rows)
{
GenericObject _genericObject = new GenericObject();
foreach (DataColumn _column in toConvert.Columns)
{
_genericObject.Properties.Add(new GenericProperty(_column.ColumnName, _row[_column]));
}
_result.Add(_genericObject);
}
return _result;
}
}
public class GenericObject
{
private readonly ObservableCollection<GenericProperty> properties = new ObservableCollection<GenericProperty>();
public GenericObject(params GenericProperty[] properties)
{
foreach (var property in properties)
Properties.Add(property);
}
public ObservableCollection<GenericProperty> Properties
{
get { return properties; }
}
}
public class GenericProperty : INotifyPropertyChanged
{
public GenericProperty(string name, object value)
{
Name = name;
Value = value;
}
public string Name { get; private set; }
public object Value { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
}
Now my question is to how can i apply the linq concept to get the MAtched Record??
Is this what you want?
var property =
from a in Collection
where a.Properties.Any(p => p.Name == "ID" && (int)p.Value == 1)
select a;
That gives:

Convert DataTable to List of strongly typed objects

I am trying to write a generic method that will convert a DataTable to a list of strongly typed objects.
The code that I'm working with so far is...
public List<T> ImportTable<T>(String fileName, String table)
{
//Establish Connection to Access Database File
var mdbData = new ConnectToAccess(#"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=F:\ACCESS\" + fileName + ".mdb;");
var tableData = new List<T>();
foreach (DataRow row in mdbData.GetData("SELECT * FROM " + table).Rows)
{
tableData.Add(ConvertRowToType<T>(row));
}
return tableData;
}
public T ConvertRowToType<T>(DataRow row)
{
//??? What is the best thing to do here ???
}
I'm not fixated on this code if anybody's suggestions would require changes to it.
So let's say I call this function passing in the type...
public class mdbConcern
{
public Int32 ConcernId { get; set; }
public String Concern { get; set; }
}
And the Data coming back in the DataTable looks like...
ConcernID Concern
1 Law and Ethics
2 Mail
3 Business English
... ...
What would be the best way to implement the ConvertRowToType(DataRow row) method?
Can someone show me how to use Func as one of the parameters so I can pass in some mapping information?
I think an extension method is the best way to go:
public static class Helper
{
public static T ToType<T>(this DataRow row) where T : new()
{
T obj = new T();
var props = TypeDescriptor.GetProperties(obj);
foreach (PropertyDescriptor prop in props)
{
if(row.Table.Columns.IndexOf(prop.Name) >= 0
&& row[prop.Name].GetType() == prop.PropertyType)
{
prop.SetValue(obj, row[prop.Name]);
}
}
return obj;
}
}
Usage:
public List<T> ImportTable<T>(String fileName, String table)
{
//Establish Connection to Access Database File
var mdbData = new ConnectToAccess(#"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=F:\ACCESS\" + fileName + ".mdb;");
var tableData = new List<T>();
foreach (DataRow row in mdbData.GetData("SELECT * FROM " + table).Rows)
{
tableData.Add(row.ToType<T>());
}
return tableData;
}
Update I see that you asked for a Func that would provide the mapping. I'm not sure exactly what you envisioned but here is a method I came up with:
public class mdbConcern
{
public Int32 ConcernId { get; set; }
public String Concern { get; set; }
public static PropertyDescriptor Mapping(string name)
{
PropertyDescriptorCollection props = TypeDescriptor.GetProperties(typeof(mdbConcern));
switch (name)
{
case "Concern_Id":
return props.GetByName("ConcernId");
case "Concern":
return props.GetByName("Concern");
default:
return null;
}
}
}
public static class Helper
{
public static T ToType<T>(this DataRow row, Func<string, PropertyDescriptor> mapping)
where T : new()
{
T obj = new T();
foreach (DataColumn col in row.Table.Columns)
{
var prop = mapping(col.ColumnName);
if(prop != null)
prop.SetValue(obj, row[col]);
}
return obj;
}
}
Usage:
foreach (DataRow row in mdbData.GetData("SELECT * FROM " + table).Rows)
{
tableData.Add(row.ToType<mdbConcern>(mdbConcern.Mapping));
}
Here's a version using attributes on the type's properties to store its mapping. I think it's a more natural solution:
[AttributeUsage(AttributeTargets.Property)]
public class ColumnMappingAttribute : Attribute
{
public string Name { get; set; }
public ColumnMappingAttribute(string name)
{
Name = name;
}
}
public class mdbConcern
{
ColumnMapping("Concern_Id")]
public Int32 ConcernId { get; set; }
ColumnMapping("Concern")]
public String Concern { get; set; }
}
public static class Helper
{
public static T ToType<T>(this DataRow row) where T : new()
{
T obj = new T();
var props = TypeDescriptor.GetProperties(obj);
foreach (PropertyDescriptor prop in props)
{
var columnMapping = prop.Attributes.OfType<ColumnMappingAttribute>().FirstOrDefault();
if(columnMapping != null)
{
if(row.Table.Columns.IndexOf(columnMapping.Name) >= 0
&& row[columnMapping.Name].GetType() == prop.PropertyType)
{
prop.SetValue(obj, row[columnMapping.Name]);
}
}
}
return obj;
}
}
Addition to #Sorax answer. I enhanced ToType method to support Nullable<> type members (using fields instead of properties and TypeInfo instead of TypeDescriptor). It takes whole DataTable object as a paramater and returns IList.
protected IList<TResult> TableToList<TResult>(DataTable table) where TResult : new()
{
var result = new List<TResult>(table.Rows.Count);
var fields = typeof(TResult).GetTypeInfo().DeclaredFields;
TResult obj;
Object colVal;
var columns = table.Columns;
var nullableTypeDefinition = typeof(Nullable<>);
var dbNullType = typeof(DBNull);
Type[] genericArguments;
foreach (DataRow row in table.Rows)
{
obj = new TResult();
foreach (var f in fields)
{
if (columns.Contains(f.Name))
{
colVal = row[f.Name];
if (colVal.GetType() == f.FieldType)
{
f.SetValue(obj, colVal);
}
else if (colVal.GetType() != dbNullType && f.FieldType.IsGenericType &&
f.FieldType.GetGenericTypeDefinition() == nullableTypeDefinition)
{
genericArguments = f.FieldType.GetGenericArguments();
if (genericArguments.Length > 0 && genericArguments[0] == colVal.GetType())
{
f.SetValue(obj, colVal);
}
}
}
}
result.Add(obj);
}
return result;
}

Categories

Resources