Reputation: 7515
I have written the following code to handle mapping parameters from my database to my data types (trust me I wish I could use a std. ORM but that isn't doable for so many reasons)
public void LoadDatabaseValue<T>(DataTable partData, string identifier, string mappingName, Expression<Func<T>> mappingProperty)
{
var partAttributeValue = mappingProperty.Name;
var memberExpression = (MemberExpression)mappingProperty.Body;
var prop = (PropertyInfo)memberExpression.Member;
try
{
var selectedRow = partData.Select($"partattributename = '{mappingName}'");
var selectedValue = selectedRow[0]["PartAttributeValue"];
var typedOutput = (T)Convert.ChangeType(selectedValue, typeof(T));
prop.SetValue(memberExpression.Expression, typedOutput, null);
}
catch (Exception exception)
{
_databaseImportError = true;
// code to log this error
}
When I try to run this I get the following exception
{System.Reflection.TargetException: Object does not match target type.
at System.Reflection.RuntimeMethodInfo.CheckConsistency(Object target)
at System.Reflection.RuntimeMethodInfo.InvokeArgumentsCheck(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at System.Reflection.RuntimePropertyInfo.SetValue(Object obj, Object value, BindingFlags invokeAttr, Binder binder, Object[] index, CultureInfo culture)
at System.Reflection.RuntimePropertyInfo.SetValue(Object obj, Object value, Object[] index) }
When I debug it my typedOutput lines up with my property type, so I am not sure why it is throwing this exception.
I am calling it with for example
LoadDatabaseValue(partData, identifier, "Offset", () => Offset);
Upvotes: 0
Views: 808
Reputation: 205559
If you want to keep your current method design, you need a way to somehow evaluate the memberExpression.Expression
in order to be able to call SetValue
method.
So change the line
prop.SetValue(memberExpression.Expression, typedOutput, null);
to
prop.SetValue(Evaluate(memberExpression.Expression), typedOutput, null);
and then use one of the following implementations:
(A) This will be sufficient if you use only property accessors:
static object Evaluate(Expression e)
{
if (e == null) return null;
var me = e as MemberExpression;
if (me != null)
return ((PropertyInfo)me.Member).GetValue(Evaluate(me.Expression), null);
return ((ConstantExpression)e).Value;
}
(B) This one is more universal, but slower:
static object Evaluate(Expression e)
{
if (e == null) return null;
return Expression.Lambda(e).Compile().DynamicInvoke();
}
Upvotes: 1
Reputation: 112279
The first parameter of SetValue
must be an object containing the property whose value you want to set.
var obj = new TEntity();
prop.SetValue(obj, typedOutput); // From .NET 4.5 there is an overload with just 2 parameters
Now obj.Offset
should have the desired value.
So there are two types involved: The type of the object containing the property and the type of the property itself (e.g. int
, string
etc.).
Therefore your expression should be like this:
Expression<Func<TEntity, object>> mappingProperty
where TEntity
is the type of the object and object
is the yet unknown type of a property of this object. Unless you know the type of the property in advance, in which case you would have
Expression<Func<TEntity, TProperty>> mappingProperty
You would call it like this:
LoadDatabaseValue(partData, identifier, "Offset", x => x.Offset);
You must change the type like this (unless the selectedValue
is already of the correct type):
object typedOutput = Convert.ChangeType(selectedValue, prop.PropertyType);
Upvotes: 0