Dynamically create lambda search on IQueryable on nested objects - c#

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.

Related

Build a Generic Expression Tree .NET Core

Hello Community i am aware of this might be a possible duplicate.
How do I dynamically create an Expression<Func<MyClass, bool>> predicate from Expression<Func<MyClass, string>>?
https://www.strathweb.com/2018/01/easy-way-to-create-a-c-lambda-expression-from-a-string-with-roslyn/
How to create a Expression.Lambda when a type is not known until runtime?
Creating expression tree for accessing a Generic type's property
There are obviously too many resources.
I am still confused though.
Could someone provide a clearer picture of what is happening in the below code.
Below i have provided some comments to help my understanding.
private Expression<Func<T, bool>> ParseParametersToFilter<T>(string parameters)
{
Expression<Func<T, bool>> finalExpression = Expression.Constant(true); //Casting error
if (string.IsNullOrEmpty(parameters))
return finalExpression;
string[] paramArray = parameters.Split(","); //parameters is one string splitted with commas
ParameterExpression argParam = Expression.Parameter(typeof(T), "viewModel"); //Expression Tree
foreach (var param in paramArray)
{
var parsedParameter = ParseParameter(param);
if (parsedParameter.operation == Operation.None)
continue; // this means we parsed incorrectly we TODO: Better way for error handling
//Property might be containment property e.g T.TClass.PropName
Expression nameProperty = Expression.Property(argParam, parsedParameter.propertyName);
//Value to filter against
var value = Expression.Constant(parsedParameter.value);
Expression comparison;
switch (parsedParameter.operation)
{ //Enum
case Operation.Equals:
comparison = Expression.Equal(nameProperty, value);
break;
//goes on for NotEquals, GreaterThan etc
}
finalExpression = Expression.Lambda(comparison, argParam);// Casting error
}
return finalExpression;
}
The above obviously is not working.
This is returned to linq query like this IEnumerable<SomeModel>.Where(ParseParametersToFilter.Compile())
I understand my mistake is a casting mistake.
How could i fix this?
After #Jeremy Lakeman answer i updated my code to look like this. Although the ViewModel i am using is quite complex. I have provided a small preview at the end.
private Expression<Func<T, bool>> ParseParametersToFilter<T>(string parameters)
{
Expression<Func<T, bool>> finalExpression = t => true;
if (string.IsNullOrEmpty(parameters))
return finalExpression;
string[] paramArray = parameters.Split(","); //parameters is one string splitted with commas
ParameterExpression argParam = Expression.Parameter(typeof(T), "viewModel"); //Expression Tree
Expression body = Expression.Constant(true);
foreach (var param in paramArray)
{
var parsedParameter = ParseParameter(param);
if (parsedParameter.operation == Operation.None)
continue; // this means we parsed incorrectly TODO: Better way for error handling
//Property might be containment property e.g T.TClass.PropName
Expression nameProperty = Expression.Property(argParam, parsedParameter.propertyName);
//Value to filter against
var value = Expression.Constant(parsedParameter.value);
switch (parsedParameter.operation)
{ //Enum
case Operation.Equals:
body = Expression.AndAlso(body, Expression.Equal(nameProperty, value));
break;
//goes on for NotEquals, GreaterThan etc
}
body = Expression.AndAlso(body, argParam);
}
return Expression.Lambda<Func<T, bool>>(body, argParam);
}
private (string propertyName, Operation operation, string value) ParseParameter(string parameter){...}
But now i get the following Exceptions
When i pass the Status as property parameter:
The binary operator Equal is not defined for the types 'model.StatusEnum' and 'System.String'.
When i pass the User.FriendlyName parameter:
Instance property 'User.FriendlyName' is not defined for type 'model.ReportViewModel'
Parameter name: propertyName
Here is how my view model looks like!
public class ReportViewModel
{
public StatusEnum Status {get;set;}
public UserViewModel User {get;set;}
}
public enum StatusEnum
{
Pending,
Completed
}
public class UserViewModel
{
public string FriendlyName {get;set;}
}
So you're trying to turn something like "a==1,b==3" into viewModel => viewModel.a == 1 && viewModel.b == 3?
I think you're already pretty close, you just need add the && (or ||), and always create a lambda;
private Expression<Func<T, bool>> ParseParametersToFilter<T>(string parameters)
{
ParameterExpression argParam = Expression.Parameter(typeof(T), "viewModel"); //Expression Tree
Expression body = Expression.Constant(true);
if (!string.IsNullOrEmpty(parameters)){
body = parameters.Split(",")
.Select(param => {
var parsedParameter = ParseParameter(param);
// ... as above, turn param into a comparison expression ...
return comparison;
})
.Aggregage((l,r) => Expression.AndAlso(l, r));
}
return Expression.Lambda<Func<T, bool>>(body, argParam);
}
And if this is for passing to entity framework, don't compile it or you'll only be able to evaluate it client side.
Here is what i came up with and works pretty well, from my tests today.
Some refactoring may be needed. I am open to suggestions.
Please make sure to check the comments inside the code.
private void ConvertValuePropertyType(Type type, string value, out dynamic converted)
{
// Here i convert the value to filter to the necessary type
// All my values come as strings.
if (type.IsEnum)
converted = Enum.Parse(type, value);
else if (type == typeof(DateTime))
converted = DateTime.Parse(value);
else if (type is object)
converted = value;
else
throw new InvalidCastException($"Value was not converted properly {nameof(value)} {nameof(type)}");
}
private MemberExpression GetContainmentMember(ParameterExpression parameterExpression, string propertyName)
{
//propertName looks like this User.FriendlyName
//So we have to first take T.User from the root type
// Then the Name property.
// I am not sure how to make this work for any depth.
var propNameArray = propertyName.Split(".");
if (propNameArray.Length > 1)
{
MemberExpression member = Expression.Property(parameterExpression, propNameArray[0]);
return Expression.PropertyOrField(member, propNameArray[1]);
}
else
{ //This needs to make sure we retrieve containment
return Expression.Property(parameterExpression, propertyName);
}
}
// ***************************************************************
// This is the core method!
private Expression<Func<T, bool>> ParseParametersToFilter<T>(string parameters)
{
Expression body = Expression.Constant(true);
ParameterExpression argParam = Expression.Parameter(typeof(T), nameof(T));
if (string.IsNullOrEmpty(parameters))
return Expression.Lambda<Func<T, bool>>(body, argParam); // return empty filter
string[] paramArray = parameters.Split(","); //parameters is one string splitted with commas
foreach (var param in paramArray)
{
var parsedParameter = ParseParameter(param);
if (parsedParameter.operation == Operation.None)
continue; // this means we parsed incorrectly, do not fail continue
//Get model
//Get property name
//Property might be containment property e.g T.TClass.PropName
//Value to filter against
MemberExpression nameProperty = GetContainmentMember(argParam, parsedParameter.propertyName);
//Convert property value according to property name
Type propertyType = GetPropertyType(typeof(T), parsedParameter.propertyName);
ConvertValuePropertyType(propertyType, parsedParameter.value, out object parsedValue);
var value = Expression.Constant(parsedValue);
switch (parsedParameter.operation)
{
//What operation did the parser retrieve
case Operation.Equals:
body = Expression.AndAlso(body, Expression.Equal(nameProperty, value));
break;
//goes on for NotEquals, GreaterThan etc
default:
break;
}
}
return Expression.Lambda<Func<T, bool>>(body, argParam);
}
private (string propertyName, Operation operation, string value) ParseParameter(string parameter){...}
This worked very good so far.

