How extract list values from expression - c#

I need to read the values from one array, that is provided as an expression.
I have this method
public ExpressionAnalizer<TModel> where TModel : class
{
public string BuildExpression(Expression<Func<TModel, bool>> expression)
{
if (expression?.Body is MethodCallExpression)
return BuildMethodCallExpression(expression);
throw new ArgumentException($"The expression '{expression?.Body}' is unsupported");
}
public string BuildMethodCallExpression(Expression<Func<TModel, bool>> expression)
{
var body = expression.Body as MethodCallExpression;
//TODO: I can't find a property that has the values of IEnumerable
return null;
}
}
And is called like this
//PersonModel is a plain class with some properties.
var analizer= new ExpressionAnalizer<PersonModel>("");
var names = new List<string>() {"n1", "n2", "n3"};
//I want to get "Email contains ('n1', 'n2', 'n3')". I can read Email property, the call method name "Contains", but not itself values
var response = analizer.BuildExpression(x => names.Contains(x.Email));
Any idea? I was thinking to compile the expression, but I get stuck on "Closure" class, because System.Runtime.CompilerServices.Closure is private and I cannot use it.
By the way, I'm using .NET Core 1.0
EDIT
I need to get a string like
email contains ('n1','n2','n3')
And the input parameter need to be always an Expression
Expression<Func<TModel, bool>>
This is because internally, this method can receive any expression, like
x => x.SomeProperty == "n1"
Internally, I handle the expression.Body type and I have an implementation for different use cases.
The case I cannot figure how can I implement is when the input expression is
var someList = new List<string>() { "string1", "anotherString", "finalString" };
someObject.SomeProperty<SomeTModel>(x => someList.Contains(x.SomeProperty))

Here is the full implementation of your ExpressionAnalizer class which will give you the desired output.
public class ExpressionAnalizer<TModel> where TModel : class
{
public string BuildExpression(Expression<Func<TModel, bool>> expression)
{
if (expression?.Body is MethodCallExpression)
return BuildMethodCallExpression(expression);
throw new ArgumentException($"The expression '{expression?.Body}' is unsupported");
}
public string BuildMethodCallExpression(Expression<Func<TModel, bool>> expression)
{
var body = expression.Body as MethodCallExpression;
//Get Method Name
string method = body.Method.Name;
//Get List of String Values
var methodExpression = ResolveMemberExpression(body.Object);
var listValues = ReadValue(methodExpression);
var vString = string.Format("'{0}'", string.Join("' , '", (listValues as List<string>)));
//Read Propery Name
var argExpression = ResolveMemberExpression(body.Arguments[0]);
var propertyName = argExpression.Member.Name;
return $"{propertyName} {method} ({vString})";
}
public MemberExpression ResolveMemberExpression(Expression expression)
{
if (expression is MemberExpression) return (MemberExpression)expression;
if (expression is UnaryExpression) return (MemberExpression)((UnaryExpression)expression).Operand;
throw new NotSupportedException(expression.ToString());
}
private object ReadValue(MemberExpression expression)
{
if (expression.Expression is ConstantExpression)
{
return (((ConstantExpression)expression.Expression).Value)
.GetType()
.GetField(expression.Member.Name)
.GetValue(((ConstantExpression)expression.Expression).Value);
}
if (expression.Expression is MemberExpression) return ReadValue((MemberExpression)expression.Expression);
throw new NotSupportedException(expression.ToString());
}
}
Usage:
var analizer= new ExpressionAnalizer<PersonModel>();
var names = new List<string>() {"n1", "n2", "n3"};
var person = new PersonModel{ Email = "Email 1"};
var response = analizer.BuildExpression( x => names.Contains(x.Email));
Response:
Email Contains ('n1' , 'n2' , 'n3')

Related

How do I use and ExpressionTree to access the name of the property in a lambda expression

