How can I convert Linq to Entities Query into expression tree? - c#

Say I have the following query how can I get the base Expression Tree?
MyContext.Items.Select(i=>i.Color);
I want to get the expression tree of this so that I can then use the expression tree to dynamically set what property is being selected (So I can choose Color, Size, Weight, Price etc.)
I think I am close with the following but I keep getting errors:
IQueryable<Item> query = c.Items;
string SelctField = "Color";
ParameterExpression pe = Expression.Parameter(typeof(Item), "i");
Expression SelectProperty = Expression.Property(pe, SelctField);
MethodCallExpression Select = Expression.Call(
typeof(Queryable),
"Select",
new Type[] {query.ElementType},
pe,
Expression.Lambda<Func<Item, string>>(SelectProperty, pe));
var result = query.Provider.CreateQuery<string>(Select);
The above results in
No generic method 'Select' 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
so I tried removing that overload and just keep getting different errors.
I do also get this internal expression text in debug mode, but don't understand how to convert it.
.Call System.Linq.Queryable.Select(
.Call .Constant<System.Data.Entity.Core.Objects.ObjectQuery`1[Inventory_Ordering_And_Reporting.Item]>(System.Data.Entity.Core.Objects.ObjectQuery`1[Inventory_Ordering_And_Reporting.Item]).MergeAs(.Constant<System.Data.Entity.Core.Objects.MergeOption>(AppendOnly))
,
'(.Lambda #Lambda1<System.Func`2[Inventory_Ordering_And_Reporting.Item,System.Nullable`1[System.String]]>))
.Lambda #Lambda1<System.Func`2[Inventory_Ordering_And_Reporting.Item,System.Nullable`1[System.String]]>(Inventory_Ordering_And_Reporting.Item $i)
{
$i.Color
}