Handle null values of nested objects in C# expression tree

I have searched, and found similar posts pertaining to my issue, however nothing seems to solve my problem.
I am fairly new to C#, and this is my first attempt at building an expression tree. (please go easy ;-)
I am trying to create an expression tree which would, once compiled, filter values on a set of data.
Here is my expression method:
private static Expression<Func<TItem, bool>> CreateFilterExpression<TItem>(string propertyName, string expressionType, dynamic filterValue)
{
if (param == null)
{
param = Expression.Parameter(typeof(TItem), "item");
}
MemberExpression member = GetMemberExpression<TItem>(propertyName);
//When we call our method, we need to evaluate on the same type
//we convert the filter value to the type of the property we are evaluating on
dynamic convertedValue = Convert.ChangeType(filterValue, member.Type);
MethodInfo method = member.Type.GetMethod(expressionType, new[] { member.Type });
ConstantExpression constantValue = Expression.Constant(convertedValue, member.Type);
Expression containsMethodExp;
if (expressionType == "NotEqual")
{
method = member.Type.GetMethod("Equals", new[] { member.Type });
}
if (member.Type.ToString().ToLower() == "system.string")
{
//We need to compare the lower case of property and value
MethodCallExpression propertyValueToLowerCase = Expression.Call(member, typeof(string).GetMethod("ToLower", System.Type.EmptyTypes));
MethodCallExpression filterValueToLowerCase = Expression.Call(constantValue, typeof(string).GetMethod("ToLower", System.Type.EmptyTypes));
containsMethodExp = Expression.Call(propertyValueToLowerCase, method, filterValueToLowerCase);
}
else if (member.Type.ToString().ToLower() == "system.datetime")
{
//we need to compare only the dates
MemberExpression dateOnlyProperty = Expression.Property(member, "Date");
containsMethodExp = Expression.Call(dateOnlyProperty, method, constantValue);
}
else
{
containsMethodExp = Expression.Call(member, method, constantValue);
}
if (expressionType == "NotEqual")
{
containsMethodExp = Expression.Not(containsMethodExp);
}
return Expression.Lambda<Func<TItem, bool>>(containsMethodExp, param);
}
private static MemberExpression GetMemberExpression<TItem>(string propertyName)
{
if (param == null)
{
param = Expression.Parameter(typeof(TItem), "item");
}
MemberExpression member = null;
//Check if we have a nested property
if (propertyName.Contains('.'))
{
Expression nestedProperty = param;
string[] properies = propertyName.Split('.');
int zeroIndex = properies.Count() - 1;
for (int i = 0; i <= zeroIndex; i++)
{
if (i < zeroIndex)
{
nestedProperty = Expression.PropertyOrField(nestedProperty, properies[i]);
}
else
{
member = Expression.Property(nestedProperty, properies[i]);
}
}
}
else
{
member = Expression.Property(param, propertyName);
}
return member;
}
Example usage would be like so:
var lambda = CreateFilterExpression<T>("Some.Nested.Object", "Equals", "Some value");
var compiled = lambda.Compile();
gridData = gridData.Where(compiled);
An example of the data I trying to ultimately bind to my grid looks like this:
public class Some : BaseClass
{
public decimal NumberAvailable { get; set; }
public DateTime EffectiveDate { get; set; }
public Batch Batch { get; set; }
public decimal Price { get; set; }
public decimal Limit { get; set; }
public NestedClass Nested { get; set; }
public int? CompanyId { get; set; }
public decimal Amount { get; set; }
}
public class NestedClass : BaseClass
{
public int RequestId { get; set; }
public string Code { get; set; }
public string Company { get; set; }
public string Reference { get; set; }
}
The problem occurs when we have null value on an object, like "Some.Nested = null", and then trying to convert "Reference" to lowercase. Here:
MethodCallExpression propertyValueToLowerCase = Expression.Call(member, typeof(string).GetMethod("ToLower", System.Type.EmptyTypes));
Here is the result in debugger:
How can I check for null values, on nested objects, and return empty string if it is null?
I hope I explained my question well enough. Thank you in advance!
What you want to do is to generate an expression like this:
Some == null ? null : Some.Nested == null ? null : Some.Nested.Object
This unfortunately is no longer a member expression, so GetMemberExpression wouldn’t work for this. Instead you need a chain of conditional expression that accesses one more level at a time.
Once you have that, you could then do <memberExpression> ?? string.Empty to get a string which you can safely operate on.
To generate the latter expression, you can use Expression.Coalesce:
Expression.Coalesce(memberExpression, Expression.Constant(string.Empty))
For the member expression itself, you could write something like this:
Expression AccessMember(Expression obj, string propertyName)
{
string[] parts = propertyName.Split(new char[] { '.' }, 2);
Expression member = Expression.PropertyOrField(obj, parts[0]);
if (parts.Length > 1)
member = AccessMember(member, parts[1]);
return Expression.Condition(Expression.Equal(obj, Expression.Constant(null)),
Expression.Constant(null, member.Type), member);
}
This can be used like this:
string path = "Some.Nested.Object";
string[] parts = path.Split(new char[] { '.' }, 2);
ParameterExpression param = Expression.Parameter(typeof(T), parts[0]);
Expression memberAccess = AccessMember(param, parts[1]);
memberAccess would then be exactly the above chained conditional expression.
Combined into your function (simplified only for strings for now), it could look like this:
Expression<Func<TObj, bool>> BuildFilterExpression<TObj, TMember>(string propertyPath, TMember comparisonValue, TMember defaultValue)
{
string[] parts = propertyPath.Split(new char[] { '.' }, 2);
ParameterExpression param = Expression.Parameter(typeof(TObj), parts[0]);
// get member access expression
Expression memberExpression = AccessMember(param, parts[1]);
// coalesce the member with the default value
memberExpression = Expression.Coalesce(memberExpression, Expression.Constant(defaultValue));
// get the comparison value as expression
Expression comparisonExpression = Expression.Constant(comparisonValue);
// type specific logic
if (memberExpression.Type == typeof(string))
{
MethodInfo toLowerMethod = typeof(string).GetMethod("ToLower", Type.EmptyTypes);
memberExpression = Expression.Call(memberExpression, toLowerMethod);
comparisonExpression = Expression.Call(comparisonExpression, toLowerMethod);
}
// create the comparison expression
Expression filterExpression = Expression.Equal(memberExpression, comparisonExpression);
return Expression.Lambda<Func<TObj, bool>>(filterExpression, param);
}
Used like this:
BuildFilterExpression<SomeType, string>("Some.Nested.Object", "foo bar", string.Empty)
… it essentially creates the following lambda expression:
(Some) => ((Some == null ? null : Some.Nested == null ? null : Some.Nested.Object) ?? string.Empty).ToLower() == "foo bar"
Above code assumes that for a property expression Some.Nested.Object, Some is the object that is being passed to the lambda, so the first property that would be accessed is Nested. The reason is that I simply didn’t know your example object structure, so I had to come up with something.
If you want Some be the first property that is accessed for the passed object, you can easily change that though. To do that, modify the beginning of BuildFilterExpression so that the propertyPath is not split up. Pass some random name (or no name even) to Expression.Parameter, and pass the full propertyPath to AccessMember:
// don’t split up the propertyPath
// let’s call the parameter `obj`
ParameterExpression param = Expression.Parameter(typeof(TObj), "obj");
// get member access expression—for the full property path
Expression memberExpression = AccessMember(param, propertyPath);