I'm using the following approach to get the name of a property specified in a lambda by inspecting the resulting ExpressionTree:
var name =GetPropertyName<Entity1, Entity2>(x => x.Entity2);
public string GetPropertyName<T, P>(Expression<Func<T, P>> expression)
{
var memberExpression = expression.Body as MemberExpression;
var propertyInfo = memberExpression.Member as PropertyInfo;
return propertyInfo.Name;
}
//name is "Entity2"
Given the following expression i want to extract the names "Entity2s", "Entity3s" and "Entity4s"
var names = GetPropertyNames<Entity1, IEnumerable<Entity4>>(x => x.Entity2s.SelectMany(x => x.Entity3s).SelectMany(x => x.Entity4s));
With an ExpressionVisitor it is a simple as
public static List<string> GetPropertyNames<T, P>(Expression<Func<T, P>> expression)
{
var visitor = new Visitor();
visitor.Visit(expression);
return visitor.Names;
}
class Visitor : ExpressionVisitor
{
public Visitor()
{
Names = new List<string>();
}
public List<string> Names { get; }
protected override Expression VisitMember(MemberExpression node)
{
Names.Add(node.Member.Name);
return base.VisitMember(node);
}
}
Here is a method for extracting the member names from exactly the Expression tree you provided. A more generic method should probably be based on an ExpressionVisitor subclass.
public static List<string> GetPropertyNames<T, P>(Expression<Func<T, P>> fe) {
var ans = new List<string>();
var callExpr1 = fe.Body as MethodCallExpression;
var callExpr2 = callExpr1.Arguments[0] as MethodCallExpression;
var e2MemberExpr1 = callExpr2.Arguments[0] as MemberExpression;
ans.Add(e2MemberExpr1.Member.Name);
var e2MemberExpr2 = (callExpr2.Arguments[1] as LambdaExpression).Body as MemberExpression;
ans.Add(e2MemberExpr2.Member.Name);
var e1MemberExpr1 = (callExpr1.Arguments[1] as LambdaExpression).Body as MemberExpression;
ans.Add(e1MemberExpr1.Member.Name);
return ans;
}

Selector expression dynamic on IQueryable

I would like to generate dynamically a selector expression from some lambdas.
I want to declare a list of lambda expression like this
Expression<Func<MyEntity, object>> select1 = myentity => myentity.Label;
Expression<Func<MyEntity, object>> select2 = myentity => myentity.User.Name;
Expression<Func<MyEntity, object>> select3 = myentity => myentity.Fields.Where(1 == 1).Select(f => f.Code).FirstOrDefault();
And let's say i have a class :
class MyClass
{
public string Label { get; set; }
public string UserName { get; set; }
public string CodeField { get; set; }
}
I want to compose dynamically the selector expression using the declared expressions.
The goal is that I want to choose the data to recover, not all together.
Expression.Lambda<Func<MyEntity, MyClass>> selectExpression = ??
req.Select(selectExpression).ToList();
I want to generate a selector expression to have something like this
return req.Select(myentity => new MyClass {
Label = myentity.Label,
UserName = myentity.User.Name,
CodeField = myentity.Fields.Where(1 == 1).Select(f => f.Code).FirstOrDefault()
}).ToList();
Can i do this?
I succeeded for example like this but it's not the way that i'm look for
var entityT = Expression.Parameter(typeof(MyEntity), "entity");
var propertyA = Expression.Property(entityT, typeof(MyEntity).GetProperty("Label"));
var propertyB = Expression.Property(entityT, typeof(MyEntity).GetProperty("User"));
var propertyC = Expression.Property(propertyB, typeof(UserEntity).GetProperty("Name"));
var binding = Expression.MemberInit(Expression.New(typeof(MyClass)),
new[]
{
Expression.Bind(typeof(MyClass).GetProperty("Label"), propertyA),
Expression.Bind(typeof(MyClass).GetProperty("UserName"), propertyC),
});
var selectExpression = Expression.Lambda<Func<Benef, MyClass>>(binding, entityT);
return req.Select(selectExpression).ToList();
In the same idea, I was tempted to do this, it compiles but does'nt work:
var binding = Expression.MemberInit(Expression.New(typeof(T)),
new[]
{
Expression.Bind(typeof(T).GetProperty("Label"), select1.Body),
Expression.Bind(typeof(T).GetProperty("UserName"), select2.Body),
});
I have this error :
"variable 'myentity' of type 'MyEntity' referenced from scope '', but it is not defined"
Thank you for your answers.
Basically you need to extract expressions from each lambda and connect it with parameter from MyClass.
Something like: Expression.Bind(typeof(MyClass).GetParameter("x"), selectX.Body).
The only difficulty is that all selectX.Body needs to point to the same paramter, so each body expression needs to be adjusted.
Here is sample code:
class Program
{
static void Main(string[] args)
{
var mapped = entities
.Select(MakeExpression<MyEntity, MyClass>(select1, select2, select3))
.ToList();
}
// Create lambda expression
private static Expression<Func<TEntity, TModel>> MakeExpression<TEntity, TModel>(params Expression<Func<TEntity, object>>[] select)
{
var param = Expression.Parameter(typeof(TEntity));
// Map expressions [select1, ..., selectN] with properties
// For keeping things simple I map nth expression with nth property
// eg. select1 with first property from MyClass
var body = Expression.MemberInit(
Expression.New(typeof(TModel)),
typeof(TModel)
.GetProperties()
.Select((p, i) => Expression.Bind(p, MakeParam(param, select[i])))
.ToArray()
);
return Expression.Lambda<Func<TEntity, TModel>>(body, param);
}
// Replace parameter from given expression with param
// All expressions must have same MyEntity parameter
private static Expression MakeParam<TEntity>(ParameterExpression param, Expression<Func<TEntity, object>> select)
{
Expression body = select.Body;
return new ParamVisitor<TEntity>(param).Visit(body);
}
}
class ParamVisitor<TEntity> : ExpressionVisitor
{
private readonly ParameterExpression _param;
public ParamVisitor(ParameterExpression param)
{
this._param = param;
}
protected override Expression VisitParameter(ParameterExpression node)
{
if (node.Type == typeof(TEntity))
{
return this._param;
}
return base.VisitParameter(node);
}
}