Not entirely sure if this will work with DB provider, but hopefully since it's purely expression based.
public class Item
{
public string Blah { get; set; }
public string Foo { get; set; }
}
[TestMethod]
public void Test()
{
string propertyName = "Blah";
System.Linq.Expressions.ParameterExpression arg = System.Linq.Expressions.Expression.Parameter(typeof(Item), "x");
PropertyInfo pi = typeof(Item).GetProperty(propertyName,
BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
System.Linq.Expressions.Expression expr = System.Linq.Expressions.Expression.Property(arg, pi);
System.Linq.Expressions.LambdaExpression lambda = System.Linq.Expressions.Expression.Lambda(expr, arg);
System.Linq.Expressions.Expression<Func<Item, string>> funcExpression = (System.Linq.Expressions.Expression<Func<Item, string>>)lambda;
List<Item> test = new List<Item> { new Item { Blah = "Test", Foo = "Another" } };
IQueryable<Item> query = test.AsQueryable();
var result = query.Select(funcExpression).ToList();
}

You could use reflection:
public static void DoStuff(string fieldName)
{
List<test> ban = new List<test>();
ban.Add(new test() { number = 40, notNumber = "hi" });
ban.Add(new test() { number = 30, notNumber = "bye" });
var result = ban.Select(item => item.GetType().GetField(fieldName).GetValue(item));
foreach (var item in result)
{
Console.WriteLine(item);
}
}
class test
{
public int number;
public string notNumber;
}
This example is for fields the select should probably check to see if the field exists and if not then look for a property.

Related

How to build a lambda expression for a nested ICollection, that can be successfully translated to SQL?

I am trying to build a lambda expression that will do an "ILIKE" search on my models, based on various client-side parameters such as field name. I have something that works pretty well for properties that are not nested. The problem arises when I want to search on a nested ICollection property. The minimum model is at the bottom of the question.
What works
Let's say the client sends that he wants to search for
f = {
"filterdatafield": "name",
"filtervalue": "test"
}
Then this code will build the required expression:
string MyType="Field";
ParameterExpression p=null;
#nullable enable
Type? x = Type.GetType(MyType);
if (x is null)
{
throw new Exception("Cannot find type " + MyType);
}
#nullable disable
p = Expression.Parameter(x);
Expression property = Expression.Property(p, f.filterdatafield);
var functions = Expression.Property(null, typeof(EF).GetProperty(nameof(EF.Functions)));
var likeFunction = typeof(NpgsqlDbFunctionsExtensions).GetMethod(nameof(NpgsqlDbFunctionsExtensions.ILike), new Type[] { functions.Type, typeof(string), typeof(string) });
var pattern = Expression.Constant($"%{f.filtervalue}%", typeof(string));
MethodCallExpression call = Expression.Call(likeFunction,
Expression.Property(null, typeof(EF), nameof(EF.Functions)), property, pattern);
Expression exp = Expression.Lambda(call, p);
return exp;
What the problem is
OK. Now, let's say that instead he wanted to search for
f = {
"filterdatafield": "fieldoperators",
"filtervalue": "test"
}
The assumption is that he meant to search in the name field of the operators. That's a nested property. How to get the ILIKE lambda for that?
What I've tried
string MyType="Field";
ParameterExpression p=null;
#nullable enable
Type? x = Type.GetType(MyType);
if (x is null)
{
throw new Exception("Cannot find type " + MyType);
}
#nullable disable
p = Expression.Parameter(x);
Expression property = Expression.Property(p, f.filterdatafield);
var functions = Expression.Property(null, typeof(EF).GetProperty(nameof(EF.Functions)));
var likeFunction = typeof(NpgsqlDbFunctionsExtensions).GetMethod(nameof(NpgsqlDbFunctionsExtensions.ILike), new Type[] { functions.Type, typeof(string), typeof(string) });
var pattern = Expression.Constant($"%{f.filtervalue}%", typeof(string));
if (property.Type == typeof(ICollection<FieldOperator>)) {
var fieldParam = Expression.Parameter(typeof(Field),"f");
var operatorsParam = Expression.Parameter(typeof(FieldOperator), "myops");
var lvl1 = Expression.Property(operatorsParam, "Operator");
var lvl2 = Expression.Property(lvl1, "Name");
var compareExpression = Expression.Call(likeFunction,
Expression.Property(null, typeof(EF), nameof(EF.Functions)), lvl2, pattern);
var lambdaForTheAnyCallPredicate = Expression.Lambda<Func<FieldOperator,Boolean>>(compareExpression, operatorsParam);
var collectionProperty = Expression.Property(fieldParam, "FieldOperators");
var resultExpression = ExpressionExtensions.CallAny(collectionProperty, lambdaForTheAnyCallPredicate);
Expression exp = Expression.Lambda<Func<Field, Boolean>>(resultExpression, p);
return exp;
}
The ExpressionExtensions.CallAny method is from this answer
This does generate a seemingly valid expression, however it fails when trying to be translated to SQL by the Entity Framework:
The LINQ expression 'DbSet<Field>()
.Where(f => (IEnumerable<FieldOperator>)f.FieldOperators
.Any(myops => __Functions_0
.ILike(
matchExpression: myops.Operator.Name,
pattern: "%test%")))' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
Model
public partial class Field
{
public Field()
{
FieldOperators = new HashSet<FieldOperator>();
}
public long FieldId { get; set; }
public string Name { get; set; }
//various other properties
public virtual ICollection<FieldOperator> FieldOperators { get; set; }
}
public partial class FieldOperator
{
public long FieldId { get; set; }
public long OperatorId { get; set; }
public virtual Field Field { get; set; }
public virtual Oem Operator { get; set; }
}
public partial class Oem
{
public long OemId { get; set; }
public string Name { get; set; }
//other properties omitted
}
Not sure what is
ParameterExpression p = typeof(Field);
in both places. It doesn't compile, so (according to the output) assuming it is
ParameterExpression p = Expression.Parameter(typeof(Field), "f");
And here is the problem. In the code in question you have
// (1)
ParameterExpression p = Expression.Parameter(typeof(Field), "f");
// ...
// (2)
var fieldParam = Expression.Parameter(typeof(Field), "f");
// ...
// (3)
var collectionProperty = Expression.Property(fieldParam, "FieldOperators");
// ...
// (4)
Expression exp = Expression.Lambda<Func<Field, Boolean>>(resultExpression, p);
in (2) you are creating a new parameter with the same name and type as (1), which you are using in (3) as part of the body of the lambda (4). However you are passing (1) as lambda parameter in (4) while the body uses (2).
And that's the problem. Parameter expressions are identified by instance, not by name. So although the expression looks fine, it isn't - if you try to Compile() it to delegate, you'd get a runtime exception. Similar is when EF Core trying to translate it.
It is a common mistake when manipulating expression trees with code. Because C# does not allow having parameters with the same name inside one and the same scope. But Expression API does not care about ParameterExpression names - as you can see, they are optional argument of Expression.Parameter.
With that being said, simply replace (2) with
var fieldParam = p;
or remove it and just use p in place of fieldParam, and the problem will be solved.
Also the collectionProperty variable is redundant since it is the same as property variable. So the final code should be something like this:
var parameter = Expression.Parameter(typeof(Field), "f");
var property = Expression.Property(parameter, f.filterdatafield);
var functions = Expression.Property(null, typeof(EF).GetProperty(nameof(EF.Functions)));
var likeFunction = typeof(NpgsqlDbFunctionsExtensions).GetMethod(nameof(NpgsqlDbFunctionsExtensions.ILike), new Type[] { functions.Type, typeof(string), typeof(string) });
var pattern = Expression.Constant($"%{f.filtervalue}%", typeof(string));
if (property.Type == typeof(ICollection<FieldOperator>))
{
var operatorsParam = Expression.Parameter(typeof(FieldOperator), "myops");
var lvl1 = Expression.Property(operatorsParam, "Operator");
var lvl2 = Expression.Property(lvl1, "Name");
var compareExpression = Expression.Call(likeFunction,
Expression.Property(null, typeof(EF), nameof(EF.Functions)), lvl2, pattern);
var anyPredicate = Expression.Lambda<Func<FieldOperator, bool>>(compareExpression, operatorsParam);
var body = Expression.Call(typeof(Enumerable),
nameof(Enumerable.Any), new[] { operatorsParam.Type },
property, anyPredicate);
var predicate = Expression.Lambda<Func<Field, bool>>(body, parameter);
return predicate;
}

OrderBy Expression Tree in Net Core Linq for Extension Method

I want to create an Extension method which mimics this, https://dejanstojanovic.net/aspnet/2019/january/filtering-and-paging-in-aspnet-core-web-api/
However, I want to add an OrderBy (for ColumnName) after StartsWith, how would I conduct this?
tried adding following and did not work .OrderBy(parameter)
Example:
return persons.Where(p => p.Name.StartsWith(filterModel.Term ?? String.Empty, StringComparison.InvariantCultureIgnoreCase))
.OrderBy(c=>c.Name)
.Skip((filterModel.Page-1) * filter.Limit)
.Take(filterModel.Limit);
public static class PaginateClass
{
static readonly MethodInfo startsWith = typeof(string).GetMethod("StartsWith", new[] { typeof(string), typeof(System.StringComparison) });
public static IEnumerable<T> Paginate<T>(this IEnumerable<T> input, PageModel pageModel, string columnName) where T : class
{
var type = typeof(T);
var propertyInfo = type.GetProperty(columnName);
//T p =>
var parameter = Expression.Parameter(type, "p");
//T p => p.ColumnName
var name = Expression.Property(parameter, propertyInfo);
// filterModel.Term ?? String.Empty
var term = Expression.Constant(pageModel.Term ?? String.Empty);
//StringComparison.InvariantCultureIgnoreCase
var comparison = Expression.Constant(StringComparison.InvariantCultureIgnoreCase);
//T p => p.ColumnName.StartsWith(filterModel.Term ?? String.Empty, StringComparison.InvariantCultureIgnoreCase)
var methodCall = Expression.Call(name, startsWith, term, comparison);
var lambda = Expression.Lambda<Func<T, bool>>(methodCall, parameter);
return input.Where(lambda.Compile()) //tried adding this and did not work .OrderBy(parameter)
.Skip((pageModel.Page - 1) * pageModel.Limit)
.Take(pageModel.Limit);
}
Other items PageModel:
public class PageModel
{
public int Page { get; set; }
public int Limit { get; set; }
public string Term { get; set; }
public PageModel()
{
this.Page = 1;
this.Limit = 3;
}
public object Clone()
{
var jsonString = JsonConvert.SerializeObject(this);
return JsonConvert.DeserializeObject(jsonString, this.GetType());
}
}
Dynamic Linq to Entities Orderby with Pagination
Check the sample code for the solution:
void Main()
{
var queryableRecords = Product.FetchQueryableProducts();
Expression expression = queryableRecords.OrderBy("Name");
var func = Expression.Lambda<Func<IQueryable<Product>>>(expression)
.Compile();
func().Dump();
}
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public static IQueryable<Product> FetchQueryableProducts()
{
List<Product> productList = new List<Product>()
{
new Product {Id=1, Name = "A"},
new Product {Id=1, Name = "B"},
new Product {Id=1, Name = "A"},
new Product {Id=2, Name = "C"},
new Product {Id=2, Name = "B"},
new Product {Id=2, Name = "C"},
};
return productList.AsQueryable();
}
}
public static class ExpressionTreesExtesion
{
public static Expression OrderBy(this IQueryable queryable, string propertyName)
{
var propInfo = queryable.ElementType.GetProperty(propertyName);
var collectionType = queryable.ElementType;
var parameterExpression = Expression.Parameter(collectionType, "g");
var propertyAccess = Expression.MakeMemberAccess(parameterExpression, propInfo);
var orderLambda = Expression.Lambda(propertyAccess, parameterExpression);
return Expression.Call(typeof(Queryable),
"OrderBy",
new Type[] { collectionType, propInfo.PropertyType },
queryable.Expression,
Expression.Quote(orderLambda));
}
}
Result
How it Works:
Created an expression using extension method on the Queryable type, which internally calls OrderBy method of the Queryable type, expecting IQueryable to be the Input, along with the field name and thus runs the ordering function and Ordered collection is the final Output
Option 2:
This may fit your use case better, here instead of calling OrderBy method, we are creating the Expression<Func<T,string>> as an extension method to the IEnumerable<T>, which can then be compiled and supplied to the OrderBy Call, as shown in the example and is thus much more intuitive and simple solution:
Creating Expression:
public static class ExpressionTreesExtesion
{
public static Expression<Func<T,string>> OrderByExpression<T>(this IEnumerable<T> enumerable, string propertyName)
{
var propInfo = typeof(T).GetProperty(propertyName);
var collectionType = typeof(T);
var parameterExpression = Expression.Parameter(collectionType, "x");
var propertyAccess = Expression.MakeMemberAccess(parameterExpression, propInfo);
var orderExpression = Expression.Lambda<Func<T,string>>(propertyAccess, parameterExpression);
return orderExpression;
}
}
How to Call:
var ProductExpression = records.OrderByExpression("Name");
var result = records.OrderBy(ProductExpression.Compile());
ProductExpression.Compile() above will compile into x => x.Name, where column name is supplied at the run-time
Please note in case the ordering field can be other types beside string data type, then make that also generic and supply it while calling extension method, only condition being property being called shall have the same type as supplied value, else it will be a run-time exception, while creating Expression
Edit 1, how to make the OrderType field also generic
public static Expression<Func<T, TField>> OrderByFunc<T,TField>(this IEnumerable<T> enumerable, string propertyName)
{
var propInfo = typeof(T).GetProperty(propertyName);
var collectionType = typeof(T);
var parameterExpression = Expression.Parameter(collectionType, "x");
var propertyAccess = Expression.MakeMemberAccess(parameterExpression, propInfo);
var orderExpression = Expression.Lambda<Func<T, TField>>(propertyAccess, parameterExpression);
return orderExpression;
}
How to call:
Now both the types need to be explicitly supplied, earlier were using generic type inference from IEnumerable<T>:
// For Integer Id field
var ProductExpression = records.OrderByFunc<Product,int>("Id");
// For string name field
var ProductExpression = records.OrderByFunc<Product,string>("Name");

Building expression for all properties of class

Not a lot of experience with Expressions, and having a hard time understanding the bigger picture.
I have a class which defines a large amount of properties.
Instead of doing a lot of dumb type work, I am trying to use reflection/expressions to evaluate these properties for me.
A short sample of the class:
[Function(Name = "sensors")]
internal class Sensors
{
[CabinetDoubleSensor(SensorType = SensorType.Temperature, Precision = 3)]
[JsonProperty(PropertyName = "IO_PCW_FL_SPR")]
public JsonSensor<double> IOPcwFlSpr { get; set; } = new JsonSensor<double>();
[CabinetDoubleSensor(SensorType = SensorType.Temperature, Precision = 3)]
[JsonProperty(PropertyName = "IO_PCW_RL_SPR")]
public JsonSensor<double> IOPcwRlSpr { get; set; } = new JsonSensor<double>();
// 100+ sensor definitions below
}
I want to evaluate all properties and store them in a list as follows:
public IEnumerable<ISensor> UpdateSensors(Json.Sensors jsonUpdate)
{
// To test if it works, but clearly, I do NOT want to list all sensor evaluations here!
UpdateSensor(() => jsonUpdate.IOPcwFlSpr);
return SensorMap.Values.ToList();
}
private void UpdateSensor(Expression<Func<JsonSensor<double>>> propertySelector)
{
if (propertySelector.Body is MemberExpression expression)
{
var compiledExpression = propertySelector.Compile();
var jsonSensor = compiledExpression.Invoke();
var name = expression.Member.Name;
var cabinetSensor = SensorMap[name];
cabinetSensor.Value = jsonSensor.Value.ToString($"F{cabinetSensor.Precision}");
}
}
So then the part where I am stuck. As said, I don't want to call UpdateSensor(() => jsonUpdate.SensorName 100+ times. So I am trying to find a way to construct that lambda expression myself.
private static readonly List<PropertyInfo> Properties;
static SensorFactory()
{
Properties = typeof(Json.Sensors).GetProperties().ToList();
}
public IEnumerable<ISensor> Test(Json.Sensors jsonUpdate)
{
foreach (var property in Properties)
{
var getterMethodInfo = property.GetGetMethod();
var parameterExpression = Expression.Parameter(jsonUpdate.GetType());
var getterCall = Expression.Call(parameterExpression, getterMethodInfo);
UnaryExpression castToObject = Expression.Convert(getterCall, typeof(JsonSensor<double>));
var lambda = (Expression<Func<JsonSensor<double>>>)Expression.Lambda(castToObject, parameterExpression);
UpdateSensor(lambda);
}
// not relevant
return null;
}
The cast is illegal:
System.InvalidCastException: 'Unable to cast object of type
'System.Linq.Expressions.Expression1[System.Func2[Asml.Mbi.FlowAndTemperature.Peripherals.Cabinet.Json.Sensors,Asml.Mbi.FlowAndTemperature.Peripherals.Cabinet.Json.JsonSensor1[System.Double]]]'
to type
'System.Linq.Expressions.Expression1[System.Func1[Asml.Mbi.FlowAndTemperature.Peripherals.Cabinet.Json.JsonSensor1[System.Double]]]'.'
I think (/hope) I am close, but I don't know how to get the Expression<Func<JsonSensor<double>>> as return value.
Actually there are few problems in your code.
Exception itself is thrown, because you provide one parameter to Lambda method, and this way it will produce Func<T1, T2>. Func<T> doesn't accept any parameter, so you should call Expression.Lambda(castToObject).
Regardless, you should probably change it to Func<Sensors, JsonSensor<double>>, otherwise you'll need to wrap jsonUpdate as constant inside lambda.
Here is example of adjusted UpdateSensor and Test methods:
private static void UpdateSensor(Sensors jsonUpdate, Expression<Func<Sensors, JsonSensor<double>>> propertySelector)
{
if (propertySelector.Body is MemberExpression expression)
{
var compiledExpression = propertySelector.Compile();
// Signature was changed and jsonUpdate is not compiled into lambda; we need to pass reference
var jsonSensor = compiledExpression.Invoke(jsonUpdate);
var name = expression.Member.Name;
var cabinetSensor = SensorMap[name];
cabinetSensor.Value = jsonSensor.Value.ToString($"F{cabinetSensor.Precision}");
}
}
public IEnumerable<Sensor> Test(Sensors jsonUpdate)
{
foreach (var property in Properties)
{
var parameterExpression = Expression.Parameter(jsonUpdate.GetType());
// You don't need call or GetMethod, you need to access Property
var propertyCall = Expression.Property(parameterExpression, property);
// Cast is redundant, and if you add it UpdateSensor will do nothing
// UnaryExpression castToObject = Expression.Convert(propertyCall, typeof(JsonSensor<double>));
var lambda = Expression.Lambda<Func<Sensors, JsonSensor<double>>>(propertyCall, parameterExpression);
UpdateSensor(jsonUpdate, lambda);
}
// not relevant
return null;
}

Make dynamic expression of EF core "Like" function

I've written some codes to make dynamic expressions for filtering my pagination.
I'm trying to make a dynamic expression of EF Core built-in functions for searching (EF.Functions.Like).
I've tried a way like bottom but it is an extension method and first parameters is not used when calling the method. I don't know how to follow the way ==> Ef => Function => Like.
The method should be used like this => Ef.Functions.Like("Property to search", "%Some Pattern")
var likeMethod = typeof(DbFunctionsExtensions)
.GetMethods()
.Where(p => p.Name == "Like")
.First();
string pattern = $"%{finalConstant}%";
ConstantExpression likeConstant = Expression.Constant(pattern,typeof(string));
// the member expression is the property expression for example p.Name
var likeMethodCall = Expression.Call(method: likeMethod, arguments: new[] { memberExpression, likeConstant });
var searchLambda = Expression.Lambda<Func<T, bool>>(likeMethodCall, parameter);
query = query.Where(searchLambda);
but it throw exception saying
Incorrect number of arguments supplied for call to method 'Boolean Like(Microsoft.EntityFrameworkCore.DbFunctions, System.String,
System.String)'\r\nParameter name: method
I implemented a dynamic search based on this article
.NET Core Npgsql.EntityFrameworkCore ILikeExpression
That's what I did:
I implement the [Searchable] attribute, with which I will mark the properties by which the search will be performed. Properties are only of type string, if necessary I can explain how to search for properties of type long and int.
[AttributeUsage(AttributeTargets.Property)]
public class SearchableAttribute : Attribute
{
}
An extension has been created for IQueryable , which takes the input string from the search and implements the Like function according to the specified properties
public static class QueryableExtension
{
public static IQueryable<TEntityDto> ExecuteQueryFilter<TEntityDto>(this IQueryable<TEntityDto> queryable, string query)
where TEntityDto : class, IEntityDto
{
// If the incoming request is empty, skip the search
if (string.IsNullOrEmpty(query))
{
return queryable;
}
// We get all properties with type of string marked with our attribute
var properties = typeof(TEntityDto).GetProperties()
.Where(p => p.PropertyType == typeof(string) &&
p.GetCustomAttributes(typeof(SearchableAttribute), true).FirstOrDefault() != null)
.Select(x => x.Name).ToList();
// If there are no such properties, skip the search
if (!properties.Any())
{
return queryable;
}
// Get our generic object
ParameterExpression entity = Expression.Parameter(typeof(TEntityDto), "entity");
// Get the Like Method from EF.Functions
var efLikeMethod = typeof(DbFunctionsExtensions).GetMethod("Like",
BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic,
null,
new[] { typeof(DbFunctions), typeof(string), typeof(string) },
null);
// We make a pattern for the search
var pattern = Expression.Constant($"%{query}%", typeof(string));
// Here we will collect a single search request for all properties
Expression body = Expression.Constant(false);
foreach (var propertyName in properties)
{
// Get property from our object
var property = Expression.Property(entity, propertyName);
// Сall the method with all the required arguments
Expression expr = Expression.Call(efLikeMethod,
Expression.Property(null, typeof(EF), nameof(EF.Functions)), property, pattern);
// Add to the main request
body = Expression.OrElse(body, expr);
}
// Compose and pass the expression to Where
var expression = Expression.Lambda<Func<TEntityDto, bool>>(body, entity);
return queryable.Where(expression);
}
}
The Dto object itself looks like this:
public class CategoryDto : IEntityDto
{
public long Id { get; set; }
[Searchable]
public string Name { get; set; }
[Searchable]
public string IconKey { get; set; }
public long UploadId { get; private set; }
[Searchable]
public string UploadFileName { get; set; }
[Searchable]
public string CreatedBy { get; set; }
public DateTime Created { get; set; }
}
I tested this search method on one million records, with objects name in one to five words. The search process very fast. The performance benefit here is that Expression is converted on the database side as LINQ to SQL
Here's a working example
public static Expression<Func<T, bool>> Like<T>(Expression<Func<T, string>> prop, string keyword)
{
var concatMethod = typeof(string).GetMethod(nameof(string.Concat), new[] { typeof(string), typeof(string) });
return Expression.Lambda<Func<T, bool>>(
Expression.Call(
typeof(DbFunctionsExtensions),
nameof(DbFunctionsExtensions.Like),
null,
Expression.Constant(EF.Functions),
prop.Body,
Expression.Add(
Expression.Add(
Expression.Constant("%"),
Expression.Constant(keyword),
concatMethod),
Expression.Constant("%"),
concatMethod)),
prop.Parameters);
}
query = query.Where(Like<User>(u => u.UserName, "angel"));
As mentioned in the comment, you need to include EF.Functions as the first parameter:
var likeMethodCall = Expression.Call(likeMethod, new []
{
Expression.Property(null, typeof(EF).GetProperty("Functions")),
memberExpression,
likeConstant
});