How to get Expression for Nullable values ( fields ) without converting from Expression.Convert in C#?

I am dealing with the scanario of Nullable types during formation of dynamic query expressions. These expressions would fetch filtered data from any SQL Tables( interfacing with Code First classes using EF ).
I have normal object ( e.g Consignment operating on several properties along with Nullable properties).
My expression formation goes well untill I encounter some Nullable types. On these nullables, I am getting
The binary operator NotEqual is not defined for the types 'System.Nullable`1[System.Single]' and 'System.Single'.
For removing this exception, I am using all appraoches regarding convertion posted on different threads.
Invoking lambda expressions in Expression trees
Trying to filter on a Nullable type using Expression Trees
These all are generating expressions with added word "Convert" ( i.e Convert(someValue) ) and in result I always have expression
t=>(t.Consignment.Id = 45000 && t.Consignment.someProperty>=45 Or t.Consignment.Weight! = Convert(5000)).
Of course I need the whole above expression WITHOUT "Convert". Because this "Convert" will not fetch the data from tables accordingly.
Any help would be greatly appreciated! What should left to do? I already know conversion, but this makes the whole expression useless, because it won't project the records because of needless "Convert"
Added
Expression NotEqual<T>(Expression PropertyType, ConstantExpression a_Constant, ParameterExpression parameter)
{
if(IsNullableType(Property.Type) &&!IsNullableType(a_Constant.Type))
{
var converted = a_Constant.Type != Property.Type ? (Expression)Expression.Convert(a_Constant, Property.Type): (Expression)a_Constant;
// here above statement returns (Convert(50000)) and all I want (50000), but i tried all combinitions from Expression in order to form this constant as expression, it always throws exception what I mentioned originally.
var body = Expression.MakeBinary(ExpressionType.NotEqual, PropertyType, converted);
//MakeBinary statement returns {(t.Weight != Convert(5000000))} but I need {(t.Weight != 5000000)}
var expr = Expression.Lambda<Func<T, bool>>(body, parameter);
return expr;
}
}
Code:
public class Consignment
{
public float? Weight { get; set; }
}
public static class GenericQueryExpressionBuilder
{
private static Expression NotEqual<T>(Expression memberExpression, ConstantExpression a_Constant, ParameterExpression parameter)
{
ConstantExpression constantExpression = null;
if (IsNullableType(memberExpression.Type) && !IsNullableType(a_Constant.Type))
{
//var converted = a_Constant.Type != memberExpression.Type ? (Expression)Expression.Convert(a_Constant, memberExpression.Type) : (Expression)a_Constant;
Expression constantExp = Expression.Property(a_Constant,typeof(T),"Weight");
**// above statement throws exception I commented.**
var body = Expression.MakeBinary(ExpressionType.NotEqual, memberExpression, converted);
//here I want "t=>(t.Weight!=5000.0) INSTEAD of t=>(t.Weight!=Convert(5000.0))"
var expr = Expression.Lambda<Func<T, bool>>(body, parameter);
return expr;
}
else if (!IsNullableType(memberExpression.Type) && IsNullableType(a_Constant.Type))
memberExpression = Expression.Convert(memberExpression, a_Constant.Type);
return Expression.NotEqual(memberExpression, constantExpression);
}
static bool IsNullableType(Type t)
{
return t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>);
}
private static Expression GetExpression<T>(ParameterExpression param, string a_strPropertyName, string Operator, object Value)
{
MemberExpression member = Expression.Property(param, a_strPropertyName);
ConstantExpression constant = Expression.Constant(Value);
try
{
return GenericQueryExpressionBuilder.NotEqual<T>(member, constant, param);
}
catch (InvalidOperationException)
{
return null;
}
return null;
}
public static Expression<Func<T, bool>> GetExpression<T>(Consignment consignment)
{
Expression expression = null;
var parameter = Expression.Parameter(typeof(T), "t");
string PropertyName = "Weight";
string Operation = "NotEqual";
object Value = consignment.Weight;
expression = GenericQueryExpressionBuilder.GetExpression<T>(parameter, PropertyName, Operation, Value);
return Expression.Lambda<Func<T, bool>>(expression, parameter);
}
}
class Program
{
static void Main(string[] args)
{
Consignment consignment = new Consignment();
consignment.Weight = 50000.0f;
var deleg = GenericQueryExpressionBuilder.GetExpression<Consignment>(consignment).Compile();
}
}
Here's a short but complete example showing how to build the c => c.Weight.HasValue && c.Weight.Value != 5000f expression tree. I've removed a lot of irrelevant code from the question:
using System;
using System.Linq.Expressions;
public class Consignment
{
public float? Weight { get; set; }
}
public class Test
{
private static Expression NotEqual(Expression memberExpression,
ConstantExpression constantToCompare)
{
// Other cases removed, for simplicity. This answer only demonstrates
// how to handle c => c.Weight != 5000f.
var hasValueExpression = Expression.Property(memberExpression, "HasValue");
var valueExpression = Expression.Property(memberExpression, "Value");
var notEqual = Expression.NotEqual(valueExpression, constantToCompare);
return Expression.AndAlso(hasValueExpression, notEqual);
}
static void Main(string[] args)
{
Consignment consignment = new Consignment();
consignment.Weight = 50000.0f;
var parameter = Expression.Parameter(typeof(Consignment), "c");
var weight = Expression.Property(parameter, "Weight");
var constant = Expression.Constant(5000f, typeof(float));
var weightNotEqualExpression = NotEqual(weight, constant);
var lambda = Expression.Lambda<Func<Consignment, bool>>
(weightNotEqualExpression, parameter);
Console.WriteLine(lambda);
}
}

