I have this injector which inject data using Expression
/// <summary>
/// Inject a data to an instance of T
/// </summary>
/// <typeparam name="T">The type of object as parameter</typeparam>
/// <param name="data">Object instance to where the data is injected</param>
/// <param name="pairData">Set of data info</param>
public static void InjectData<T>(this T data, Dictionary<Expression<Func<T, object>>, object> pairData) where T : IAuditable
{
// posible content of T data
// Employee
// : Code
// : Name
// : ID
// possible content of pairData
// Key: a => a.Code
// Value: "12345"
// Key: a => a.Name
// Value: "Vincent"
// after the injection, the data (Employee) will now have value on each properties: Code, Name
foreach (var _o in pairData)
{
var _value = Expression.Constant(_o.Value);
var _assign = Expression.Assign(_o.Key, _value);
//_lambda.Compile()(data);
//var _lambda = Expression.Lambda<Action<object, T>>(_assign, _value, _o.Key);
//_lambda.Compile()(_o.Value, data);
}
}
i only pass a collection of Expression and value.
Expression on which where to assign the value (eg. a => a.Code) and value (eg. "123456")
i want to assign it to an object T but with my current attempt, i got a System.ArgumentException: Incorrect number of parameters supplied for lambda declaration. I am stuck and don't know what to do next. :) can anyone point me to the right direction on how to make this work?
This is the sample usage
Employee _employee = new Employee();
Dictionary<Expression<Func<Employee, object>>, object> _x = new Dictionary
<Expression<Func<Employee, object>>, object>
{
{a => a.Code, "12345"}
};
_employee.InjectData(_x);
Assert.AreEqual("12345", _employee.Code);
i also tried
foreach (var _o in pairData)
{
var _mem = _o.Key.Body as MemberExpression;
var _prop = _mem.Member as PropertyInfo;
var _setMethod = _prop.GetSetMethod();
_prop.SetValue(data, _o.Value);
and i can see a progress here because the private property is already assigned but the getter is NULL
Thanks in advance
any help would be appreciated.
You can use the following code:
public static void InjectData<T>(this T data, Dictionary<Expression<Func<T, object>>, object> pairData)
{
foreach (var pair in pairData)
{
data.SetPropertyValue(pair.Key, pair.Value);
}
}
public static T SetPropertyValue<T>(this T target, Expression<Func<T, object>> memberLamda, object value)
{
var memberSelectorExpression = memberLamda.Body as MemberExpression;
if (memberSelectorExpression == null)
{
return target;
}
var property = memberSelectorExpression.Member as PropertyInfo;
if (property == null)
{
return target;
}
property.SetValue(target, value, null);
return target;
}
Basically, this adds another extension method to set a property based on the expression.
If you only want to do changes when the value is actually different (which is handy when working with INotifyPropertyChanged
), you can use the following code which takes an additional comparer to check to see if the value changed:
public static T SetPropertyValue<T, R>(this T target, Expression<Func<T, R>> memberLamda, R value)
{
return target.SetPropertyValue(memberLamda, value, EqualityComparer<R>.Default);
}
public static T SetPropertyValue<T, R>(this T target, Expression<Func<T, R>> memberLamda, R value, IEqualityComparer<R> comparer)
{
var memberSelectorExpression = memberLamda.Body as MemberExpression;
if (memberSelectorExpression == null)
{
return target;
}
var property = memberSelectorExpression.Member as PropertyInfo;
if (property == null)
{
return target;
}
var currentValue = (R) property.GetValue(target, null);
if (comparer.Equals(currentValue, value))
{
return target;
}
property.SetValue(target, value, null);
return target;
}
Note that here we use the comparer to check if the current value equals the new value. If so, we just return (no need to update), if not we set the value.
Doesn't make too much sense to me, but here it is:
public static void InjectData<T>(this T data, Dictionary<Expression<Func<T, object>>, object> pairData) where T : IAuditable
{
foreach (var item in pairData)
{
var member = item.Key;
// If member type is a reference type, then member.Body is the property accessor.
// For value types it is Convert(property accessor)
var memberBody = member.Body as MemberExpression ?? (MemberExpression)((UnaryExpression)member.Body).Operand;
var lambda = Expression.Lambda<Action<T>>(Expression.Assign(memberBody, Expression.Constant(item.Value)), member.Parameters);
var action = lambda.Compile();
action(data);
}
}
UPDATE As requested in the comment, a version that supports nested class property intitialization:
public static void InjectData<T>(this T data, Dictionary<Expression<Func<T, object>>, object> pairData) //where T : IAuditable
{
var assignments = new List<Expression>();
foreach (var item in pairData)
{
var member = item.Key;
// If member type is a reference type, then member.Body is the property accessor.
// For value types it is Convert(property accessor)
var memberBody = member.Body as MemberExpression ?? (MemberExpression)((UnaryExpression)member.Body).Operand;
assignments.Clear();
assignments.Add(Expression.Assign(memberBody, Expression.Constant(item.Value)));
var target = member.Parameters[0];
while (memberBody.Expression != target)
{
var childMember = (MemberExpression)memberBody.Expression;
assignments.Add(Expression.IfThen(Expression.ReferenceEqual(childMember, Expression.Constant(null)),
Expression.Assign(childMember, Expression.New(childMember.Type))));
memberBody = childMember;
}
assignments.Reverse();
var body = assignments.Count > 1 ? Expression.Block(assignments) : assignments[0];
var lambda = Expression.Lambda<Action<T>>(body, target);
var action = lambda.Compile();
action(data);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With