How to create Expression<Func<TModel, string>> expression from Property Name - c#

My Html helper method looks like following
public static MvcHtmlString Control<TModel>(this MyHtmlHelper<TModel> helper,
string propertyName, LayoutHelper layout, TemplateType templateType = TemplateType.Screen)
{
//...
}
I want to convert my property name into following
Expression<Func<TModel, string>> expression
Any help will be much appreciated

It looks like you want to call ModelMetadata.FromLambdaExpression, not FromStringExpression. You can create an expression like
x => x.PropertyName
from scratch, like this:
// Get a reference to the property
var propertyInfo = ExpressionHelper.GetPropertyInfo<TModel>(propertyName);
var model = ExpressionHelper.Parameter<TModel>();
// Build the LINQ expression tree backwards:
// x.Prop
var key = ExpressionHelper.GetPropertyExpression(model, propertyInfo);
// x => x.Prop
var keySelector = ExpressionHelper.GetLambda(typeof(TModel), propertyInfo.PropertyType, model, key);
To make the code more readable, the nitty-gritty expression tree manipulation is moved into this helper class:
public static class ExpressionHelper
{
private static readonly MethodInfo LambdaMethod = typeof(Expression)
.GetMethods()
.First(x => x.Name == "Lambda" && x.ContainsGenericParameters && x.GetParameters().Length == 2);
private static MethodInfo GetLambdaFuncBuilder(Type source, Type dest)
{
var predicateType = typeof(Func<,>).MakeGenericType(source, dest);
return LambdaMethod.MakeGenericMethod(predicateType);
}
public static PropertyInfo GetPropertyInfo<T>(string name)
=> typeof(T).GetProperties()
.Single(p => p.Name == name);
public static ParameterExpression Parameter<T>()
=> Expression.Parameter(typeof(T));
public static MemberExpression GetPropertyExpression(ParameterExpression obj, PropertyInfo property)
=> Expression.Property(obj, property);
public static LambdaExpression GetLambda<TSource, TDest>(ParameterExpression obj, Expression arg)
=> GetLambda(typeof(TSource), typeof(TDest), obj, arg);
public static LambdaExpression GetLambda(Type source, Type dest, ParameterExpression obj, Expression arg)
{
var lambdaBuilder = GetLambdaFuncBuilder(source, dest);
return (LambdaExpression)lambdaBuilder.Invoke(null, new object[] { arg, new[] { obj } });
}
}
Building the expression tree from scratch gives you the most flexibility in creating the lambda expression. Depending on the target property type, it may not always be an Expression<Func<TModel, string>> - the last type could be an int or something else. This code will build the proper expression tree no matter the target property type.

Referring to the following for reference
Creating Expression Trees by Using the API
Expression<Func<TModel, string>> GetPropertyExpression<TModel>(string propertyName) {
// Manually build the expression tree for
// the lambda expression model => model.PropertyName.
var parameter = Expression.Parameter(typeof(TModel), "model");
var property = Expression.Property(parameter, propertyName);
var expression = Expression.Lambda<Func<TModel, string>>(property, parameter);
return expression;
}
Which would allow you to derive the expression in the helper
public static MvcHtmlString Control<TModel>(this MyHtmlHelper<TModel> helper, string propertyName,
LayoutHelper layout, TemplateType templateType = TemplateType.Screen) {
Expression<Func<TModel, string>> expression = GetPropertyExpression<TModel>(propertyName);
var propertyMetadata = ModelMetadata.FromStringExpression(expression, helper.Html.ViewData);
//...other code...
}

Expression is just a wrapper around the lambda that creates a tree-style data structure. Things like HTML helpers need this so they can introspect the lambda to determine things like the name of the property. The meat of the type is in the Func<TModel, string>, which indicates that it requires a lambda that takes a class instance of some type (generic) and returns a string (the property value). In other words:
m => m.Foo
Where m is parameter to the lambda, and would likely be executed by passing in your model. The m, here, is analogous to a typed param to a normal method, so it can be named anything any other variable could be named. The return value, then, is Model.Foo, where Foo is the property you're accessing.

Related

