Generic C# - how to use reflection to assign properties to generic class - c#

I would like to assign values to various classes. How I would achieve the code below would work?
T row = new T();
row[propertyName1] = "value 1";
row[propertyName2] = "value 2";
row[propertyName3] = "value 3";
I have various classes. I have property names for each class with values.
What I am trying to do is to assign all table values from database to classes Lists. All classes in C# are exact match to tables in SQL. Entitiy framework do this. I would like to build the same functionality as EF do.

You can use such thing using reflexion:
using System.Reflection;
static void Test()
{
var list = new List<int>();
list.SetProperty("Capacity", 10);
}
static BindingFlags DefaultBindingFlags
= BindingFlags.Public
| BindingFlags.Static
| BindingFlags.Instance;
static void SetProperty<T>(this T instance, string name, object value)
//where T : ...
{
PropertyInfo p = instance.GetType().GetProperty("Capacity", DefaultBindingFlags);
if ( p != null )
p.SetValue(instance, value, null);
else
throw new Exception($"Property {name} doesn't exist.");
}

Related

Activator.CreateInstance with string

I'm trying to populate a generic List< T > from another List< U > where the field names match, something like the untested pseudocode below. Where I'm having problems is when T is a string, for instance, which has no parameterless constructor. I've tried adding a string directly to the result object, but this gives me the obvious error -- that a string is not of Type T. Any ideas of how to solve this issue? Thanks for any pointers.
public static List<T> GetObjectList<T, U>(List<U> givenObjects)
{
var result = new List<T>();
//Get the two object types so we can compare them.
Type returnType = typeof(T);
PropertyInfo[] classFieldsOfReturnType = returnType.GetProperties(
BindingFlags.Instance |
BindingFlags.Static |
BindingFlags.NonPublic |
BindingFlags.Public);
Type givenType = typeof(U);
PropertyInfo[] classFieldsOfGivenType = givenType.GetProperties(
BindingFlags.Instance |
BindingFlags.Static |
BindingFlags.NonPublic |
BindingFlags.Public);
//Go through each object to extract values
foreach (var givenObject in givenObjects)
{
foreach (var field in classFieldsOfReturnType)
{
//Find where names match
var givenTypeField = classFieldsOfGivenType.Where(w => w.Name == field.Name).FirstOrDefault();
if (givenTypeField != null)
{
//Set the value of the given object to the return object
var instance = Activator.CreateInstance<T>();
var value = field.GetValue(givenObject);
PropertyInfo pi = returnType.GetProperty(field.Name);
pi.SetValue(instance, value);
result.Add(instance);
}
}
}
return result;
}
If T is string and you have already created custom code to convert your givenObject to a string, you just need to do an intermediate cast to object to add it to a List<T>:
public static List<T> GetObjectList2<T, U>(List<U> givenObjects) where T : class
{
var result = new List<T>();
if (typeof(T) == typeof(string))
{
foreach (var givenObject in givenObjects)
{
var instance = givenObject.ToString(); // Your custom conversion to string.
result.Add((T)(object)instance);
}
}
else
{
// Proceed as before
}
return result;
}
Incidentally, you are adding an instance of T to result for every property of T that matches a property name in U and for every item in givenObjects. I.e. if givenObjects is a list of length 1 and T is a class with 10 matching properties, result could end up with 10 entries. This looks wrong. Also, you need to watch out for indexed properties.
As an alternative to this approach, consider using Automapper, or serializing your List<U> to JSON with Json.NET then deserializing as a List<T>.

Checking for Nulls on DB Record Mapping

