I have a main method that creates a basis search criteria for a given entity. In this method i consequently check for default values before applying it to the query.
E.g.
if (!string.IsNullOrEmpty(value))
qry = qry.Where(x => x.PropA.Contains(value));
if (!string.IsNullOrEmpty(anotherValue))
qry = qry.Where(x => x.PropB.Contains(anotherValue));
However, I would like to refactor this and use a helper method instead, but since my knowledge and experience with Expressions are somewhat limited I'm having difficulty completing the task.
I have this boiler code which I believe illustrates what I'm trying to accomplish:
IQueryable<T> Test<T, TV>(IQueryable<T> qry, Expression<Func<T, TV>> prop, TV value)
{
if (EqualityComparer<TV>.Default.Equals(value, default(TV)))
return qry;
var right = Expression.Constant(value);
var body = Expression.Equal(prop, right);
var lambda = Expression.Lambda<Func<T, bool>>(body);
return qry.Where(lambda);
}
Which should enable me to make calls like this:
qry = Test(qry, x=>PropA, value);
qry = Test(qry, x=>PropB, anotherValue);
The problem however is that the body variable results in a BinaryExpression and I'm totally ignorant in how to proceed from here.
You have to transform the method into an Expression and then include it as the body of the lambda.
So, starting from your boiler code, after the above changes it should look like
IQueryable<T> Test<T, TV>(IQueryable<T> qry, Expression<Func<T, TV>> prop, string propertyValue)
{
MethodInfo method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
var someValue = Expression.Constant(propertyValue, typeof(string));
var body = Expression.Call(prop, method, someValue); // pseudocode, to be refined below
var lambda = Expression.Lambda<Func<T, bool>>(body);
return qry.Where(lambda);
}
Now let me rephrase it using a string accessor
static IQueryable<T> Test<T>(IQueryable<T> qry, string propertyName, string propertyValue)
{
var parameterExp = Expression.Parameter(typeof(T), "type");
var propertyExp = Expression.Property(parameterExp, propertyName);
MethodInfo method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
var someValue = Expression.Constant(propertyValue, typeof(string));
var containsMethodExp = Expression.Call(propertyExp, method, someValue);
var lambda = Expression.Lambda<Func<T, bool>>(containsMethodExp, parameterExp);
return qry.Where(lambda);
}
Finally a trivial usage example
class MyClass
{
public string Myname { get; set; }
}
static void Main(string[] args)
{
var check = new MyClass() { Myname = "11 aa 22" };
var check2 = new MyClass() { Myname = "11 bb 22" };
var x = new List<MyClass>();
x.Add(check);
x.Add(check2);
var q = x.AsQueryable();
var qry = Test(q, "Myname", "bb");
}
Well, if you prefer a property selector, the helper will become
static IQueryable<T> Test<T>(IQueryable<T> qry, Expression<Func<T, string>> selector, string propertyValue)
{
var parameterExp = Expression.Parameter(typeof(T), "type");
var memberExpression = (MemberExpression)selector.Body;
var parameterTProperty = (PropertyInfo)memberExpression.Member;
var propertyExp = Expression.Property(parameterExp, parameterTProperty);
MethodInfo method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
var someValue = Expression.Constant(propertyValue, typeof(string));
var containsMethodExp = Expression.Call(propertyExp, method, someValue);
var lambda = Expression.Lambda<Func<T, bool>>(containsMethodExp, parameterExp);
return qry.Where(lambda);
}
used as
var qry = Test(q, z => z.Myname , "bb");
Related
I have the following class and extension method to invoke String.Contains method. How can I change it to be case insensitive ? Something like in Expression tree for String.IndexOf method but I don't have an ideas so far how to adjust that code into my code. Any help ?
public class testItem
{
public string SomeProperty { get; set; }
}
public static IQueryable<testItem> PropertyContainsNEW<testItem>(this IQueryable<testItem> source,
Expression<Func<testItem, string>> selector,
string value)
{
ParameterExpression parameter = Expression.Parameter(typeof(testItem), "x");
Expression property = Expression.Property(parameter, ((MemberExpression)selector.Body).Member.Name);
var search = Expression.Constant(value, typeof(string));
MethodInfo method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
var containsMethodExp = Expression.Call(property, method, search);
var predicate = Expression.Lambda<Func<testItem, bool>>(containsMethodExp, parameter);
return source.Where(predicate);
}
In order to use the StringComparison parameter, you need to correctly identify that method.
Is this what you need?:
public static IQueryable<testItem> PropertyContainsNEW(this IQueryable<testItem> source,
Expression<Func<testItem, string>> selector,
string value)
{
var parameter = Expression.Parameter(typeof(testItem), "x");
var property = Expression.Property(parameter, ((MemberExpression)selector.Body).Member.Name);
var search = Expression.Constant(value, typeof(string));
var parms = new Expression[] { search,
Expression.Constant(StringComparison.OrdinalIgnoreCase) };
var method = typeof(string).GetMethod("Contains", new[] { typeof(string), typeof(StringComparison) });
var containsMethodExp = Expression.Call(property, method, parms);
var predicate = Expression.Lambda<Func<testItem, bool>>(containsMethodExp, parameter);
return source.Where(predicate);
}
If I have a dynamically created ParameterExpression:
class Product
{
public string Name { get; set; }
}
var propertyName = "Name";
var propertyType = typeof(Product).GetProperty(propertyName).PropertyType;
var parameterExpression = Expression.Parameter(propertyType , propertyName);
How can I covert it into a Func<Product, TPropertyType>?
I specifically want to pass this into the Where or OrderBy linq methods used by entity framework.
I'm also open to other suggestions not using Expressions, but I highly doubt it's possible.
Edit 1: Removed the where use case as Where and OrderBy will have different implementations Removed in an attempt to narrow the scope of the question.
Here is an example with generating expressions for OrderBy and Where. As Johnathon Sullinger said in comments, you must know type of property you ordering by at compile time, because it is mentioned in signature of OrderBY. However you don't have to know it for Where:
class Product
{
public string Name { get; set; }
}
static void Main(string[] args)
{
var products = new List<Product> {
new Product { Name = "ZZZ"},
new Product { Name = "AAA"}
};
var propertyName = "Name";
var ordered = products.AsQueryable().OrderBy(GetOrderExpression<string>(propertyName));
Console.WriteLine(ordered.ElementAt(0).Name);
Console.WriteLine(ordered.ElementAt(1).Name);
var filtered = products.AsQueryable().Where(GetWhereExpression(propertyName, "AAA"));
Console.WriteLine(filtered.Count());
Console.WriteLine(filtered.ElementAt(0).Name);
Console.ReadKey();
}
static Expression<Func<Product, TKey>> GetOrderExpression<TKey>(string propertyName)
{
var prm = Expression.Parameter(typeof(Product), "p");
var prop = Expression.Property(prm, typeof(Product), propertyName);
var lambda = Expression.Lambda<Func<Product, TKey>>(prop, "p", new[] { prm });
return lambda;
}
static Expression<Func<Product, bool>> GetWhereExpression(string propertyName, object value)
{
var prm = Expression.Parameter(typeof(Product), "p");
var prop = Expression.Property(prm, typeof(Product), propertyName);
var equal = Expression.Equal(prop, Expression.Constant(value));
var lambda = Expression.Lambda<Func<Product, bool>>(equal, "p", new[] { prm });
return lambda;
}
Hope it helps.
I have a problem converting simple linq query to Lambda Expression.
My queries look like this:
int[] array = List<int> array2 = sql.OfType<Table1>().Select(x=>x.ID).Take(10).ToList();
var result = sql.OfType<Table1>().Where(x => array.Contains(x.ID)).Take(10).ToList();
and the final result should be:
static void DynamicSQLQuery<T>(IQueryable<T> sql, string fieldName)
{
List<int> array = sql.OfType<T>().Select(SelectExpression<T>(fieldName)).Take(10).ToList();
var result = sql.OfType<T>().Where(InExpression<T>(fieldName, array)).Take(10).ToList();
}
Class
public class Table1
{
public int ID { get; set; }
public string Name { get; set; }
}
I already converted the first lambda:
public static Expression<Func<T, int>> SelectExpression<T>(string fieldName)
{
ParameterExpression param = Expression.Parameter(typeof(T), "x");
MemberExpression selection = Expression.PropertyOrField(param, fieldName);
var lambdaExp = Expression.Lambda<Func<T, int>>(selection, param);
return lambdaExp;
}
But stuck on the second one:
static Expression<Func<T, bool>> InExpression<T>(string propertyName,IEnumerable<int> array)
{
System.Reflection.MethodInfo containsMethod = typeof(IEnumerable<int>).GetMethod("Contains");
ParameterExpression param = Expression.Parameter(typeof(T), "x");
MemberExpression member = Expression.PropertyOrField(param, propertyName);//x.{property}
var constant = Expression.Constant(3);
var body = Expression.GreaterThanOrEqual(member, constant); //x.{property} >= 3 but I need array.Contains(x.{property})
var finalExpression = Expression.Lambda<Func<T, bool>>(body, param);
return finalExpression;
}
Can anyone help me to make the lambda expression x=>array2.Contains(x.ID) in InExpression method?
Also, I'll be very grateful for some link to article/tutorial about creating these type of expressions.
Probably something like:
static Expression<Func<T, bool>> InExpression<T>(
string propertyName, IEnumerable<int> array)
{
var p = Expression.Parameter(typeof(T), "x");
var contains = typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public)
.Single(x => x.Name == "Contains" && x.GetParameters().Length == 2)
.MakeGenericMethod(typeof(int));
var property = Expression.PropertyOrField(p, propertyName);
var body = Expression.Call(contains, Expression.Constant(array), property);
return Expression.Lambda<Func<T, bool>>(body, p);
}
The trick here is to start off with something simple that compiles; for example:
using System.Linq;
using System;
using System.Linq.Expressions;
using System.Collections.Generic;
public class C {
static Expression<Func<Foo, bool>> InExpression<T>(
string propertyName,IEnumerable<int> array)
{
return x => array.Contains(x.Id);
}
}
class Foo {
public int Id {get;set;}
}
Now either compile it and look in ildasm/reflector, or (and much simpler): run that through https://sharplab.io specifying C# as the output, like this
This shows you the code that the compiler generated:
private static Expression<Func<Foo, bool>> InExpression<T>(string propertyName, IEnumerable<int> array)
{
C.<>c__DisplayClass0_0<T> <>c__DisplayClass0_ = new C.<>c__DisplayClass0_0<T>();
<>c__DisplayClass0_.array = array;
ParameterExpression parameterExpression = Expression.Parameter(typeof(Foo), "x");
Expression arg_77_0 = null;
MethodInfo arg_77_1 = methodof(IEnumerable<!!0>.Contains(!!0));
Expression[] expr_38 = new Expression[2];
expr_38[0] = Expression.Field(Expression.Constant(<>c__DisplayClass0_, typeof(C.<>c__DisplayClass0_0<T>)), fieldof(C.<>c__DisplayClass0_0<T>.array));
Expression[] expr_5F = expr_38;
expr_5F[1] = Expression.Property(parameterExpression, methodof(Foo.get_Id()));
Expression arg_86_0 = Expression.Call(arg_77_0, arg_77_1, expr_5F);
ParameterExpression[] expr_82 = new ParameterExpression[1];
expr_82[0] = parameterExpression;
return Expression.Lambda<Func<Foo, bool>>(arg_86_0, expr_82);
}
Note that there's a few things in here we need to fixup, but it allows us to see what it is doing - things like memberof and fieldof don't actually exist, for example, so we need to look them up via reflection.
A humanized version of the above:
private static Expression<Func<Foo, bool>> InExpression<T>(string propertyName, IEnumerable<int> array)
{
ExpressionState state = new ExpressionState();
state.array = array;
ParameterExpression parameterExpression = Expression.Parameter(typeof(Foo), "x");
MethodInfo contains = typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public)
.Single(x => x.Name == nameof(Enumerable.Contains) && x.GetParameters().Length == 2)
.MakeGenericMethod(typeof(int));
Expression[] callArgs = new Expression[2];
callArgs[0] = Expression.Field(Expression.Constant(state, typeof(ExpressionState)), nameof(ExpressionState.array));
callArgs[1] = Expression.Property(parameterExpression, propertyName);
Expression body = Expression.Call(null, contains, callArgs);
ParameterExpression[] parameters = new ParameterExpression[1];
parameters[0] = parameterExpression;
return Expression.Lambda<Func<Foo, bool>>(body, parameters);
}
with:
class ExpressionState
{
public IEnumerable<int> array;
}
I need to extend the Where method for IQueryable to something like this:
.WhereEx("SomeProperty", "==", "value")
I dont know if this is even possible, but i found this in SO : Unable to sort with property name in LINQ OrderBy
I tried this answer and it seems quite interesting (Ziad's answer):
using System.Linq;
using System.Linq.Expressions;
using System;
namespace SomeNameSpace
{
public static class SomeExtensionClass
{
public static IQueryable<T> OrderByField<T>(this IQueryable<T> q, string SortField, bool Ascending)
{
var param = Expression.Parameter(typeof(T), "p");
var prop = Expression.Property(param, SortField);
var exp = Expression.Lambda(prop, param);
string method = Ascending ? "OrderBy" : "OrderByDescending";
Type[] types = new Type[] { q.ElementType, exp.Body.Type };
var mce = Expression.Call(typeof(Queryable), method, types, q.Expression, exp);
return q.Provider.CreateQuery<T>(mce);
}
}
}
Is It possible to do the same with the Where method ?
Thanks.
Update :
I can now set an expression to pass to the Call method, but i get the following exception: "No generic method 'Where' on type 'System.Linq.Queryable' is compatible with the supplied type arguments and arguments. No type arguments should be provided if the method is non-generic."
here is my code:
public static IQueryable<T> WhereEx<T>(this IQueryable<T> q, string Field, string Operator, string Value)
{
var param = Expression.Parameter(typeof(T), "p");
var prop = Expression.Property(param, Field);
var val = Expression.Constant(Value);
var body = Expression.Equal(prop, val);
var exp = Expression.Lambda<Func<T, bool>>(body, param);
Type[] types = new Type[] { q.ElementType, typeof(bool) };
var mce = Expression.Call(
typeof(Queryable),
"Where",
types,
exp
);
return q.Provider.CreateQuery<T>(mce);
}
Notice that i dont use the Operator argument yet, instead i use Equal for debugging purpose.
Can someone help me with this please ?
I did another post with a simplified question. the link is Here
public static IQueryable<T> WhereEx<T>(this IQueryable<T> q, string Field, string Operator, string Value)
{
var param = Expression.Parameter(typeof(T), "p");
var prop = Expression.Property(param, Field);
var val = Expression.Constant(Value);
var body = Expression.Equal(prop, val);
var exp = Expression.Lambda<Func<T, bool>>(body, param);
return System.Linq.Queryable.Where(q, exp);
}
I am just getting started with expression trees so I hope this makes sense. I am trying to create an expression tree to represent:
t => t.SomeProperty.Contains("stringValue");
So far I have got:
private static Expression.Lambda<Func<string, bool>> GetContainsExpression<T>(string propertyName, string propertyValue)
{
var parameterExp = Expression.Parameter(typeof(T), "type");
var propertyExp = Expression.Property(parameter, propertyName);
var containsMethodExp = Expression.*SomeMemberReferenceFunction*("Contains", propertyExp) //this is where I got lost, obviously :)
...
return Expression.Lambda<Func<string, bool>>(containsMethodExp, parameterExp); //then something like this
}
I just don't know how to reference the String.Contains() method.
Help appreciated.
Something like:
class Foo
{
public string Bar { get; set; }
}
static void Main()
{
var lambda = GetExpression<Foo>("Bar", "abc");
Foo foo = new Foo { Bar = "aabca" };
bool test = lambda.Compile()(foo);
}
static Expression<Func<T, bool>> GetExpression<T>(string propertyName, string propertyValue)
{
var parameterExp = Expression.Parameter(typeof(T), "type");
var propertyExp = Expression.Property(parameterExp, propertyName);
MethodInfo method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
var someValue = Expression.Constant(propertyValue, typeof(string));
var containsMethodExp = Expression.Call(propertyExp, method, someValue);
return Expression.Lambda<Func<T, bool>>(containsMethodExp, parameterExp);
}
You might find this helpful.
To perform a search like:
ef.Entities.Where(entity => arr.Contains(entity.Name)).ToArray();
which the trace string will be:
SELECT .... From Entities ... Where Name In ("abc", "def", "qaz")
I use the method I created below:
ef.Entities.Where(ContainsPredicate<Entity, string>(arr, "Name")).ToArray();
public Expression<Func<TEntity, bool>> ContainsPredicate<TEntity, T>(T[] arr, string fieldname) where TEntity : class {
ParameterExpression entity = Expression.Parameter(typeof(TEntity), "entity");
MemberExpression member = Expression.Property(entity, fieldname);
var containsMethods = typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public)
.Where(m => m.Name == "Contains");
MethodInfo method = null;
foreach (var m in containsMethods) {
if (m.GetParameters().Count() == 2) {
method = m;
break;
}
}
method = method.MakeGenericMethod(member.Type);
var exprContains = Expression.Call(method, new Expression[] { Expression.Constant(arr), member });
return Expression.Lambda<Func<TEntity, bool>>(exprContains, entity);
}
How about this:
Expression<Func<string, string, bool>> expFunc = (name, value) => name.Contains(value);
In the client code:
bool result = expFunc.Compile()("FooBar", "Foo"); //result true
result = expFunc.Compile()("FooBar", "Boo"); //result false
Here is how to create an expression tree of string.Contains.
var method = typeof(Enumerable)
.GetRuntimeMethods()
.Single(m => m.Name == nameof(Enumerable.Contains) && m.GetParameters().Length == 2);
var containsMethod = method.MakeGenericMethod(typeof(string));
var doesContain = Expression
.Call(containsMethod, Expression.Constant(criteria.ToArray()),
Expression.Property(p, "MyParam"));
Actual usage at https://raw.githubusercontent.com/xavierjohn/Its.Cqrs/e44797ef6f47424a1b145d69889bf940b5581eb8/Domain.Sql/CatchupEventFilter.cs