Expression Tree Property Setter InvalidCastException

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.

Handling invalid input in extension of IQueryable.OrderBy using string object member names

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));
}

Create Expression<Func<T, object>> from a given Type

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 }));
}

Using Expression vs Func

I have several autocomplete actions, one of them is listed below. Instead of writing different predicates for each autocomplete Where method, I have created an autoCompletePredicate. Since I have multiple autocompletes I am using Reflection to get the Property which is required for that specific AutoComplete and use that Property in my autoCompletePredicate.
I have following code which is working alright.
static string param1, param2;
static PropertyInfo[] properties;
static PropertyInfo prop1, prop2;
public IHttpActionResult GetAutComplete(string term, string dependent)
{
int pagerSize = 10;
properties = new MyObject().GetType().GetProperties();
prop1 = properties.Where(p => p.Name.ToUpper().Equals("PROP1")).FirstOrDefault();
prop2 = properties.Where(p => p.Name.ToUpper().Equals("PROP2")).FirstOrDefault();
param1 = term;
param2 = dependent;
return Json(context.MyObject.Where(autoCompletePredicate).Select(r => new { label = r.PROP1 }).Distinct().OrderBy(r => r.label).Take(pagerSize).ToList());
}
Func<MyObject, int, bool> autoCompletePredicate = (GF, index) =>
{
bool isFound = false;
string term, dependent;
term = prop1.GetValue(GF).ToString();
dependent = prop2.GetValue(GF).ToString();
var termFound = term.Contains(param1.ToUpper());
var dependentFound = String.IsNullOrEmpty(param2) ? true : dependent.Contains(param2.ToUpper());
isFound = termFound && dependentFound;
return isFound;
};
How can I change this code into Expression. I tried below code which compiled fine but at runtime I got the following error
public static Expression<Func<MyObject, bool>> AutoCompleteExpression()
{
return r => prop1.GetValue(r).ToString().Contains(param1.ToUpper()) && (String.IsNullOrEmpty(param2) ? true : prop2.GetValue(r).ToString().Contains(param2.ToUpper()));
}
"LINQ to Entities does not recognize the method 'System.Object
GetValue(System.Object)' method, and this method cannot be translated
into a store expression."
I looked at the following post which makes absolute sense, but I am not sure how I can use that example in my scenario (which is dynamically finding properties using Reflection).
Also, what I would like to know what can be advantage of using Expression vs Func (specially in my case)
You are trying to trying to execute Contains method on string and want that to be represented in the ExpressionTrees, following is the code you need:
Create String Extension method - Contains: (Case Insensitive)
public static class StringExtensions
{
public static bool Contains(this string source, string toCheck)
{
return source.IndexOf(toCheck, StringComparison.OrdinalIgnoreCase) >= 0;
}
}
Create the AutoCompleteExpression method as follows, it returns Func<MyObject, bool>:
public static Func<MyObject, bool> AutoCompleteExpression()
{
// Create ParameterExpression
ParameterExpression parameterType = Expression.Parameter(typeof(MyObject), "object");
// Create MemberExpression for Columns
MemberExpression typeColumnProp1 = Expression.Property(parameterType, "PROP1");
MemberExpression typeColumnProp2 = Expression.Property(parameterType, "PROP2");
// Create MethoIndo
MethodInfo containsMethodInfo = typeof(StringExtensions).GetMethod("Contains",new[] { typeof(string), typeof(string) },null);
// Create ConstantExpression values
ConstantExpression constant1 = Expression.Constant(param1, typeof(string));
ConstantExpression constant2 = Expression.Constant(param2, typeof(string));
// Expression for calling methods
MethodCallExpression expression1 = Expression.Call(null, containsMethodInfo, typeColumnProp1, constant1);
MethodCallExpression expression2 = Expression.Call(null, containsMethodInfo, typeColumnProp2, constant2);
// Combine `MethodCallExpression` to create Binary Expression
BinaryExpression resultExpression = Expression.And(expression1,expression2);
// Compile Expression tree to fetch `Func<MyObject, bool>`
return Expression.Lambda<Func<MyObject, bool>>(resultExpression, parameterType).Compile();
}
It is possible to add lot more flexibility by defining custom extension methods and combining expressions using And / Or