How can I check for db null values in the attached code? Please understand I am a new C# convert...
What this code does is takes a IDataReader object and converts and maps it to a strongly-typed list of objects. But what I am finding is it completely errors out when there are null columns returned in the reader.
Converter
internal class Converter<T> where T : new()
{
// Declare our _converter delegate
readonly Func<IDataReader, T> _converter;
// Declare our internal dataReader
readonly IDataReader dataReader;
// Build our mapping based on the properties in the class/type we've passed in to the class
private Func<IDataReader, T> GetMapFunc()
{
// declare our field count
int _fc = dataReader.FieldCount;
// declare our expression list
List<Expression> exps = new List<Expression>();
// build our parameters for the expression tree
ParameterExpression paramExp = Expression.Parameter(typeof(IDataRecord), "o7thDR");
ParameterExpression targetExp = Expression.Variable(typeof(T));
// Add our expression tree assignment to the exp list
exps.Add(Expression.Assign(targetExp, Expression.New(targetExp.Type)));
//does int based lookup
PropertyInfo indexerInfo = typeof(IDataRecord).GetProperty("Item", new[] { typeof(int) });
// grab a collection of column names from our data reader
var columnNames = Enumerable.Range(0, _fc).Select(i => new { i, name = dataReader.GetName(i) }).AsParallel();
// loop through all our columns and map them properly
foreach (var column in columnNames)
{
// grab our column property
PropertyInfo property = targetExp.Type.GetProperty(column.name, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
// check if it's null or not
if (property != null)
{
// build our expression tree to map the column to the T
ConstantExpression columnNameExp = Expression.Constant(column.i);
IndexExpression propertyExp = Expression.MakeIndex(paramExp, indexerInfo, new[] { columnNameExp });
UnaryExpression convertExp = Expression.Convert(propertyExp, property.PropertyType);
BinaryExpression bindExp = Expression.Assign(Expression.Property(targetExp, property), convertExp);
// add it to our expression list
exps.Add(bindExp);
}
}
// add the originating map to our expression list
exps.Add(targetExp);
// return a compiled cached map
return Expression.Lambda<Func<IDataReader, T>>(Expression.Block(new[] { targetExp }, exps), paramExp).Compile();
}
// initialize
internal Converter(IDataReader dataReader)
{
// initialize the internal datareader
this.dataReader = dataReader;
// build our map
_converter = GetMapFunc();
}
// create and map each column to it's respective object
internal T CreateItemFromRow()
{
return _converter(dataReader);
}
}
Mapper
private static IList<T> Map<T>(DbDataReader dr) where T : new()
{
try
{
// initialize our returnable list
List<T> list = new List<T>();
// fire up the lamda mapping
var converter = new Converter<T>(dr);
while (dr.Read())
{
// read in each row, and properly map it to our T object
var obj = converter.CreateItemFromRow();
// add it to our list
list.Add(obj);
}
// reutrn it
return list;
}
catch (Exception ex)
{
// make sure this method returns a default List
return default(List<T>);
}
}
I just don't quite understand where the column to typed object happens in here, so I'd try to do it myself... but I just don;t know where it is.
I know this probably won't help much, but the error I am getting is:
Unable to cast object of type 'System.DBNull' to type 'System.String'.
and it happens on the
internal T CreateItemFromRow()
{
return _converter(dataReader); //<-- Here
}
Note
This does not happen if I wrap the columns in the query itself with an ISNULL(column, ''), but I am sure you can understand that this is surely not a solution
The problem lies in the line convertExp = Expression.Convert(propertyExp, property.PropertyType). You can't expect to convert DbNull value to its equivalent in framework type. This is especially nasty when your type is a value type. One option is to check if the read value from db is DbNull.Value and in case yes, you need to find a compatible value yourself. In some cases people are ok with default values of those types in C#. If you have to do this
property = value == DBNull.Value ? default(T): value;
a generic implementation would look like (as far as the foreach in your converter class goes):
foreach (var column in columns)
{
var property = targetExp.Type.GetProperty(
column.name,
BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
if (property == null)
continue;
var columnIndexExp = Expression.Constant(column.i);
var propertyExp = Expression.MakeIndex(
paramExp, indexerInfo, new[] { columnIndexExp });
var convertExp = Expression.Condition(
Expression.Equal(
propertyExp,
Expression.Constant(DBNull.Value)),
Expression.Default(property.PropertyType),
Expression.Convert(propertyExp, property.PropertyType));
var bindExp = Expression.Assign(
Expression.Property(targetExp, property), convertExp);
exps.Add(bindExp);
}
Now this does an equivalent of
property = reader[index] == DBNull.Value ? default(T): reader[index];
You could avoid the double lookup of the reader by assigning it to a variable and using its value in the conditional check. So this should be marginally better, but a lil' more complex:
foreach (var column in columns)
{
var property = targetExp.Type.GetProperty(
column.name,
BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
if (property == null)
continue;
var columnIndexExp = Expression.Constant(column.i);
var cellExp = Expression.MakeIndex(
paramExp, indexerInfo, new[] { columnIndexExp });
var cellValueExp = Expression.Variable(typeof(object), "o7thPropValue");
var convertExp = Expression.Condition(
Expression.Equal(
cellValueExp,
Expression.Constant(DBNull.Value)),
Expression.Default(property.PropertyType),
Expression.Convert(cellValueExp, property.PropertyType));
var cellValueReadExp = Expression.Block(new[] { cellValueExp },
Expression.Assign(cellValueExp, cellExp), convertExp);
var bindExp = Expression.Assign(
Expression.Property(targetExp, property), cellValueReadExp);
exps.Add(bindExp);
}
This does the conditional check this way:
value = reader[index];
property = value == DBNull.Value ? default(T): value;
This is one of the most annoying problems in dealing with datasets in general.
The way I normally get around it is to convert the DBNull value to something more useful, like an actual null or even a blank string in some cases. This can be done in a number of ways, but just recently I've taken to using extension methods.
public static T? GetValueOrNull<T>(this object value) where T : struct
{
return value == null || value == DBNull.Value ? (T?) null : (T) Convert.ChangeType(value, typeof (T));
}
A handy extension method for nullable types, so for example:
int? myInt = DataSet.Tables[0].Rows[0]["DBNullInt"].GetValueOrNull<int>();
Or a more generic one to just convert a DBNull in to a null:
public static object GetValueOrNull(this object value)
{
return value == DBNull.Value ? null : value;
}
string myString DataSet.Tables[0].Rows[0]["DBNullString"].GetValueOrNull();
You'll then get a null string, rather than trying to put a DBNull in to a string.
Hopefully that may help you a little.
As I come across this problem recently
both
Expression.TypeIs(propertyExp,typeof(DBNull));
and
Expression.Equal(propertyExp,Expression.Constant(DBNull.Value));
didn't work for me as they did increase memory allocation (which is my primary concern in this case)
here is the benchmark for both mapper approach compare to Dapper on 10K rows query.
TypeIs
and
Equal
so to fix this problem it came out that an IDataRecord is able to call "IsDBNull" to check whether the column in current reader is DBNull or not
and can be write as expression like
var isReaderDbNull = Expression.Call(paramExp, "IsDBNull", null, readerIndex);
finally, I end up with this solution
and now the performance is acceptable again.

Entity framework code first access to CSSpace

is there any way how to actually get to the CSSpace in entity framework 6 code first?
I tried to convert DbContext to ObjectContext like this:
public static ObjectContext ToObjectContext(this DbContext dbContext)
{
return ((IObjectContextAdapter)dbContext).ObjectContext;
}
but how can I get access to the CSSpace via the ObjectContext?
thanks for answers, any help would be appreciated, I tried a lot but it seems that nothing works :/
I was having the same problem and came across a few links online which I tweaked and build the extension methods below. TableMapping and ColumnMapping are just simple classes I use to store the translation data, so you can just ignore those.
I adapted snippets of Måns Tånneryd code from his answer here
(half way down).
The biggest issue with CSSpace is that most of the information you need is not exposed. Thus the most useful part of this is the use of reflection to get array and property data. Hopefully, this help. feel free to ping me if you need any further explanation.
public static IEnumerable<TableMapping> GetMappings(this DbContext context)
{
List<TableMapping> tableMappings = new List<TableMapping>();
var storageMapping = ((IObjectContextAdapter)context).ObjectContext.MetadataWorkspace.GetItem<GlobalItem>(((IObjectContextAdapter)context).ObjectContext.DefaultContainerName, DataSpace.CSSpace);
dynamic entitySetMaps = storageMapping.GetType().InvokeMember(
"EntitySetMaps",
BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance,
null, storageMapping, null);
foreach (var entitySetMap in entitySetMaps)
{
var typeMappings = GetArrayList("EntityTypeMappings", entitySetMap);
dynamic typeMapping = typeMappings[0];
dynamic types = GetArrayList("Types", typeMapping);
var fragments = GetArrayList("MappingFragments", typeMapping);
var fragment = fragments[0];
var tableSet = GetProperty("TableSet", fragment);
var tableName = GetProperty("Name", tableSet);
TableMapping tm = new TableMapping
{
Entity = context.FindContextTableName((string)types[0].Name),
Table = tableName
};
var properties = GetArrayList("AllProperties", fragment);
List<ColumnMapping> columnMappings = new List<ColumnMapping>();
foreach (var property in properties)
{
var edmProperty = GetProperty("EdmProperty", property);
var columnProperty = GetProperty("ColumnProperty", property);
ColumnMapping cm = new ColumnMapping
{
Property = edmProperty.Name,
Column = columnProperty.Name
};
columnMappings.Add(cm);
}
tm.Columns = columnMappings.AsEnumerable();
tableMappings.Add(tm);
}
return tableMappings.AsEnumerable();
}
private static ArrayList GetArrayList(string property, object instance)
{
var type = instance.GetType();
var objects = (IEnumerable)type.InvokeMember(
property,
BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance, null, instance, null);
var list = new ArrayList();
foreach (var o in objects)
{
list.Add(o);
}
return list;
}
private static dynamic GetProperty(string property, object instance)
{
var type = instance.GetType();
return type.InvokeMember(property, BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance, null, instance, null);
}

Referencing a field with a variable

I was wondering if it was possible to referance a class field via variable. like so:
int variable = 0;
while (variable > 3)
{
class._fieldvariable = something;
i++
}
where if I have fields: _field1, _field2, _field3 I can iterate through them all.
The main reason for doing this is I have an sql query that will append multiple records and I'd rather not have to do all the parameters multiple times but rather something like this:
while (i < 4)
}
command.Parameters.AddWithValue("#Alpha1", _alphai01.ToString());
i++
}
to let me set parameters 3 times with _alpha101, _alpha201 and _alpha301 are used for three different queries.
thanks!
Associate properties to the fields, all of them with get/set access.
If we're really talking about three fields, a more or less clean way to do so is by using a function GetField(int index) which would return the corresponding property. Then your code can be
class.GetField(i) = something;
An array is better in the more general case (for example, if the number of fields is expected to change).
I would prefer not to use reflection for such a simple purpose.
You can use invokeMember to call a certain setter of a property:
Object obj; //your instance;
obj.GetType().InvokeMember("Alpha1",
BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetProperty,
Type.DefaultBinder, obj, "ValueForAlpha1");
that's equal to
obj.Alpha1 = "ValueForAlpha1";
http://msdn.microsoft.com/en-US/library/vstudio/66btctbe.aspx
You could use reflection like this
if you have a class A
public class A
{
int field1, field2, field3;
}
you could set these fields like this
A obj = new A();
for (int i = 1; i < 4; i++)
{
FieldInfo field = obj.GetType().GetField(String.Format("field{0}", i), BindingFlags.NonPublic | BindingFlags.Instance);
if (null != field)
{
field.SetValue(obj, i);
}
}

C# Reflection - Get field values from a simple class

I have a class:
class A {
public string a = "A-val" , b = "B-val";
}
I want to print the object members by reflection
//Object here is necessary.
Object data = new A();
FieldInfo[] fields = data.GetType().GetFields();
String str = "";
foreach(FieldInfo f in fields){
str += f.Name + " = " + f.GetValue(data) + "\r\n";
}
Here is the desired result:
a = A-val
b = B-val
Unfortunately this did not work. Please help, thanks.
Once fixed to get rid of the errors (lacking a semi-colon and a bad variable name), the code you've posted does work - I've just tried it and it showed the names and values with no problems.
My guess is that in reality, you're trying to use fields which aren't public. This code:
FieldInfo[] fields = data.GetType().GetFields();
... will only get public fields. You would normally need to specify that you also want non-public fields:
FieldInfo[] fields = data.GetType().GetFields(BindingFlags.Public |
BindingFlags.NonPublic |
BindingFlags.Instance);
(I hope you don't really have public fields, after all...)
Remember when you write fields like :
public string VarName{ get; set;}
Then actually you have this code(this is what reflection see) :
private string _varName;
public string get_VarName(){
....
}
public void set_VarName(strig value){
....
}
As #Stanislav say, you must keep in mind the backing fields generated by the compiler for properties. If you want to exclude these fields you can use the following code:
FieldInfo[] fields = data.GetType()
.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
.Where(f => f.GetCustomAttribute<CompilerGeneratedAttribute>() == null)
.ToArray();

Categories