I am defining a collection of mappings in code at design time and then executing those mappings at run time after doing some data extraction :
public class FormExtractionMap<T>
{
public Expression<Func<T>> Destination { get; set; }
public string Source { get; set; }
}
Mapping Code:
var extractionRequest = new ExtractionRequest<PlanningApplication>
{
Mapping = new List<FormExtractionMap<PlanningApplication>>
{
new FormExtractionMap<PlanningApplication> {Destination = x => x.Site.Address.MapCoordinate.Eastings, Source = "site_address_easting"},
new FormExtractionMap<PlanningApplication> {Destination = x => x.Site.Address.MapCoordinate.Northings, Source = "site_address_northing"},
}
};
and then I'm looking through each of the mapping expressions to go and get the Source value (any Type) and then assign it to the Destination Expression (any Type).
foreach (var extractionMap in extractionRequest.Mapping)
{
extractionRequest.ExtractTo.Set(extractionMap.Destination, form.GetValue(extractionMap.Source));
}
I then have Expression extensions to create a setter, compile and do the property assignment.
public static TEntity Set<TEntity, TProperty>(
this TEntity obj,
Expression<Func<TEntity, TProperty>> selector,
TProperty value)
{
var setterExpr = CreateSetter(selector);
setterExpr.Compile()(obj, value);
return obj;
}
private static Expression<Action<TEntity, TProperty>> CreateSetter<TEntity, TProperty>(Expression<Func<TEntity, TProperty>> selector)
{
ParameterExpression valueParameterExpression = Expression.Parameter(typeof(TProperty), "value");
Expression targetExpression = selector.Body is UnaryExpression ? ((UnaryExpression)selector.Body).Operand : selector.Body;
var newValue = Expression.Parameter(selector.Body.Type);
return Expression.Lambda<Action<TEntity, TProperty>>
(
Expression.Assign(targetExpression, Expression.Convert(valueParameterExpression, targetExpression.Type)),
selector.Parameters.Single(),
valueParameterExpression
);
}
if Source is a string and the Destination is a string the value is assigned fine. If the Destination is a double on setterExpr.Compile()(obj, value); I get :
System.InvalidCastException : Unable to cast object of type
'System.String' to type 'System.Double'.
I thought the "Expression.Convert" was handling the type conversion but clearly not. What am I doing wrong please ?
So, I did eventually solve this. Expression.Convert is akin to an explicit cast (Foo)Bar, not as "Convert" implies. I ended up with:
private static Expression<Action<TEntity, TProperty>> CreateSetter<TEntity, TProperty>(
Expression<Func<TEntity, TProperty>> selector, Type valueParameterType)
{
ParameterExpression valueParameterExpression = Expression.Parameter(typeof(object), "value");
Expression targetExpression = selector.Body is UnaryExpression ? ((UnaryExpression)selector.Body).Operand : selector.Body;
var resultBody = ConvertToDestination(valueParameterExpression, valueParameterType, targetExpression);
return Expression.Lambda<Action<TEntity, TProperty>>
(
Expression.Assign(targetExpression, resultBody),
selector.Parameters.Single(),
valueParameterExpression
);
}
private static Expression ConvertToDestination(ParameterExpression valueParameterExpression, Type valueParameterType, Expression targetExpression)
{
if (valueParameterType == typeof(string))
{
switch (targetExpression.Type)
{
case Type _ when targetExpression.Type == typeof(double):
return Expression.Call(typeof(Convert), "ToDouble", null, valueParameterExpression);
case Type _ when targetExpression.Type == typeof(int):
return Expression.Call(typeof(Convert), "ToInt", null, valueParameterExpression);
default:
return Expression.Convert(valueParameterExpression, targetExpression.Type);
}
}
return Expression.Convert(valueParameterExpression, targetExpression.Type);
}
However, I thought it was messy, verbose and frankly unnecessary as I was able to implement similar functionality using AutoMapper in a few hours. Automapper does a better job of type conversion, caching the maps etc. So the real solution was re-factor and don't re-invent the wheel.
Related
I was generating a dynamic expression on CustomType based on some parameters. Code looks like this:
ParameterExpression parameter = Expression.Parameter(typeof(CustomType), "x");
MemberExpression idMember = Expression.Property(parameter, "CustomProperty");
When I changed from type CustomType to interface ICustomType it stopped working by throwing an error 'Instance property 'CustomProperty' is not defined for type 'ICustomType''
. How to fix it?
Without a minimum verifiable example, we cannot be sure what the problem is, but from your example code, I've put together the following:
interface ICustomType
{
int CustomProperty { get; set; }
}
class CustomType : ICustomType
{
public int CustomProperty { get; set; }
}
Now, when I call your example code, everything works as expected
ParameterExpression parameter = Expression.Parameter(typeof(CustomType), "x");
MemberExpression idMember = Expression.Property(parameter, "CustomProperty");
Also, when I change the type to ICustomType, it still works as expected.
ParameterExpression parameter = Expression.Parameter(typeof(ICustomType), "x");
MemberExpression idMember = Expression.Property(parameter, "CustomProperty");
However, if I remove the declaration of CustomProperty from ICustomType, I get the following error:
Instance property 'CustomProperty' is not defined for type 'ICustomType'
So, this leads me to believe that your interface does not include a declaration for CustomProperty. If you add it to your interface, your code should then work.
A common task when working with expressions is to replace certain nodes with other nodes. For instance, you can replace ParameterExpression as in this answer. I believe the OP used a similar parameter replacer, and forgot to also replace MemberExpression.
If you replace parameters of an expression, the original MemberExpression might not the new parameter type. E.g. a member expression for CustomType.CustomProperty will not be able to handle ICustomType.CustomProperty.
If my theory is correct, the OP must replace some MemberExpression instances too. The following expression visitor will do the trick:
public class ParameterReplacerVisitor : ExpressionVisitor
{
private readonly Type newType;
private Dictionary<ParameterExpression, ParameterExpression> parametersToReplace;
public ParameterReplacerVisitor(Type newType)
{
this.newType = newType;
}
public LambdaExpression Convert(LambdaExpression expression)
{
parametersToReplace = expression.Parameters
.Where(p => ShouldReplace(p.Type))
.ToDictionary(p => p, p => Expression.Parameter(newType, p.Name));
return (LambdaExpression)Visit(expression);
}
protected override Expression VisitLambda<T>(Expression<T> node)
{
var parameters = node.Parameters.Select(GetNewParameter);
return Expression.Lambda(Visit(node.Body), parameters);
}
protected override Expression VisitParameter(ParameterExpression node)
{
return base.VisitParameter(GetNewParameter(node));
}
protected override Expression VisitMember(MemberExpression node)
{
if (ShouldReplace(node.Member.DeclaringType))
{
var targetProperty = GetNewProperty(node.Member);
node = Expression.MakeMemberAccess(Visit(node.Expression), targetProperty);
}
return base.VisitMember(node);
}
private MemberInfo GetNewProperty(MemberInfo member)
{
return newType.GetProperty(member.Name) ?? throw new ArgumentException(
$"Property '{member.Name}' is not defined for type '{newType.Name}'"
);
}
private bool ShouldReplace(Type type) => newType.IsAssignableFrom(type);
private ParameterExpression GetNewParameter(ParameterExpression parameter)
{
parametersToReplace.TryGetValue(parameter, out var newParameter);
return newParameter ?? parameter;
}
}
Example
Expression<Func<Derived, string>> derived = t => t.A;
var lessDerived = derived.ToLessDerived<Derived, IBase, string>();
var d = lessDerived.Compile();
var result = d.Invoke(new Base());
And the extension method:
public static Expression<Func<TLess, TValue>> ToLessDerived<TClass, TLess, TValue>(this Expression<Func<TClass, TValue>> expression)
{
var visitor = new ParameterReplacerVisitor(typeof(TLess));
return (Expression<Func<TLess, TValue>>)visitor.Convert(expression);
}
For me, this solved the exact same type of error as the OP asked about.
I wrote an extension method to IQueryable OrderBy (and OrderByDescending, ThenBy and ThenByDescending), which all call my function CreateExpression to get the expression for sorting.
Problem is if someone calls this method with an invalid parameter, for example a string which is not a member name in the object type being sorted. That's why I check if pi is not null. Now I need to return something other than null if the parameter was invalid and pi is null, but I'm not quite sure how to do that. I would like to return an expression without sorting, is that possible and how would I do that?
public static IQueryable<T> OrderBy<T>(this IQueryable<T> source, string orderBy)
{
if (!string.IsNullOrWhiteSpace(orderBy))
{
var resultExp = CreateExpression(source, "OrderBy", orderBy);
return source.Provider.CreateQuery<T>(resultExp);
}
else
{
return source;
}
}
private static MethodCallExpression CreateExpression<T>(IQueryable<T> source, string methodName, string orderBy)
{
if (methodName != "OrderBy" && methodName != "OrderByDescending" && methodName != "ThenBy" && methodName != "ThenByDescending")
methodName = "OrderBy";
if (!string.IsNullOrWhiteSpace(orderBy))
{
PropertyInfo pi = typeof(T).GetProperty(orderBy);
if (pi != null)
{
var parameter = Expression.Parameter(typeof(T), "p");
MemberExpression me = Expression.MakeMemberAccess(parameter, pi);
var orderByExp = Expression.Lambda(me, parameter);
return Expression.Call(typeof(Queryable), methodName,
new Type[] { typeof(T), pi.PropertyType }, source.Expression, Expression.Quote(orderByExp));
}
}
//todo
return null;
}
Potential solution would be to use a property expression like this. Pass a property selector into your method, instead of a string:
Expression<Func<T, TProperty>> propertySelector
Inside of your method you can get the name of your property and use it just like in your code:
private static string GetPropertyName<TSource, TProperty>(Expression<Func<TSource, TProperty>> propertySelector)
{
MemberExpression member = propertySelector.Body as MemberExpression;
PropertyInfo propInfo = member.Member as PropertyInfo;
return propInfo.Name;
}
Your method signature would look like this:
public static IQueryable<T> OrderBy<T, TProperty>(this IQueryable<T> source, Expression<Func<T, TProperty>> propertySelector)
And you could call it this way:
collection.OrderBy(p => p.UserName)
You'll get static typing, and you can remove the magic string.
Otherwise, than that - I'd throw an InvalidOperationException, given it's an invalid situation and the user of your code should be informed about it with an exception - rather than the code silently failing.
Since you have written your extension method to work with any type (you have no constraints), the only option you have is to throw an exception if the user has provided you with a property name that does not exist. There is not much else you can do.
However, if you apply a simple constraint to T, then you can use that to provide a default sort in the case wherein the provided property does not exist. Here is how:
Create an interface and have all your Ts implement the interface:
public interface IOrderable
{
int Id { get; }
}
Change your extension and apply a constraint to it. Then if you do not find the provided property name, return an expression which will sort on the Id column by default:
private static MethodCallExpression CreateExpression<T>(IQueryable<T> source, string methodName, string orderBy)
where T : IOrderable
{
// your code...
//here is the todo
// Here we are returning an expression which will sort on the
// Id column by default
var parameterDef = Expression.Parameter(typeof(T), "p");
PropertyInfo piDef = typeof(T).GetProperty(nameof(IOrderable.Id));
MemberExpression meDef = Expression.MakeMemberAccess(parameterDef, piDef);
var orderByExpDef = Expression.Lambda(meDef, parameterDef);
return Expression.Call(typeof(Queryable), methodName,
new Type[]
{
typeof(T), piDef.PropertyType
},
source.Expression, Expression.Quote(orderByExpDef));
}
I am looking to use CsvHelper dynamically by building up Expressions in code which represent property member access for a given type.
The method I am trying to pass these expressions to has the following signature:
public virtual CsvPropertyMap<TClass, TProperty> Map<TProperty>( Expression<Func<TClass, TProperty>> expression )
{
//
}
So you would normally call it, for any given type you want to map, like this (for a type with a property called 'stringProperty'):
mapper.Map(x => x.StringProperty);
Passing in a lambda which is converted internally into an Expression<Func<T, object>>
I have tried to create this expression in code, using Expressions. At compile time it all works fine (in that it returns an Expression<Func<TModel, object>>), but at runtime I get an exception 'not a member access'. Here is the code which takes a PropertyInfo object representing the property I want to map:
private Expression<Func<TModel, object>> CreateGetterExpression( PropertyInfo propertyInfo )
{
var getter = propertyInfo.GetGetMethod();
Expression<Func<TModel, object>> expression = m => getter.Invoke( m, new object[] { } );
return expression;
}
Basically, how do I build that Expression up properly in code?
Just try something looks like this:
public static Expression<Func<T, P>> GetGetter<T, P>(string propName)
{
var parameter = Expression.Parameter(typeof(T));
var property = Expression.Property(parameter, propName);
return Expression.Lambda<Func<T, P>>(property, parameter);
}
public static Expression<Func<T, P>> GetGetter<T, P>(PropertyInfo propInfo)
{
var parameter = Expression.Parameter(typeof(T));
var property = Expression.Property(parameter, propInfo);
return Expression.Lambda<Func<T, P>>(property, parameter);
}
It's example of usages:
private class TestCalss
{
public int Id { get; set; }
}
private static void Main(string[] args)
{
var getter = GetGetter<TestCalss, int>(typeof(TestCalss).GetProperty("Id")).Compile();
Console.WriteLine(getter(new TestCalss { Id = 16 }));
}
I am using this code to set the value of a property via reflection :
public static void Set<T>(this T target, Expression<Func<T, object>> memberLamda, object value)
{
var memberSelectorExpression = memberLamda.Body as MemberExpression;
if (memberSelectorExpression != null)
{
var property = memberSelectorExpression.Member as PropertyInfo;
if (property != null)
{
property.SetValue(target, value, null);
}
}
}
But for some reason when I do :
myObject.Set(x=>x.ID, 1);
Where ID is of type int, I can see that memberSelectorExpression is null. However I have no issue with properties of a reference type.
I am not very familiar yet with expression trees, what I am doing wrong ?
The solution is to use the following signature :
public static void Set<T, TProp>(this T target, Expression<Func<T, TProp>> memberLamda,
TProp value)
To make sure a MemberExpression is correctly inferred. The "object" generic constraint is not specific enough.
The thing to be aware of is that your expression body will most likely be wrapped in a Convert expression, representing the fact that your property is being implicitly cast as an object. So you'll probably need code something like this in your Setmethod.
var expressionBody = memberLamda.Body;
if (expressionBody is UnaryExpression expression && expression.NodeType == ExpressionType.Convert)
{
expressionBody = expression.Operand;
}
var memberSelectorExpression = (MemberExpression)expressionBody;
I have two types: Cat and Dog. I'd like to select Cats using a Func<Dog, bool>. To do that, I need a way to map the properties from Cat to Dog in some sort of mapper (similar to how AutoMapper maps properties from one object to another type of object).
I'm imagining something like this:
public Cat GetCat(Func<Dog, bool> selector)
{
Func<Cat, bool> mappedSelector = getMappedSelector(selector);
return _catRepository.Get(mappedSelector);
}
private Func<Cat, bool> getMappedSelector(Func<Dog, bool> selector)
{
//some code here to map from one function type to another
//something like AutoMapper would be sweet...
//something that I can configure how I want the properties to be mapped.
}
Either there's already something that does this or there should be.
Here's a solution using AutoMapper:
Func<Cat, bool> GetMappedSelector(Func<Dog, bool> selector)
{
Func<Cat, Dog> mapper = Mapper.CreateMapExpression<Cat, Dog>().Compile();
Func<Cat, bool> mappedSelector = cat => selector(mapper(cat));
return mappedSelector;
}
UPDATE: It's been 1.5 years since I first answered this, and I figured I'd expand on my answer now since people are asking how to do this when you have an expression as opposed to a delegate.
The solution is the same in principle - we need to be able to compose the two functions (selector and mapper) into a single function. Unfortunately, since there's no way in C# to "call" one expression from another (like we could with delegates), we can't directly represent this in code. For example, the following code will fail to compile:
Expression<Func<Cat, bool>> GetMappedSelector(Expression<Func<Dog, bool>> selector)
{
Expression<Func<Cat, Dog>> mapper = Mapper.CreateMapExpression<Cat, Dog>();
Expression<Func<Cat, bool>> mappedSelector = cat => selector(mapper(cat));
return mappedSelector;
}
The only way to create our composed function, therefore, is to build up the expression tree ourselves using the System.Linq.Expressions classes.
What we really need to do is to modify the body of the selector function so that all instances of its parameter are replaced by the body of the mapper function. This will become the body of our new function, which will accept mapper's parameter.
To replace the parameter I created a subclass of ExpressionVisitor class that can traverse an expression tree and replace a single parameter with an arbitrary expression:
class ParameterReplacer : ExpressionVisitor
{
private ParameterExpression _parameter;
private Expression _replacement;
private ParameterReplacer(ParameterExpression parameter, Expression replacement)
{
_parameter = parameter;
_replacement = replacement;
}
public static Expression Replace(Expression expression, ParameterExpression parameter, Expression replacement)
{
return new ParameterReplacer(parameter, replacement).Visit(expression);
}
protected override Expression VisitParameter(ParameterExpression parameter)
{
if (parameter == _parameter)
{
return _replacement;
}
return base.VisitParameter(parameter);
}
}
Then I created an extension method, Compose(), that uses the visitor to compose two lambda expressions, an outer and an inner:
public static class FunctionCompositionExtensions
{
public static Expression<Func<X, Y>> Compose<X, Y, Z>(this Expression<Func<Z, Y>> outer, Expression<Func<X, Z>> inner)
{
return Expression.Lambda<Func<X ,Y>>(
ParameterReplacer.Replace(outer.Body, outer.Parameters[0], inner.Body),
inner.Parameters[0]);
}
}
Now, with all that infrastructure in place, we can modify our GetMappedSelector() method to use our Compose() extension:
Expression<Func<Cat, bool>> GetMappedSelector(Expression<Func<Dog, bool>> selector)
{
Expression<Func<Cat, Dog>> mapper = Mapper.CreateMapExpression<Cat, Dog>();
Expression<Func<Cat, bool>> mappedSelector = selector.Compose(mapper);
return mappedSelector;
}
I created a simple console application to test this out. Hopefully, my explanation was not too obfuscated; but unfortunately, there isn't really a simpler approach to doing what you're trying to do. If you are still totally confused, at least you can reuse my code and have gained an appreciation for the nuances and complexities of dealing with expression trees!
#luksan, thanks for the inspiration! Your solution didn't solve my problem, but got me thinking. Since I needed to pass the translated expression to IQueryable.OrderBy(), using the inside-expression translation approach didn't work. But I came up with a solution that will work on both cases and is also simpler to implement. It's also generic so can be reused for any mapped types. Here is the code:
private Expression<Func<TDestination, TProperty>> GetMappedSelector<TSource, TDestination, TProperty>(Expression<Func<TSource, TProperty>> selector)
{
var map = Mapper.FindTypeMapFor<TSource, TDestination>();
var mInfo = ReflectionHelper.GetMemberInfo(selector);
if (mInfo == null)
{
throw new Exception(string.Format(
"Can't get PropertyMap. \"{0}\" is not a member expression", selector));
}
PropertyMap propmap = map
.GetPropertyMaps()
.SingleOrDefault(m =>
m.SourceMember != null &&
m.SourceMember.MetadataToken == mInfo.MetadataToken);
if (propmap == null)
{
throw new Exception(
string.Format(
"Can't map selector. Could not find a PropertyMap for {0}", selector.GetPropertyName()));
}
var param = Expression.Parameter(typeof(TDestination));
var body = Expression.MakeMemberAccess(param, propmap.DestinationProperty.MemberInfo);
var lambda = Expression.Lambda<Func<TDestination, TProperty>>(body, param);
return lambda;
}
Here is the ReflectionHelper code (used just to keep the code above cleaner)
private static class ReflectionHelper
{
public static MemberInfo GetMemberInfo(Expression memberExpression)
{
var memberExpr = memberExpression as MemberExpression;
if (memberExpr == null && memberExpression is LambdaExpression)
{
memberExpr = (memberExpression as LambdaExpression).Body as MemberExpression;
}
return memberExpr != null ? memberExpr.Member : null;
}
}