How to assign a value from MemberExpression to a field according to another MemberExpression?

I would like write a method which accepts two MemberExpression, and generates a delegate which accepts two objects - source and target, and assigns the value from the source - according to it's MemberExpression, to the field of the target, according to the second MemberExpression. The objects does not have to be of the same type.
I'm looking for something like this:
public Action<TSource, TTarget> Map(Expression<Func<TSource, object>> getter, Expression<Func<TTarget, object>> setter)
{
var sourceField = getter.Body as MemberExpression;
var targetField = setter.Body as MemberExpression;
/*
* Now I would like to create a lambda expression which accepts TSource and TTarget instances,
* and assings TTarget according to the above getter and setter expressions. Kind of like:
* var assignExp = Expression.Assign(x, y);
* var lambda = Expression.Lambda<Action<TTarget, TSource>>( .... ).Compile();
* return lambda;
*/
}
Usage:
Target target;
Source source;
//...
var action = Map(p => p.NestedField.Dummy, x => x.TargetName);
action(source, target);
I don't understand how to build the expressions to send to Expression.Assign.
At this point, I don't mind about null values or initialization of fields. Please assume all fields are initialized.
Assign is used to generate assign expression, but in your case each lambda expression has own parameter, and both this parameters should be send to a new lambda expression.
So in my example i generate new assign expression, then create a new lambda expression, and send ParameterExpression from both getter and setter expressions to a new lambda.
So it should be like this:
Here is working sample - https://dotnetfiddle.net/uuPVAl and the code itself
using System;
using System.Linq.Expressions;
public class Program
{
public static void Main(string[] args)
{
Target target = new Target();
Source source = new Source()
{
NestedField = new NestedSource()
{
Dummy = "Hello world"
}
};
var action = Map<Source, Target>(p => p.NestedField.Dummy, x => x.TargetName);
action(source, target);
Console.WriteLine(target.TargetName);
}
public static Action<TSource, TTarget> Map<TSource, TTarget>(Expression<Func<TSource, object>> getter, Expression<Func<TTarget, object>> setter)
{
var sourceField = getter.Body as MemberExpression;
var targetField = setter.Body as MemberExpression;
// here we create new assign expression
var assign = Expression.Assign(targetField, sourceField);
// and then compile it with original two parameters
var lambda = Expression.Lambda<Action<TSource, TTarget>>(assign, getter.Parameters[0], setter.Parameters[0]);
return lambda.Compile();
}
}
public class Target
{
public string TargetName { get; set; }
}
public class NestedSource
{
public string Dummy { get; set; }
}
public class Source
{
public NestedSource NestedField { get; set; }
}
UPDATE
So each Lambda Expression can have any parameters. From code side it's ParameterExpression. When you write expression as typical code, then it means function parameters, so in your case (p) => p.NestedField.Dummy - (p) is parameter of that function. And expression inside body uses it - p.NestedField.Dummy, so to be able to compile it - lambda expression needs to know that parameter.
In this case you have two lambda expressions for target and source, and each of them have own parameter - (p) and (x) and each expression use own parameter. But in result function we need to use both of them, as we have two parameters in the function, so we need to resend original ParameterExpression from source and target to a new lambda. Or you can create a new ParameterExpression but then you need to create a new tree as old one will use old ParameterExpression. Usually such things are done with ExpressionVisitor class, but in your case we can just resend original expressions without tree body changes.
This will do:
public Action<TSource, TTarget> Map<TSource, TTarget>(Expression<Func<TSource, object>> getter, Expression<Func<TTarget, object>> setter)
{
var targetPropertyExpression = setter.Body as MemberExpression;
var targetProperty = targetPropertyExpression.Member as PropertyInfo;
return (src, tgt) => { targetProperty.SetValue(tgt, getter.Compile().Invoke(src)); };
}
It get's the property of the setter from the 1st lambda expression and just returns an action, which assigns the value to the property based on the 2nd lambda expression, which just needs to be invoked.
Take care of the <TSource, object> though, you maybe need an additional cast.

Categories