Expression.Like in C#

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

LINQ to Entities does not recognize the method 'System.Object GetValue(...)'

My issue is I need to query on the value of a property in a generic class. The property is tagged with an attribute.
See the following code:
var rowKeyProperty = EFUtil.GetClassPropertyForRowKey<T>();
var tenantKeyProperty = EFUtil.GetClassPropertyForTenantKey<T>();
var queryResult =
objContext.CreateObjectSet<T>().Single(l => (((int) tenantKeyProperty.GetValue(l, null)) == tenantKey) &&
(((int)rowKeyProperty.GetValue(l, null)) == KeyValue));
The rowKeyProperty and tenantKeyProperty are of type System.Reflection.PropertyInfo.
I understand why I am getting the error. When the linq query is translated to SQL, it can't understand the property.GetValue.
However, I'm completely stumped as to a work around here. Does anyone have any ideas how to achieve this? Thx.
You need to actually build up the Expression objects to represent the expression that you want this to mimic, in this case the expression you want to represent is:
l => l.SomeProperty == SomeValue
So you need to build up each component of that bit by bit, from creating the parameter, defining the equality operator, the property access, the constant value, etc.
public static Expression<Func<TItem, bool>> PropertyEquals<TItem, TValue>(
PropertyInfo property, TValue value)
{
var param = Expression.Parameter(typeof(TItem));
var body = Expression.Equal(Expression.Property(param, property),
Expression.Constant(value));
return Expression.Lambda<Func<TItem, bool>>(body, param);
}
Once you have all of that you can call it using the data that you have:
var queryResult = objContext.CreateObjectSet<T>()
.Where(PropertyEquals<T, int>(tenantKeyProperty, tenantKey))
.Where(PropertyEquals<T, int>(rowKeyProperty, KeyValue))
.Single();
Appendix here... Following #Servy answer and based on this topic with a nice answer by #TomBrothers, you can use the same logic to make a StartsWith (or similar) function:
public static Expression<Func<TItem, bool>> PropertyStartsWith<TItem>(PropertyInfo propertyInfo, string value)
{
var param = Expression.Parameter(typeof(TItem));
var m = Expression.MakeMemberAccess(param, propertyInfo);
var c = Expression.Constant(value, typeof(string));
var mi = typeof(string).GetMethod("StartsWith", new Type[] { typeof(string) });
var body = Expression.Call(m, mi, c);
return Expression.Lambda<Func<TItem, bool>>(body, param);
}
In this case, it forces value to be a string.
It is more correct to specify the type in Expression.Constant(value, typeof(TValue)))
public static Expression<Func<TItem, bool>> PropertyEquals<TItem, TValue>(
string property, TValue value)
{
var xParameter = Expression.Parameter(typeof(TItem));
var body = Expression.Equal(Expression.Property(xParameter, property), Expression.Constant(value, typeof(TValue)));
return Expression.Lambda<Func<TItem, bool>>(body, xParameter);
}
Or, like this, to check the property. ChangeType
public static Expression<Func<TItem, bool>> PropertyEquals<TItem, TValue>(
string property, TValue value)
{
var xParameter = Expression.Parameter(typeof(TItem));
var type = typeof(TItem).GetProperty(property).PropertyType;
value = ChangeType<TValue>(value);
BinaryExpression body = Expression.Equal(Expression.Property(xParameter, property), Expression.Constant(value, type));
return Expression.Lambda<Func<TItem, bool>>(body, xParameter);
}
What is it for. I check all class references to classes, I look for "..ID" entries. Somewhere I have a type "int" and "int?".
public class BudgetLimit : BaseRecord
{
[Required]
public int DepartmentID { get; set; }
public virtual Department Department { get; set;}
public int? ProjectID { get; set; }
public virtual Project Project { get; set; }
}
You add .AsEnableable after the LINQ statement.
e.g objectdata.AsEnumerable()
enter link description here

Categories

Resources