I try to make an expression with a PropertyInfo. The expression should look like:
Expression < Func < PropertyType >>
I need this Expression for this:
https://github.com/aspnet/AspNetCore/blob/8d46b3a64ea784c95dddeb9d421c7cda6de993a2/src/Components/Web/src/Forms/ValidationMessage.cs
The For property needs this kind of Expression.
Can anyone direct me to some links how to get this kind of Expression?
var entity = new Entity;
var propertyInfo = entity.GetType().GetProperty("OneProperty");
var expr = GetExpression(propertyInfo);
// could be:
// Expression<Func<string>> or Expression<Func<int>>
edited;
If you need a property accessor for a property name given as string, you must also pass it the the object as parameter. Therefore you need an
Expression<Func<Entity, PropertyType>>
This function creates such an expression:
private static LambdaExpression CreatePropertyGetterExpression(Type entityType,
string propertyName)
{
PropertyInfo property = entityType.GetProperty(propertyName);
var parameter = Expression.Parameter(entityType, "e");
var body = Expression.MakeMemberAccess(parameter, property);
return Expression.Lambda(body, parameter);
}
This overload allows you to pass the entity type as generic parameter:
private static LambdaExpression CreatePropertyGetterExpression<TEntity>(string propertyName)
{
return CreatePropertyGetterExpression(typeof(TEntity), propertyName);
}
This test
var expr = CreatePropertyGetterExpression<Entity>("OneProperty");
Console.WriteLine(expr);
Console.WriteLine(expr.GetType());
prints (assuming OneProperty to be of type int):
e => e.OneProperty
System.Linq.Expressions.Expression`1[System.Func`2[MyProject.Entity, System.Int32]]
You can get an Expression<Func<PropertyType>> if you capture a variable like this:
Entity e = new Entity { PropertyOne = 42 };
Expression<Func<int>> expr = () => e.PropertyOne;
But how do you want to capture a variable if you are constructing an expression dynamically? This is not possible.
I found this HtmlHelperValidationExtensions.ValidationMessageFor Method doing what you need:
public static IHtmlContent ValidationMessageFor<TModel,TResult> (
this IHtmlHelper<TModel> htmlHelper,
Expression<Func<TModel,TResult>> expression,
string message,
object htmlAttributes);
You can pass it the Expression<Func<TModel,TResult>> expression from above! However, you must know the result type in advance. Otherwise you will have to use reflection to call it.
I found a solution while trying to bind blazor inputs dynamically:
Entity e = new Entity { PropertyOne = 42 };
Expression<Func<int>> expr = () => e.PropertyOne;
to get the expression for this statement you should use a constant expression:
var e = new Entity() { PropertyOne = 42 };
var property = typeof(Entity).GetProperty("PropertyOne");
var entityType = typeof(Entity);
var parameter = Expression.Constant(e);
var body = Expression.MakeMemberAccess(parameter, property);
var resultToReturn = Expression.Lambda<func<TValue>>(body);
best regards.
Related
I would like to create a function to retrieve a List of a given property name's Type. But i dont know yet how to create a working lambda selector.
public IList<object> GetDistinctListOfProperty(string propertyName)
{
var propInfoByName = typeof(T).GetProperty(propertyName);
if (propInfoByName == null) return new List<object>();
using (var db = new ApplicationDbContext())
{
var lambda = //TODO
return db.Set<T>().Select(lambda).Distinct().ToList();
}
}
You can use this method to generate lambda
public static Expression<Func<T, object>> LambdaGenerator<T>(string propertyName)
{
var arg = Expression.Parameter(typeof(T), "current");
var property = Expression.Property(arg, propertyName);
var conv = Expression.Convert(property, typeof(object));
var exp = Expression.Lambda<Func<T, object>>(conv, new ParameterExpression[] { arg });
return exp;
}
and then use this method to create your expression
var lambda = LambdaGenerator<T>("Your Property Name");
You can use an expression for this:
private IList<Tout> GetDistinctListOfProperty<Ttable, Tout>(Expression<Func<Ttable, Tout>> returnField) where Ttable : class
{
using (var db = new ApplicationDbContext())
{
return db.Set<Ttable>().Select(returnField).Distinct().ToList();
}
}
You need to wrap the Func into an Expression so that entity can translate it into a valid sql. This version allows you to use intellisense when you choose your parameter. You would call it like this:
var result = GetDistinctListOfProperty<YourTableType>(x => x.YourProperty);
This version will work on every table that is known to your ApplicationDbContext
I'm trying to build a dynamic search on nested objects, which will later be sent to EF and SQL Server. So far, I'm able to search on all properties of the first object. Here's a very simplified version:
public class User
{
public string Name { get; set; }
public Address Address { get; set; }
}
public class Address
{
public string City { get; set; }
}
public class MyClass<TEntity> where TEntity : class {
public IQueryable<TEntity> applySearch(IQueryable<TEntity> originalList, string propName, string valueToSearch) {
ParameterExpression parameterExpression = Expression.Parameter(typeof(TEntity), "p");
PropertyInfo propertyInfo = typeof(TEntity).GetProperty(propName);
MemberExpression member = Expression.MakeMemberAccess(parameterExpression, propertyInfo);
lambda = Expression.Lambda<Func<TEntity, bool>>(Expression.Equal(member, Expression.Constant(valueToSearch)), parameterExpression);
return originalList.Where(expression);
}
}
When propName = "Name" everything is fine, but when propName = "Address.City", the propertyInfo is null, and I get this error on the member assignment line:
System.ArgumentNullException: Value cannot be null
I was able to obtain the propertyInfo of the nested property using the solution from this answer:
PropertyInfo propertyInfo = GetPropertyRecursive(typeof(TEntity), propName);
...
private PropertyInfo GetPropertyRecursive(Type baseType, string propertyName)
{
string[] parts = propertyName.Split('.');
return (parts.Length > 1)
? GetPropertyRecursive(baseType.GetProperty(parts[0]).PropertyType, parts.Skip(1).Aggregate((a, i) => a + "." + i))
: baseType.GetProperty(propertyName);
}
But then I get this error on member assignment:
System.ArgumentException: Property 'System.String City' is not defined for type 'User'
This should point to Address instead of User, but I don't know if I'm on right track here, I mean, should I change parameterExpression now?
How can I make a dynamic search on nested objects, so that this can be turned into a lambda expression and later sent to SQL?
After Kobi's advice and a lot of trial and error, I finally got this working. This uses the Universal PredicateBuilder. Here it is:
public class MyClass<TEntity> where TEntity : class
{
public IQueryable<TEntity> ApplySearch(IQueryable<TEntity> originalList, string valueToSearch, string[] columnsToSearch)
{
Expression<Func<TEntity, bool>> expression = null;
foreach (var propName in columnsToSearch)
{
Expression<Func<TEntity, bool>> lambda = null;
ParameterExpression parameterExpression = Expression.Parameter(typeof(TEntity), "p");
string[] nestedProperties = propName.Split('.');
Expression member = parameterExpression;
foreach (string prop in nestedProperties)
{
member = Expression.PropertyOrField(member, prop);
}
var canConvert = CanConvertToType(valueToSearch, member.Type.FullName);
if (canConvert)
{
var value = ConvertToType(valueToSearch, member.Type.FullName);
if (member.Type.Name == "String")
{
ConstantExpression constant = Expression.Constant(value);
MethodInfo mi = typeof(string).GetMethod("StartsWith", new Type[] { typeof(string) });
Expression call = Expression.Call(member, mi, constant);
lambda = Expression.Lambda<Func<TEntity, bool>>(call, parameterExpression);
}
else
{
lambda = Expression.Lambda<Func<TEntity, bool>>(Expression.Equal(member, Expression.Constant(value)), parameterExpression);
}
}
if (lambda != null)
{
if (expression == null)
{
expression = lambda;
}
else
{
expression = expression.Or(lambda);
}
}
}
if (expression != null)
{
return originalList.Where(expression);
}
return originalList;
}
}
private bool CanConvertToType(object value, string type)
{
bool canConvert;
try
{
var cValue = ConvertToType(value, type);
canConvert = true;
}
catch
{
canConvert = false;
}
return canConvert;
}
private dynamic ConvertToType(object value, string type)
{
return Convert.ChangeType(value, Type.GetType(type));
}
Warning in advance - I'm not building the expression, just inspecting its structure.
When I need to dynamically create Expressions, I find it useful to inspect an Expression and copy its structure:
Expression<Func<User, string>> getCity = user => user.Address.City;
Now you can simply debug it, for example in the immediate window (ctrlalti here):
getCity
{user => user.Address.City}
Body: {user.Address.City}
CanReduce: false
DebugView: ".Lambda #Lambda1<System.Func`2[ConsoleApplication1.User,System.String]>(ConsoleApplication1.User $user) {\r\n ($user.Address).City\r\n}"
Name: null
NodeType: Lambda
Parameters: Count = 1
ReturnType: {Name = "String" FullName = "System.String"}
TailCall: false
Here we can see getCity is a Lambda with one parameter. Let's inspect it's body:
getCity.Body
{user.Address.City}
CanReduce: false
DebugView: "($user.Address).City"
Expression: {user.Address}
Member: {System.String City}
NodeType: MemberAccess
Type: {Name = "String" FullName = "System.String"}
getCity.Body is a member access - it accesses the member City of the Expression user.Address. Technically that's a PropertyExpression, which is an internal class so we can't even cast to it, but that's OK.
Finally, let's look at that inner expression:
((MemberExpression)getCity.Body).Expression
{user.Address}
CanReduce: false
DebugView: "$user.Address"
Expression: {user}
Member: {ConsoleApplication1.Address Address}
NodeType: MemberAccess
Type: {Name = "Address" FullName = "ConsoleApplication1.Address"}
That's just user.Address.
Now we can build an identical expression:
var addressProperty = typeof (User).GetProperty("Address");
var cityProperty = typeof(Address).GetProperty("City");
var userParameter = Expression.Parameter(typeof (User), "user");
var getCityFromUserParameter = Expression.Property(Expression.Property(userParameter, addressProperty), cityProperty);
var lambdaGetCity = Expression.Lambda<Func<User, string>>(getCityFromUserParameter, userParameter);
Expression.MakeMemberAccess works too, instead of Expression.Property.
Obviously, you'd need to build your expression in a loop, and more dynamically, but the structure is the same.
It might be worth taking a look at Linqkit's predicate builder...
http://www.albahari.com/nutshell/predicatebuilder.aspx
I'd also take a look at Entity SQL...
https://msdn.microsoft.com/en-us/library/vstudio/bb387145(v=vs.100).aspx
You might be reinventing a wheel with the code you're writing.
Also I should comment, in terms of the SQL Server plan caching, unless you have no other choice I wouldn't dynamically build queries. You're better off creating a single query that handles all your cases that SQL Server can cache a plan for, your queries will run a lot slower if every time they're executed no plan is hit in SQL Server's plan cache.
eg: x=> x.Name = "g"
I have code block like this
public Expression<Func<TEntity, bool>> SearchExpression()
{
var c = new ConstantExpression[_paramList.Count];
var b = new BinaryExpression[_paramList.Count];
BinaryExpression comparisonExpression = null;
var entity = Expression.Parameter(typeof(TEntity));
for (int i = 0; i < _paramList.Count; i++)
{
var value = Convert.ChangeType(_paramList[i].Item2 /*"g"*/, _paramList[i].Item3 /*System.String*/);
c[i] = Expression.Constant(value); //"g"
// PROBLEM IS HERE
b[i] = Expression.Equal(Expression.Property(entity, _paramList[i].Item1 /*Name*/, c[i]);
// PROBLEM IS HERE
}
_paramList.Clear();
comparisonExpression = b.Aggregate(Expression.And);
return Expression.Lambda<Func<TEntity, bool>>(comparisonExpression, entity);
}
works like charm but I need Expression.Like (Like "g" not Equal "g")
Expression.Like(Expression.Property(entity, _paramList[i].Item1), c[i])
but C# expression tree does not support Like method
UPDATE :
I wrote something like this :
Expression.Call(Expression.Property(entity, _paramList[i].Item1),
typeof(String).GetMethod("Contains"), new Expression[] { c[i] });
but I need BinaryExpression not MethodCallExpression
You can make your code work by adding an equals expression over the method call, like so:
b[i] = Expression.Equal(
Expression.Call(Expression.Property(entity, _paramList[i].Item1),
typeof (String).GetMethod("Contains"),
new Expression[] {c[i]}), Expression.Constant(true));
In pseudo code this reads as:
b[i] = entity => entity.someProperty.Contains(c[i]) == true;
Which will return a binary expression for you.
This answer does not consider your array and the 'and' aggregation, but this should be considered as a separate issue.
Consider this class:
class MyEntity { string Name { get; set; } }
We want to query:
select ... from MyEntity where Name like '%query%';
The following method is a general implementation of the above query pattern:
static Expression<Func<TEntity, bool>> Like<TEntity>(string propertyName, string queryText)
{
var parameter = Expression.Parameter(typeof (TEntity), "entity");
var getter = Expression.Property(parameter, propertyName);
//ToString is not supported in Linq-To-Entities, throw an exception if the property is not a string.
if (getter.Type != typeof (string))
throw new ArgumentException("Property must be a string");
//string.Contains with string parameter.
var stringContainsMethod = typeof (string).GetMethod("Contains", new[] {typeof (string)});
var containsCall = Expression.Call(getter, stringContainsMethod,
Expression.Constant(queryText, typeof (string)));
return Expression.Lambda<Func<TEntity, bool>>(containsCall, parameter);
}
If you want to have a pattern of query% or %query you can use string.StartsWith and string.EndsWith instead of Contains.
Also, you can share the parameter across multiple calls if you adjust the signature.
The current implementation throws an exception if the data type of the property is not a string. Look at this answer https://stackoverflow.com/a/3292773/668272 for converting numbers to strings.
I've done this in a scripting language I wrote, which allows you to say things like name like 'bob%'. The trick is that you need to map it to a method call which takes the value and regular expression and call this from within the Expression.
If you take a look at the LikeEvaluator class in my Wire scripting language you'll see how I did it:
static class LikeEvaluator
{
private static readonly MethodInfo ApplyLikeMethodInfo=typeof(LikeEvaluator).GetMethod("ApplyLike");
private static readonly MethodInfo ApplyLikeNoCaseMethodInfo=typeof(LikeEvaluator).GetMethod("ApplyLikeNoCase");
public static Expression Like(CaseMode caseMode, Expression lhs, Expression pattern)
{
Expression x=null;
if(caseMode==CaseMode.Sensitive)
{
x=Expression.Call(ApplyLikeMethodInfo,lhs,pattern);
}
else
{
x=Expression.Call(ApplyLikeNoCaseMethodInfo,lhs,pattern);
}
return x;
}
public static bool ApplyLike(string text, string likePattern)
{
string pattern=PatternToRegex(likePattern);
return Regex.IsMatch(text,pattern,RegexOptions.None);
}
public static bool ApplyLikeNoCase(string text, string likePattern)
{
string pattern=PatternToRegex(likePattern);
return Regex.IsMatch(text,pattern,RegexOptions.IgnoreCase);
}
public static string PatternToRegex(string pattern)
{
pattern=Regex.Escape(pattern);
pattern=pattern.Replace("%",#".*");
pattern=string.Format("^{0}$",pattern);
return pattern;
}
}
i'm starting to explore dynamic expressions, so please help me to solve one issue.
I have an object
public class Categorisation{
string Name{get;set;}
}
public class Client{
public Categorisation Categorisation{get;set;}
}
All I need is to write a dynamic expression and call Categorisation.Name.Equals("A1") from Client object.
x=>x.Categorisation.Name.Equals("A1")
How can I do this using Expressions?
var param = Expression.Parameter(typeof(Client));
var prop = Expression.Property(param, typeof(Client).GetProperty("Categorisation"));
var argument = Expression.Constant("A1");
var method = typeof(string).GetMethod("Equals", new[] { typeof(string) });
var call = Expression.Call(prop, method);
var expr = Expression.Lambda<Func<Client, bool>>(call, param);
Of course this code is wrong and I call method Equals from Categorisation property and not from Name of Categorisation. But how to invoke Name property?
var param = Expression.Parameter(typeof(Client));
var prop = Expression.Property(param, typeof(Client).GetProperty("Categorisation"));
var namePropExpr = Expression.Property(prop, "Name");
var argument = Expression.Constant("A1");
var method = typeof(string).GetMethod("Equals", new[] { typeof(string) });
var call = Expression.Call(namePropExpr, method, argument);
var expr = Expression.Lambda<Func<Client, bool>>(call, param);
Given an expression that points to a property in an object graph, I want to retrieve the instance that owns the property withing that graph. I've been struggling. The current code just gives me System.InvalidOperationException : Lambda Parameter not in scope
[Test]
public void TestExpression()
{
var person = new Person {Address = {Street = "Bank Street"}, Name = "Joe"};
Expression<Func<Person, object>> exp = p => p.Address.Street;
Assert.AreEqual("Bank Street", exp.Compile().Invoke(person));
var owner = GetPropertyOwner(person, exp);
Assert.AreEqual(owner, person.Address);
}
private object GetPropertyOwner<T>(T root, Expression<Func<T, object>> exp)
{
if (exp.Body is MemberExpression)
{
var member = exp.Body as MemberExpression;
if (member.Expression is MemberExpression)
{
var parentMember = member.Expression as MemberExpression;
//parent member will be {p.Address}
//Now I'm trying to reconstruct an expression that I can combile
var parameterExpression = System.Linq.Expressions.Expression.Parameter(typeof(T), "p");
var lambdaExpression = System.Linq.Expressions.Expression.Lambda(parentMember, parameterExpression);
//Next Linethrows System.InvalidOperationException : Lambda Parameter not in scope
var found = lambdaExpression.Compile().DynamicInvoke(person);
return found;
}
}
return new object();
}
ParameterExpression objects are compared by reference equality, not by name, so the "p" parameter that lambdaExpression takes is not the same as the one used in parentMember. You will need to use the same ParameterExpression instance when you build a new lambda. Try:
var parameterExpression = exp.Parameters[0];