Call Static Method in expression.call with arguments

I have extended the string class for Contains method. I'm trying to call it in Expression.Call, but how to pass the argument properly?
Code: String Contains method:
public static class StringExts
{
public static bool NewContains(this string source, string ValToCheck, StringComparison StrComp)
{
return source.IndexOf(ValToCheck, StrComp) >= 0;
}
}
In Expression calling as :
public class Person { public string Name {get; set;} }
public class Persons {
public List<Person> lstPersons {get; set;}
public Persons() {
lstPersons = new List<Person>();
}
}
public class Filter
{
public string Id { get; set; }
public Operator Operator { get; set; }
public string value { get; set; }
}
public void Main()
{
//Get the json.
//"Filters": [{"id": "Name", "operator": "contains", "value": "Microsoft"}]
Filter Rules = JsonConvert.DeserializeObject<Filter>(json);
// Get the list of person firstname.
List<Person> lstPerson = GetFirstName();
ParameterExpression param = Expression.Parameter(typeof(Person), "p");
Expression exp = null;
exp = GetExpression(param, rules[0]);
//get all the name contains "john" or "John"
var filteredCollection = lstPerson.Where(exp).ToList();
}
private Expression GetExpression(ParameterExpression param, Filter filter){
MemberExpression member = Expression.Property(param, filter.Id);
ConstantExpression constant = Expression.Constant(filter.value);
Expression bEXP = null;
switch (filter.Operator)
{
case Operator.contains:
MethodInfo miContain = typeof(StringExts).GetMethod("NewContains", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static);
return Expression.Call(miContain, member, constant , Expression.Constant(StringComparison.OrdinalIgnoreCase));;
break;
}
}
Error:
An unhandled exception of type 'System.ArgumentException' occurred in System.Core.dll.Additional information: Static method requires null instance, non-static method requires non-null instance.
How to call the parameter in miContain for following Call() methods?
I have updated the Code.
You are not specifying all parameters. If you create expressions for all, it works:
ParameterExpression source = Expression.Parameter(typeof(string));
string ValToCheck = "A";
StringComparison StrComp = StringComparison.CurrentCultureIgnoreCase;
MethodInfo miContain = typeof(StringExts).GetMethod("NewContains", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static);
var bEXP = Expression.Call(miContain, source, Expression.Constant(ValToCheck), Expression.Constant(StrComp));
var lambda = Expression.Lambda<Func<string, bool>>(bEXP, source);
bool b = lambda.Compile().Invoke("a");
You haven't specified enough arguments (2 vs. 3). NewContains has three arguments.
Also, since this method is not an instance method you can't set the this parameter. This overload looks better.
You probably should have examined the overload list. That is how I found the right answer to this question without knowing it myself beforehand.

Categories

Resources