MemberExpression, build Expression.Property from class

Below expression compares property NAME with the value PETER.
ParameterExpression pe = Expression.Parameter(typeof(T), "x");
MemberExpression member = Expression.Property(pe, "name");
ConstantExpression value = Expression.Constant("Peter");
exp = Expression.Equal(member, value);
What if the property is a class:
public class Address
{
public string Name {get; set;}
}
Then the expression would look something similar to this:
MemberExpression member = Expression.Property(pe, "Address.Name");
ConstantExpression value = Expression.Constant("Peter");
exp = Expression.Equal(member, value);
This would fail because the member type doesn't match the value type.
So, the question is: How to build an Expression that would work using the above class sample ??
I use this expression in a NHibernate.Linq query:
var q = from f in data //of type IQueryable<T>
select f;
if (filter != null) //filter of type Expression<Func<T, bool>>
q = q.Where(filter);
etc....
Thank you.
UPDATE by Peter:
Based on the code from xanatos (next post) I have created the following test to understand how it works. Its not very different from what xanatos do, but at first I could not get it to work, so I decided to write it allover in one simple test, and that did it. With thanks to xanatos:
[Test]
public void FilterWithDeepProperties()
{
//Arrange
IGenericGridRepository repository = ObjectFactory.GetInstance<IGenericGridRepository>();
FilterDescriptor filter = new FilterDescriptor("AgreementId.Name", FilterOperator.IsEqualTo, "a name");
string[] properties = filter.Member.Split('.');
ParameterExpression pe = Expression.Parameter(typeof(SampleDomain), "x");
//Act
Expression lastMember = pe;
for (int i = 0; i < properties.Length; i++)
{
MemberExpression member = Expression.Property(lastMember, properties[i]);
lastMember = member;
}
ConstantExpression valueExpression = Expression.Constant(filter.Value);
Expression equalityExpression = Expression.Equal(lastMember, valueExpression);
Expression<Func<SampleDomain, bool>> where = Expression.Lambda<Func<SampleDomain, bool>>(equalityExpression, pe);
var result = repository.GetObjects<SampleDomain>(filter: where);
//Assert
result.Count().Should().BeGreaterThan(0, "because there are many schedule items equals to " + filter.Value);
}
You probably want something like:
public static Expression<Func<TSource, bool>> GetEquality<TSource>(object value, params string[] properties)
{
ParameterExpression pe = Expression.Parameter(typeof(TSource), "source");
Expression lastMember = pe;
for (int i = 0; i < properties.Length; i++)
{
MemberExpression member = Expression.Property(lastMember, properties[i]);
lastMember = member;
}
Expression valueExpression = Expression.Constant(value);
Expression equalityExpression = Expression.Equal(lastMember, valueExpression);
Expression<Func<TSource, bool>> lambda = Expression.Lambda<Func<TSource, bool>>(equalityExpression, pe);
return lambda;
}
Use it like:
Expression exp = GetEquality<Person>("Foo", "Address", "Name");
Where Foo is your Peter (so the value that must be compared), while Address and Name are the names of the "chain" of properties. For example I'm using
public class Person
{
public Address Address { get; set; }
}
public class Address
{
public string Name { get; set; }
}
So the expression generated is
source.Address.Name == "Foo"
If you want to use something like Address.Name, you can use the method like
Expression exp = GetEquality<Person>("Foo", "Address.Name".Split('.'));

