inherited class properties order - c#

I have 2 classes, where one of them inherits from the other:
public class First
{
public string a{get;set;}
public string b{get;set;}
}
public class Second: First
{
public string c {get;set;}
}
Then i create a List<Second> and fill with some information, and then i export this list to an Excel using EPPlus.
But the "problem" is that the properties of the inherited class(Second) appears first and those of the base class(First) after in the excel:
c | a | b
-------------------
The question is whether i can put the properties of the base class first, or can i say to EPPlus the order of the properties?
EDIT:
I put the code to create the Excel, is very simple
:
using (ExcelPackage package = new ExcelPackage(archivo))
{
ExcelWorksheet ws= package.Workbook.Worksheets.Add("Solds");
{
ws.Cells["A3"].LoadFromCollection(lstSolds);
package.Save();
}
}

You can use attributes and reflexion to select the order of the properties. It also allow to select which properties you want to output or not
public class First {
[Order(1)]
public string a { get; set; }
[Order(2)]
public string b { get; set; }
}
public class Second : First {
[Order(3)]
public string c { get; set; }
}
[AttributeUsage(AttributeTargets.Property)]
public class OrderAttribute : Attribute {
public int Order {get; set; }
public OrderAttribute(int order) {
Order = order;
}
}
class Program {
static void Main(string[] args) {
List<Second> list = new List<Second>();
list.Add(new Second {
a = "a",
b = "b",
c = "c"
});
WriteList(list);
}
static void WriteList(List<Second> list) {
PropertyInfo[] properties = typeof(Second).GetProperties();
int row = 3;
int col = 0;
foreach (var item in list) {
Dictionary<int, object> values = new Dictionary<int, object>();
foreach (var pi in properties) {
var orderAttr = pi.GetCustomAttribute(typeof(OrderAttribute)) as OrderAttribute;
if (orderAttr != null) { //this allow to output selective propertes. Those properties without [order] attriburte will not output
values.Add(orderAttr.Order, pi.GetValue(item, null));
}
}
foreach (var key in values.Keys.OrderBy(x => x)) {
ws.Cells[row, col + key].Value = values[key];
}
row++;
}
}
Edited as #Ion comments, added function to read from Excel
static List<Second> ReadFromExcel(string filePath) {
List<Second> result = new List<Second>();
PropertyInfo[] props = typeof(Second).GetProperties();
//Allow access propertyInfo by name
Dictionary<string, PropertyInfo> properties = new Dictionary<string, PropertyInfo>();
foreach (var pi in props) {
properties.Add(pi.Name, pi);
}
using (var xls = new ExcelPackage(new FileInfo(filePath))) {
ExcelWorksheet ws = xls.Workbook.Worksheets[0];
//Let's assume you have property names has row heading in Excel
List<string> names = new List<string>(3);
for (int i = 1; i <= 4; i++) {
names.Add(ws.Cells[1, i].Value.ToString());
}
//Fill the list from Excel
for (int row = 3; row <= ws.Dimension.End.Row; row++) {
Second second = new Second();
for (int col = 1; col <= 4; col++) {
object value = ws.Cells[row, col].Value;
if (value != null)
properties[names[col]].SetValue(second, value);
}
result.Add(second);
}
}
return result;
}

Another way, more "Epplus" way I guess you could say, would be something like this:
var lstSolds = new List<First>();
for (var i = 0; i < 10; i++)
{
lstSolds.Add(new Second
{
a = "A-" + i,
b = "B-" + i,
c = "C-" + i
});
}
using (ExcelPackage package = new ExcelPackage(archivo))
{
var mi = typeof (Second)
.GetProperties()
.OrderBy(pi => pi.Name) //This controls the column order
.Cast<MemberInfo>()
.ToArray();
ws.Cells["A3"]
.LoadFromCollection(
lstSolds
, true
, TableStyles.Custom
, BindingFlags.Default
, mi
);
package.Save();
}
You can control the order of the columns by adjusting the mi collection order. In this case it is ordered ascending but any order set will be respected by LoadFromCollection.
Response to Comments
Its easy to have them sort in any order you need. Just need to build the array before passing in a way that guarantees sort order. Say we add a property d to the First class. We can make sure it sorts before c like this:
public class First
{
public string a { get; set; }
public string b { get; set; }
public string d { get; set; } //Say we need this to sort before 'c'
}
public class Second : First
{
public string c { get; set; }
}
...
var lstSolds = new List<First>();
for (var i = 0; i < 10; i++)
{
lstSolds.Add(new Second
{
a = "A-" + i,
b = "B-" + i,
c = "C-" + i,
d = "D-" + i,
});
}
using (var package = new ExcelPackage(file))
{
var ws = package.Workbook.Worksheets.Add("table");
//var mi = typeof(Second)
// .GetProperties()
// .OrderBy(pi => pi.Name) //This controls the column order
// .Cast<MemberInfo>()
// .ToArray();
var firstmi = typeof (First)
.GetProperties()
.OrderBy(pi => pi.Name);
var secondmi = typeof (Second)
.GetProperties()
.Where(pi => !firstmi.Select(fpi => fpi.Name).Contains(pi.Name))
.OrderBy(pi => pi.Name);
//Sorting above will keep first proper before second
var mi = firstmi
.Concat(secondmi)
.Cast<MemberInfo>()
.ToArray();
ws.Cells["A3"]
.LoadFromCollection(
lstSolds
, true
, TableStyles.Custom
, BindingFlags.Public | BindingFlags.Instance
, mi
);
package.Save();
}

Related

CsvHelper - Populating datatable slow(ish)

Have put together the code below to read a particular set of CSV files. It works but is very much a work in progress. There is one section of the code (populating datatable row - see snip below) that is taking as long to run as the SqlBulkCopy operation. Asking for advice/recommendations on how to improve performance.
In the code (below) processing a ~15M row file in 50K batches took just under 11.5 minutes. Breaking down the sections. SqlBulkCopy took ~236Kms (4 min) the reader only needed 105Kms (~1.5min), and the section populating the datatable took ~200Kms (3.33 min).
csvTableTimer.Start();
// Process row and populate datatable
DataRow dr = dt.NewRow();
foreach (DataColumn dc in dt.Columns)
{
if (row.GetType().GetProperty(dc.ToString()).GetValue(row) != null)
{
dr[dc.ToString()] = row.GetType().GetProperty(dc.ToString()).GetValue(row);
}
}
dt.Rows.Add(dr);
csvTableTimer.Stop();
The CSV files are very large (10+GB) and do not have headers. I'm using the Class to build the datatable structure and like to continue with that approach when populating the datatable rows as I'll need to expand this to work with multiple CSV types.
The datatable reflects the column names from the class which line up with the SQL DB table. Had wanted to use GetField (converted, not raw) walking each column in the datatable row[column.ColumnName] = csv.GetField( column.DataType, column.ColumnName ); but kept getting an error about there not being headers. Found an open issue relating to HasHeaderRecord = false that matches up with what I was trying to do so that added to my desire to seek advice from those who are more skilled at this. Appreciate the help!
Expanding on the code block;
var rconfig = new CsvHelper.Configuration.CsvConfiguration(CultureInfo.InvariantCulture)
{
BufferSize = 1024,
Delimiter = ",",
AllowComments = true,
HasHeaderRecord = false,
HeaderValidated = null,
IgnoreBlankLines = true,
MissingFieldFound = null,
Comment = '#',
Escape = '"',
TrimOptions = TrimOptions.Trim,
BadDataFound = x =>
{
isBadRecord = true;
ErrRecords.Add(x.RawRecord);
++badCount;
}
};
var loadFType = #"B";
// Create datatable using class as definition.
PropertyDescriptorCollection props1 = TypeDescriptor.GetProperties(loaderFileType);
DataTable dt = new DataTable();
dt = UtilExtensions.CreateDataTable(props1);
using (var reader = new StreamReader(rFile))
{
reader.ReadLine();
using (var csv = new CsvReader(reader, rconfig))
{
switch (loadFType)
{
case "ALL":
csv.Context.RegisterClassMap<CSVLoader.AMap>();
var allRecords = new List<CSVLoader.A>();
break;
case "BAL":
csv.Context.RegisterClassMap<CSVLoader.BMap>();
var balRecords = new List<CSVLoader.B>();
break;
case "CIF":
csv.Context.RegisterClassMap<CSVLoader.CMap>();
var cifRecords = new List<CSVLoader.C>();
break;
}
dt.BeginLoadData();
while (csv.Read())
{
csvReadTimer.Start();
var row = csv.GetRecord(loaderFileType);
csvReadTimer.Stop();
runningCount++;
if (!isBadRecord)
{
csvTableTimer.Start();
// Process row and populate datatable
DataRow dr = dt.NewRow();
foreach (DataColumn dc in dt.Columns)
{
if (row.GetType().GetProperty(dc.ToString()).GetValue(row) != null)
{
dr[dc.ToString()] = row.GetType().GetProperty(dc.ToString()).GetValue(row);
}
}
dt.Rows.Add(dr);
csvTableTimer.Stop();
++goodCount;
if (batchCount >= dtbatchSize || runningCount >= fileRecCount)
{
try
{
// Write from the source to the destination.
bcpLoadTimer.Start();
bulkCopy.WriteToServer(dt);
bcpLoadTimer.Stop();
bcpLoadBatchCount++;
}
catch (Exception ex)
{
}
dt.Clear();
batchCount = 0;
}
batchCount++;
}
isBadRecord = false;
}
dt.EndLoadData();
reader.Close();
dt.Clear();
transaction.Commit();
// B
public class B
{
[Index(0)]
public string A { get; set; }
[Index(1)]
public string BString { get; set; }
[Index(2)]
public int? C { get; set; }
[Index(3)]
public string D { get; set; }
[Index(4)]
public string E { get; set; }
[Index(5)]
public DateTime? F { get; set; }
[Index(6)]
public decimal? G { get; set; }
[Index(7)]
public decimal? H { get; set; }
[Index(8)]
public decimal? I { get; set; }
[Index(9)]
public decimal? J { get; set; }
[Index(10)]
public int? K { get; set; }
[Index(11)]
public string L { get; set; }
[Index(12)]
public DateTime? M { get; set; }
}
// B
public sealed class BMap : ClassMap<B>
{
public BMap()
{
// AutoMap(CultureInfo.InvariantCulture);
Map(m => m.A).Index(0);
Map(m => m.BString).Index(1);
Map(m => m.C).Index(2);
Map(m => m.D).Index(3);
Map(m => m.E).Index(4);
Map(m => m.F).Index(5).TypeConverterOption.Format("yyyyMMdd");
Map(m => m.G).Index(6);
Map(m => m.H).Index(7);
Map(m => m.I).Index(8);
Map(m => m.J).Index(9);
Map(m => m.K).Index(10);
Map(m => m.L).Index(11);
Map(m => m.M).Index(12).TypeConverterOption.Format("yyyy-MM-dd-hh.mm.ss.ffffff");
}
}
Your question doesn't really include a minimal reproducible example, so I simplified your code to create the following FileLoader class that times how long it takes to populate the DataTable from instances of some class TClass (here B) that had been read from a CSV row using CsvReader:
public class FileLoader
{
public System.Diagnostics.Stopwatch csvTableTimer { get; } = new();
public long Load<TClass, TClassMap>(string rFile, int dtbatchSize) where TClassMap : ClassMap<TClass>, new()
{
bool isBadRecord = false;
long badCount = 0;
long runningCount = 0;
long goodCount = 0;
long batchCount = 0;
var rconfig = CreateCsvConfiguration(
x =>
{
isBadRecord = true;
//ErrRecords.Add(x.RawRecord);
++badCount;
});
// Create datatable using class as definition.
var dt = UtilExtensions.CreateDataTable(typeof(TClass));
using (var reader = new StreamReader(rFile))
{
//reader.ReadLine(); FIXED - THIS SKIPPED THE FIRST LINE AND CAUSED A RECORD TO BE OMITTED.
using (var csv = new CsvReader(reader, rconfig))
{
csv.Context.RegisterClassMap<TClassMap>();
dt.BeginLoadData();
while (csv.Read())
{
isBadRecord = false;
//csvReadTimer.Start();
var record = csv.GetRecord<TClass>();
//csvReadTimer.Stop();
runningCount++;
if (!isBadRecord)
{
csvTableTimer.Start();
// Process row and populate datatable
DataRow dr = dt.NewRow();
foreach (DataColumn dc in dt.Columns)
{
if (record.GetType().GetProperty(dc.ToString()).GetValue(record) != null)
{
dr[dc.ToString()] = record.GetType().GetProperty(dc.ToString()).GetValue(record);
}
}
dt.Rows.Add(dr);
csvTableTimer.Stop();
goodCount++;
if (++batchCount >= dtbatchSize)
{
// Flush the data table
FlushTable(dt);
batchCount = 0;
}
}
}
dt.EndLoadData();
FlushTable(dt);
Commit();
}
}
return goodCount;
}
protected virtual void FlushTable(DataTable dt) => dt.Clear(); // Replace with SqlBulkCopy
protected virtual void Commit() {} // Replace with transaction.Commit();
public static CsvConfiguration CreateCsvConfiguration(BadDataFound badDataFound) =>
new CsvHelper.Configuration.CsvConfiguration(CultureInfo.InvariantCulture)
{
BufferSize = 1024,
Delimiter = ",",
AllowComments = true,
HasHeaderRecord = false,
HeaderValidated = null,
IgnoreBlankLines = true,
MissingFieldFound = null,
Comment = '#',
Escape = '"',
TrimOptions = TrimOptions.Trim,
BadDataFound = badDataFound,
};
}
public static partial class UtilExtensions
{
static IEnumerable<PropertyInfo> GetSerializableProperties(this Type type) =>
type.GetProperties().Where(p => p.GetIndexParameters().Length == 0 && p.CanRead && p.CanWrite && p.GetGetMethod() != null && p.GetSetMethod() != null);
public static DataTable CreateDataTable(Type type)
{
var dt = new DataTable();
foreach (var p in type.GetSerializableProperties())
dt.Columns.Add(p.Name, Nullable.GetUnderlyingType(p.PropertyType) ?? p.PropertyType);
return dt;
}
}
Then, if I use the file loader and call loader.Load<B, BMap>(rFile, 1000) to read a CSV file with 5555 rows 20 times, it takes roughly 1049 ms on dotnetfiddle. See demo #1 here.
One problem you are encountering is that reflection in c# can be very slow. You are calling record.GetType().GetProperty(dc.ToString()).GetValue(record) twice, and if I simply reduce the number of calls by 1, the time is reduced to around 706 ms:
foreach (DataColumn dc in dt.Columns)
{
var value = record.GetType().GetProperty(dc.ToString()).GetValue(record);
if (value != null)
{
dr[dc.ToString()] = value;
}
}
Demo #2 here.
However, we can do better by manufacturing a delegate in runtime. First, add the following utility methods that make use of the System.Linq.Expressions namespace:
public static partial class UtilExtensions
{
public static Func<TSource, object> CreatePropertyGetter<TSource>(PropertyInfo propertyInfo)
{
var parameter = Expression.Parameter(typeof(TSource), "obj");
var property = Expression.Property(parameter, propertyInfo);
var convert = Expression.Convert(property, typeof(object));
var lambda = Expression.Lambda(typeof(Func<TSource, object>), convert, parameter);
return (Func<TSource, object>)lambda.Compile();
}
public static ReadOnlyDictionary<string, Func<TSource, object>> PropertyGetters<TSource>() => PropertyExpressionsCache<TSource>.PropertyGetters;
static ReadOnlyDictionary<string, Func<TSource, object>> CreatePropertyGetters<TSource>() =>
typeof(TSource)
.GetSerializableProperties()
.ToDictionary(p => p.Name,
p => CreatePropertyGetter<TSource>(p))
.ToReadOnly();
static class PropertyExpressionsCache<TSource>
{
public static ReadOnlyDictionary<string, Func<TSource, object>> PropertyGetters { get; } = UtilExtensions.CreatePropertyGetters<TSource>();
}
public static ReadOnlyDictionary<TKey, TValue> ToReadOnly<TKey, TValue>(this IDictionary<TKey, TValue> dictionary) =>
new ReadOnlyDictionary<TKey, TValue>(dictionary ?? throw new ArgumentNullException());
}
And modify Load<TClass, TClassMap>() as follows:
public long Load<TClass, TClassMap>(string rFile, int dtbatchSize) where TClassMap : ClassMap<TClass>, new()
{
bool isBadRecord = false;
long badCount = 0;
long runningCount = 0;
long goodCount = 0;
long batchCount = 0;
var rconfig = CreateCsvConfiguration(
x =>
{
isBadRecord = true;
//ErrRecords.Add(x.RawRecord);
++badCount;
});
var loaderFileType = typeof(TClass);
// Create datatable using class as definition.
var dt = UtilExtensions.CreateDataTable(loaderFileType);
var properties = UtilExtensions.PropertyGetters<TClass>();
using (var reader = new StreamReader(rFile))
{
//reader.ReadLine(); FIXED - THIS SKIPPED THE FIRST LINE AND CAUSED A RECORD TO BE OMITTED.
using (var csv = new CsvReader(reader, rconfig))
{
csv.Context.RegisterClassMap<TClassMap>();
dt.BeginLoadData();
while (csv.Read())
{
isBadRecord = false;
//csvReadTimer.Start();
var record = csv.GetRecord<TClass>();
//csvReadTimer.Stop();
runningCount++;
if (!isBadRecord)
{
csvTableTimer.Start();
// Process row and populate datatable
DataRow dr = dt.NewRow();
foreach (var p in properties)
{
var value = p.Value(record);
if (value != null)
dr[p.Key] = value;
}
dt.Rows.Add(dr);
csvTableTimer.Stop();
goodCount++;
if (++batchCount >= dtbatchSize)
{
// Flush the data table
FlushTable(dt);
batchCount = 0;
}
}
}
dt.EndLoadData();
FlushTable(dt);
}
}
return goodCount;
}
The time will be further reduced, to roughly 404 ms. Demo fiddle #3 here.
I also tried using Delegate.CreateDelegate() instead of Expression:
public static partial class UtilExtensions
{
static Func<TSource, object> CreateTypedPropertyGetter<TSource, TValue>(PropertyInfo propertyInfo)
{
var typedFunc = (Func<TSource, TValue>)Delegate.CreateDelegate(typeof(Func<TSource, TValue>), propertyInfo.GetGetMethod());
return i => (object)typedFunc(i);
}
public static Func<TSource, object> CreatePropertyGetter<TSource>(PropertyInfo propertyInfo)
{
var typedCreator = typeof(UtilExtensions).GetMethod(nameof(CreateTypedPropertyGetter), BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
var concreteTypedCreator = typedCreator = typedCreator.MakeGenericMethod(typeof(TSource), propertyInfo.PropertyType);
return (Func<TSource, object>)concreteTypedCreator.Invoke(null, new object [] { propertyInfo });
}
public static ReadOnlyDictionary<string, Func<TSource, object>> PropertyGetters<TSource>() => PropertyExpressionsCache<TSource>.PropertyGetters;
static ReadOnlyDictionary<string, Func<TSource, object>> CreatePropertyGetters<TSource>() =>
typeof(TSource)
.GetSerializableProperties()
.ToDictionary(p => p.Name,
p => CreatePropertyGetter<TSource>(p))
.ToReadOnly();
static class PropertyExpressionsCache<TSource>
{
public static ReadOnlyDictionary<string, Func<TSource, object>> PropertyGetters { get; } = UtilExtensions.CreatePropertyGetters<TSource>();
}
public static ReadOnlyDictionary<TKey, TValue> ToReadOnly<TKey, TValue>(this IDictionary<TKey, TValue> dictionary) =>
new ReadOnlyDictionary<TKey, TValue>(dictionary ?? throw new ArgumentNullException());
}
And got roughly the same time, of 410 ms. Demo fiddle #4 here.
Notes:
The code in your question skips the first line of the CSV file, by calling reader.ReadLine();.
In my test harness this caused an incorrect number of records to be read, so I removed this line.
Rather than having a non-generic method that has a switch on the record type, I extracted a generic method that takes the record type and class map type as generic parameters. This makes delegate creation a little easier as it is no longer necessary to do runtime casting to the record type.

Pass a different list types to method and loop through it like array

// i have this class
public class SCHOOL
{
public int ID { get; set; }
public string Name { get; set; }
public string Country{ get; set; }
public decimal Total{ get; set; }
}
// and another class with different type
public class CLASS_2
{
public string Student { get; set; }
public DateTime Graduate { get; set; }
}
// and may bee i add class 3 and 4 with different type
// i fill the lists with my data
static void Main()
{
List < SCHOOL> FirstClass = new List < SCHOOL>();
FirstClass.Add( new SCHOOL{ID=1,Name="aaa",County="USA",Total=10});
FirstClass.Add( new SCHOOL{ID=1,Name="bbb",County="JAP",Total=7});
FirstClass.Add( new SCHOOL{ID=1,Name="ccc",County="GBR",Total=5});
List < CLASS_2 > SecondClass = new List < CLASS_2 >();
SecondClass.Add( new CLASS_2 {Student =1, Graduate ="2/6/2015"});
SecondClass.Add( new CLASS_2 {Student =1, Graduate ="2/4/2015"});
SecondClass.Add( new CLASS_2 {Student =1, Graduate ="2/8/2015"});
}
// i want to pass the first List and loop through my data
GetmyData ( firstClass);
// and also pass another List with different Class type to the same method and also loop through my data
GetmyData ( SenecdClass );
// i want one method to get the list and loop throught the data like array
private void GetmyData <T> ( List<T> newlist )
{
for (int y=0; y < newList.Count; y++)
{
for ( int x=0 ; x < newLsit[y].Colms; x++ )
{
Console.WriteLine ( newList[y][x].value );
}
}
}
I would say that firstly you need to use a generic method:
private void GetMyData(List<T> the List)
{
foreach (T entry in theList)
{
//deal with the entry on the list
}
}
and for the printing every property of the class, the best way in my eyes would be to override ToString
but if you need to access each property for something other than just displaying it will require reflection:
private void GetMyData(List<T> the List)
{
foreach (T entry in theList)
{
foreach (var property in typeof(T).GetProperties())
{
var propertyName = property.Name;
var propertyValue = property.GetValue(entry);
Console.WriteLine("Value of " + propertyName + " is " + propertyValue);
}
}
}
but bear in mind that not all properties can be read.
static void GetMyData(List theList)
{
int count = typeof(T).GetProperties().Count();
for ( int y = 0 ; y < theList.Count ; y++ )
{
for ( int x = 0; x < count; x++ )
{
var propertyName = typeof(T).GetProperties()[x].Name;
var propertyValue = typeof(T).GetProperties()[x].GetValue( theList[y] , null );
var propertyType = typeof(T).GetProperties()[x].PropertyType;
Console.WriteLine(propertyType + " " + propertyName + " " + propertyValue );
}
}
}

how to pass the correct target when getting value from a PropertyInfo[]

In the first inner loop I was able to pass the correct target object when getting the value from PropertyInfo[], however in the second inner loop it gives an exception that the target object is not correct.
So what I want is to get all of the values of a property with inner properties of this listProperties[j], how do I pass correctly the target object to get all of those values?
Model Data:
public class Foo
{
public string Name { get; set; }
public Bar BarProp { get; set; }
}
public class Bar
{
public string Name { get; set; }
public decimal Value { get; set; }
}
Method:
private void CreateDataRow(ISheet sheet, IList iList)
{
for (int i = 0; i < iList.Count; i++)
{
var listType = iList[i].GetType();
var listProperties = listType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
for (int j = 0; j < listProperties.Count(); j++)
{
int columnCount = j;
if (IsPrimitiveType(listProperties[j].PropertyType))
{
var columnData = listProperties[j].GetValue(iList[i], null) ?? "-";
var dataTypeValue = Convert.ChangeType(columnData, listProperties[j].PropertyType);
//omitted codes
continue;
}
var innerProperties = listProperties[j].PropertyType.GetProperties().ToArray();
for (int k = 0; k < innerProperties.Count(); k++)
{
//this throws an exception
var columnData = innerProperties[k].GetValue(listProperties[j], null) ?? "-";
//omitted codes
}
}
}
}
CreateDataRow is being called here:
private XSSFWorkbook CreateWorkbook(T model)
{
var workbook = new XSSFWorkbook();
var type = model.GetType();
var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var property in properties)
{
//check if the property is a list
if (property.PropertyType.IsGenericType &&
typeof(List<>).IsAssignableFrom(property.PropertyType.GetGenericTypeDefinition()))
{
var propertyValueList = (IList)property.GetValue(model, null);
if (propertyValueList != null && propertyValueList.Count > 0)
{
//create data row
CreateDataRow(sheet, propertyValueList);
}
}
}
return workbook;
}
The T model uses this Model:
public class ExportModel
{
public List<Foo> FooList { get; set; }
}
in this line of code:
var innerProperties = listProperties[j].PropertyType.GetProperties().ToArray();
You are getting properties of listProperties[j].PropertyType, but in GetValue:
var columnData = innerProperties[k].GetValue(listProperties[j], null) ?? "-";
You are sending listProperties[j] as instance argument. Correct one of the following lines:
var innerProperties = listProperties[j].GetProperties().ToArray();
//or
var columnData = innerProperties[k].GetValue(listProperties[j].PropertyType, null) ?? "-";
Different is between object instance and its type. The PropertyType represents types of retrieved Property and acts like:
propertyInfo.GetValue(x,y).GetType();
You should send exact instance of the target object not its type and not type of retrieved property. and if you want to get value of one of properties of property write:
var columnData = innerProperties[k].GetValue(listProperties[j].GetValue(iList[i],null) ,null) ?? "-";

Get All attributes of a class where there are lists of class c#?

How can i do to get all attributes of this class
public class Data
{
public Person;
public List<Nationality> nationality;
public List<Passport> passport;
public List<Family> family;
public List<WK_Permit> Permit;
public List<Role> role;
}
the expected output is for example
i wan to get something like that "DATA.PERSON.NAME" or "DATA.PASSPORT.NUMBER" or "DATA.ROLES.HOST"...etc so i have to get the class name and the attibuts name
I did this but it doesn't work for class's list
Type parent = typeof(VSM_Data);
FieldInfo[] children = parent.GetFields();
for (int i = 0; i < children.Length; i++)
{
Type child = children[i].FieldType;
var columnnamesChild = from t in child.GetProperties() select t.Name;
foreach (var item in columnnamesChild)
{
DragAndDrop FundDragAndDrop = new DragAndDrop();
FundDragAndDrop.TITLE = item;
FundDragAndDrop.adresse = "{{PERSON." + children[i].Name.ToUpper() + "." + item.ToUpper() + "}}";
FundList.Add(FundDragAndDrop);
}
you can do it using Reflection:
Reflection; for an instance:
obj.GetType().GetProperties();
obj.GetType().GetMembers();
for a type:
typeof(Foo).GetProperties();
typeof(Foo).GetMembers();
for example:
class Foo {
public int A {get;set;}
public string B {get;set;}
public string Member1;
public int Member2;
}
...
For Fields:
foreach(var prop in foo.GetType().GetFields()) {
Console.WriteLine("{0}={1}", prop.Name);
}
For Properties:
Foo foo = new Foo {A = 1, B = "abc"};
foreach(var prop in foo.GetType().GetProperties()) {
Console.WriteLine("{0}={1}", prop.Name, prop.GetValue(foo, null));
}
for Members:
foreach(var prop in foo.GetType().GetMembers()) {
Console.WriteLine("{0}", prop.Name);
}
This code will give you all public property name
Type type = typeof(Data);
foreach (var property in type.GetProperties())
string name = property.Name;
static void Main(string[] args)
{
Data data = new Data();
//you can use this for your case
var fields = data.GetType().GetFields().Select(f => f.Name);
foreach (var f in fields)
{
Console.WriteLine(f);
}
Console.WriteLine("===");
//but if you want a more generic one try this
var members = data.GetType().GetMembers().Select(m=>m.Name);
foreach (var member in members)
{
Console.WriteLine(member);
}
}
It ok i find the solution sometime the best way to find the solution is to ask the question and think by your self ;-)
Type type = typeof(VSM_Data);
foreach (var property in type.GetFields())
{
FieldInfo[] children = type.GetFields();
for (int i = 0; i < children.Length; i++)
{
Type child = property.FieldType;
var columnnamesChild = from t in child.GetFields() select t.Name;
foreach (var item in columnnamesChild)
{
DragAndDrop FundDragAndDrop = new DragAndDrop();
FundDragAndDrop.TITLE = item;
FundDragAndDrop.adresse = "{{PERSON." + children[i].Name.ToUpper() + "." + item.ToUpper() + "}}";
FundList.Add(FundDragAndDrop);
}
}
}

yield return returning same result on each iteration

The problem is to generate combinations of search parameters to be used as test case inputs in automation tests.
public class CombinationInput<T>
{
public string Name { get; set; }
public List<T> PossibleValues { get; set; }
public bool ReadOnly { get; set; }
}
GetCombinations is a method in the Combinationsgenerator Class:
private IEnumerable<object[]> _GetCombinations(ArrayList inputs)
{
var returnobjects = new object[inputs.Count];
var element = inputs[0];
var type = element.GetType();
var generictype = type.GenericTypeArguments[0];
var elementvalues = type.GetProperty("PossibleValues").GetValue(element, null) as IEnumerable;
foreach (var val in elementvalues)
{
returnobjects[0] = val;
if (inputs.Count > 1)
{
var objs = _GetCombinations(inputs.GetRange(1, inputs.Count - 1));
foreach (var item in objs)
{
for (int i = 0; i < item.Length; i++)
{
returnobjects[i + 1] = item[i];
}
yield return returnobjects;
}
}
else
{
yield return returnobjects;
}
}
}
Inside TestMethod:
[TestMethod]
public void GetCombinationTest()
{
var x = new CombinationInput<Enums.AmountType>();
var y = new CombinationInput<Enums.AreaMeasureType>();
var z = new CombinationInput<Enums.DisplayItemType>();
ArrayList list = new ArrayList();
list.Add(x);
list.Add(y);
list.Add(z);
var combgen = new CombinationGenerator(list);
var combs = combgen.GetCombinations();
}
Get Combinations is always returning the same combination when I run the code but when I debug through the code each time it hits the yield return statement its having the right combination.
You always return the same object as you are editing the content itself. What you are yielding is the reference to an array, and it's always the same reference. Instead of reusing the same array, you should move the declaration and creation of returnobjects within the foreach loop.

Categories