Invoking lambda expressions in Expression trees

I have a SelectionCriteria class that I use for building Entity Framework query expressions, based on PredicateBuilder. Within its limits, it's working fine. I'd like to extend it so that it can query whether a field contains a substring. My problem is that I can't see how to build the needed expression object.
My actual class supports and, or, and not, but they aren't relevant to my question. So I've simplified my example code to handle only a single binary operation:
public class SelectionCriteria
{
public SelectionComparison selectionComparison { get; set; }
public string fieldName { get; set; }
public object fieldValue { get; set; }
public Expression<Func<T, bool>> constructSinglePredicate<T>()
{
var type = typeof(T);
if (type.GetProperty(this.fieldName) == null && type.GetField(this.fieldName) == null)
throw new MissingMemberException(type.Name, this.fieldName);
ExpressionType operation;
if (!operationMap.TryGetValue(this.selectionComparison, out operation))
throw new ArgumentOutOfRangeException("selectionComparison", this.selectionComparison, "Invalid filter operation");
var parameter = Expression.Parameter(type);
var member = Expression.PropertyOrField(parameter, this.fieldName);
var value = (this.fieldValue == null) ? Expression.Constant(null) : Expression.Constant(this.fieldValue, this.fieldValue.GetType());
try
{
var converted = (value.Type != member.Type)
? (Expression) Expression.Convert(value, member.Type)
: (Expression) value;
var comparison = Expression.MakeBinary(operation, member, converted);
var lambda = Expression.Lambda<Func<T, bool>>(comparison, parameter);
return lambda;
}
catch (Exception)
{
throw new InvalidOperationException(
String.Format("Cannot convert value \"{0}\" of type \"{1}\" to field \"{2}\" of type \"{3}\"", this.fieldValue,
value.Type, this.fieldName, member.Type));
}
}
private static Dictionary<SelectionComparison, ExpressionType> operationMap =
new Dictionary<SelectionComparison, ExpressionType>
{
{ SelectionComparison.Equal, ExpressionType.Equal },
{ SelectionComparison.GreaterThan, ExpressionType.GreaterThan },
};
}
public enum SelectionComparison
{
Equal,
GreaterThan,
Contains,
};
Usage is simple:
var criteria = new SelectionCriteria
{
selectionComparison = SelectionComparison.GreaterThan,
fieldName = "worktobegindate",
fieldValue = DateTime.Now.AddDays(-2)
};
var predicate = criteria .constructPredicate<job>();
var jobs = myDbContext.jobs.Where(predicate);
So, my problem - I need a method, like my constructSinglePredictate() above, that returns an Expression> and applies a .Contains(
It's trivial to apply Contains() to a string, given a string to compare it to, but I'm having difficulty figuring out how to do the same in an Expression.
Ideas?
As is usual, I was thinking about things incorrectly. I don't need a lambda that calls a method on a string, I need a MethodExpression (here extracted from the methodMap dictionary):
public Expression<Func<T, bool>> constructMethodCallPredicate<T>()
{
var type = typeof(T);
if (type.GetProperty(this.fieldName) == null && type.GetField(this.fieldName) == null)
throw new MissingMemberException(type.Name, this.fieldName);
MethodInfo method;
if (!methodMap.TryGetValue(this.selectionComparison, out method))
throw new ArgumentOutOfRangeException("selectionComparison", this.selectionComparison, "Invalid filter operation");
var parameter = Expression.Parameter(type);
var member = Expression.PropertyOrField(parameter, this.fieldName);
var value = (this.fieldValue == null) ? Expression.Constant(null) : Expression.Constant(this.fieldValue, this.fieldValue.GetType());
try
{
var converted = (value.Type != member.Type)
? (Expression)Expression.Convert(value, member.Type)
: (Expression)value;
var methodExpression = Expression.Call(member, method, converted);
var lambda = Expression.Lambda<Func<T, bool>>(methodExpression, parameter);
return lambda;
}
catch (Exception)
{
throw new InvalidOperationException(
String.Format("Cannot convert value \"{0}\" of type \"{1}\" to field \"{2}\" of type \"{3}\"", this.fieldValue,
value.Type, this.fieldName, member.Type));
}
}
private static readonly Dictionary<SelectionComparison, MethodInfo> methodMap =
new Dictionary<SelectionComparison, MethodInfo>
{
{ SelectionComparison.Contains, typeof(string).GetMethod("Contains", new[] { typeof(string) }) },
{ SelectionComparison.StartsWith, typeof(string).GetMethod("StartsWith", new[] { typeof(string) }) },
{ SelectionComparison.EndsWith, typeof(string).GetMethod("EndsWith", new[] { typeof(string) }) },
};
public enum SelectionComparison
{
Equal,
NotEqual,
LessThan,
LessThanOrEqual,
GreaterThan,
GreaterThanOrEqual,
Contains,
StartsWith,
EndsWith,
};
Getting an actual "Like" comparison to work, using SqlFunctions.PatIndex is a bit more complicated, PatIndex() returns an int, and I need to wrap it in a >0 expression, but it works just fine, as well.

Generic order by parameter

I'm trying to do a generic method that would accept an order by parameter that I could then inspect for it's name and attributes before building the query and sending it to the database.
I was thinking of something like:
public class SearchEngine<T>
{
// Removed other parameters to make it simple
public IEnumerable<T> Search<K>(Func<T, K> orderBy)
{
throw new NotImplementedException();
}
}
I was hoping to consume it later with:
var searchEngine = new SearchEngine<User>();
var result = searchEngine.Search(x => x.Name);
My goal is to get a hold, inside of the search method, of that property so that I could get the name of the property, "Name" in this case, and even after it, use reflection to get its attributes to get other information I have setup.
I know about getting the attributes, what I'm not sure is how can I get the info of the property being passed. I'm sure Linq does this in some way, I just don't know how.
If I tried something like:
var result = searchEngine.Search<PropertyInfo>(x => x.Name);
This wouldnt work since the parameter is returning a string in this case.
Any ideas would be appreciated.
Use expression tree and change Search method parameter type to Expression<Func<T, K>>:
public IEnumerable<T> Search<K>(Expression<Func<T, K>> orderBy)
{
var memberExpression = orderBy.Body as MemberExpression;
if (memberExpression == null)
throw new ArgumentException("orderBy");
var member = memberExpression.Member;
var memberName = member.Name;
return null;
}
It will throw ArgumentException when orderBy is not simple, member expression.
You can still call the method the same way:
var result = searchEngine.Search(x => x.Name);
Change parameter type of Search to:
Expression<Func<T, K>>
and try like this:
public class SearchEngine<T>
{
// Removed other parameters to make it simple
public IEnumerable<T> Search<K>(Expression<Func<T, K>> orderBy)
{
var name = GetMemberName(orderBy.Body);
throw new NotImplementedException();
}
static string GetMemberName(Expression expression)
{
string memberName = null;
var memberExpression = expression as MemberExpression;
if (memberExpression != null)
memberName = memberExpression.Member.Name;
var unaryExpression = expression as UnaryExpression;
if (unaryExpression != null)
memberName = ((MemberExpression) unaryExpression.Operand).Member.Name;
var methodCallExpression = expression as MethodCallExpression;
if (methodCallExpression != null)
memberName = methodCallExpression.Method.Name;
Contract.Assume(memberName != null);
return memberName;
}
}
You can use Dynamic Linq. The syntax would be something like:
string criteria = "Age < 40 and Age > 30";
string sort = "Name";
var result = searchEngine.Where(criteria).OrderBy(sort);

Categories

Resources