In my API I give the option to filter the result - similar to the SQL statement WHERE. This works if I use a string field and compare this with a string value:
https://apiurl/items?filterfieldname=name&filterfieldvalue=test
Now I only get back items where the name is "test"
However, if I want to apply this to a bool field (in this case, called "isActive"), to return only active items:
https://apiurl/items?filterfieldname=isActive&filterfieldvalue=true
Then I get the following exception:
System.InvalidOperationException: 'The binary operator Equal is not defined for the types 'System.Nullable`1[System.Boolean]' and 'System.String'.'
I use the following code to create the expression:
static Expression<Func<T, bool>> GetExpressionForFilter<T>(string propertyName, string propertyValue)
{
var nameForWhereClause = propertyName.ToLowerInvariant();
var valueForWhereClause = propertyValue.Trim().ToLowerInvariant();
var parameterExp = Expression.Parameter(typeof(T), "type");
var propertyExp = Expression.Property(parameterExp, nameForWhereClause);
ConstantExpression someValue = Expression.Constant(valueForWhereClause, typeof(string));
return Expression.Lambda<Func<T, bool>>(Expression.Equal(propertyExp, someValue), parameterExp);
}
And apply the expression to my collection:
var condition = GetExpressionForFilter<TEntity>(entitiesResourceParameters.FilterFieldName, entitiesResourceParameters.FilterFieldValue);
condition.Compile();
var collectionAfterFilter = collectionBeforeFilter.Where(condition).AsQueryable();
I tried to convert the property type to a string before comparing it to a "true" or "false" string, but this made no difference.
I would like to be able to enter any type of field (including boolean) and compare this with the value (as string) of this field (for example, "true" or "false"). Is this possible?
You can convert the string value into the type you're trying to compare it with.
var propertyType = property.PropertyType;
var value = Convert.ChangeType(valueForWhereClause, propertyType);
ConstantExpression someValue = Expression.Constant(value, propertyType);
Be aware that Convert.ChangeType can throw an exception if an invalid value is provided (e.g. &filterfieldvalue=foo).
Related
I'm creating a simple way to get the Name and Value of Expressions in C#. However, I found a case I cannot figure out. See the code below:
public void GetValue_Object_TIn_Property_Test()
{
string val = "value";
var tuple = Tuple.Create(new object(), val);
Expression<Func<object, string>> expression = x => tuple.Item2;
Assert.AreEqual(val, expression.GetValue());
}
The .GetValue() method is my extension method.
Basically this expression-tree consists of a LambdaExpression, two MemberExpressions and a ConstantExpression, in that order.
When I try to get the name of tuple.Item2 I get memberExpression.Member.Name from the last MemberExpression. This gives me "tuple" instead of "Item2". How can I get "Item2" instead?
Also, when I try to get the value of the expression, I get the entire tuple instead of Item2. I'm using the following method to get the value:
public override bool TryGetValue(
MemberExpression memberExpression,
out T value
) {
value = default(T);
bool success = false;
var fieldInfo = memberExpression.Member as FieldInfo;
if (success = (fieldInfo != null))
{
var valueObj = fieldInfo.GetValue(expression.Value);
if (success = (valueObj is T || valueObj == null))
{
value = (T)valueObj;
}
}
return success;
}
Where the MemberExpression again is the last MemberExpression. What am I doing wrong here? What is the exact case I am missing?
Thank you in advance
Actually, the tree is a LambdaExpression whose Body is a PropertyExpression that has a Member field with a Name of "Item2" and an Expression that is aFieldExpression for getting the value of tuple. Note that PropertyExpression and FieldExpression are internal types that inherit from MemberExpression.
So you need to get the (Body as MemberExpression).Member.Name instead of the Body.Expression.Member.Name.
Think of the tree as MemberExpression(get Member:Item2 from Expression:MemberExpression(get Member:tuple from Expression:[surrounding class])).
Have you used LINQPad? It's Dump() command can show you this and more.
I have expression tree code used for data filtering, till date it was used on a Generic list List<T> and following code use to work fine:
var parameterType = Expression.Parameter(typeof(T), "obj");
var memberExpression = Expression.Property(parameterType, "Name");
It was easy to create a binary expression as follows and process the result:
var constantExpression = Expression.Constant("Jack",typeof(string));
var finalExpression = Expression.Equal(memberExpression,constantExpression);
var resultFunc = Expression.Lambda<Func<T, bool>>(finalExpression, parameterType).Compile();
// Final Result
sourceList.Where(obj => resultFunc(obj));
Here Name is a property in Type T, as result post Lambda compilation was Func<T,bool>, I use to apply the same to the Where clause of IEnumerable type. Now the underlying system has changed to use the same code on Dictionary<string,T>, so all the Type T values in the collection now have a string Key associated and type T is now accessible as a value of dictionary object. Also I am applying on a IQueryable, which takes an expression tree the final lambda post compilation at source would be Func<KeyValuePair<string,T>,bool>, therefore cannot apply the Value in the final result processing.
Following is the code modification now:
var parameterType = Expression.Parameter(typeof(KeyValuePair<string,T>), "obj");
Following code fails, since now Name property is in Value of the KeyValuePair and we cannot use it as Type T:
var memberExpression = Expression.Property(parameterType, "Name");
Any pointer to make it work or any suggestion to set me in the right direction?
You could get Expression for calling ["name"] item this way:
var nameProperty= Expression.Call(parameterType,
typeof(IDictionary<string, T>).GetMethod("get_Item"),
Expression.Constant("Name"));
or as:
var nameProperty = Expression.Property(parameterType, "Item",
new Expression[] { Expression.Constant("Name") });
Both is call of Item property
EDIT : To get Value from KeyValuePair you have to get property Key, compare it with "Name" and a property Value and compare it with value:
var parameterType = Expression.Parameter(typeof(KeyValuePair<string,T>), "obj");
var value = Expression.Property(parameterType, "Value" );
var key = Expression.Property(parameterType, "Key");
var eq1 = Expression.Equal(key, Expression.Constant("Name"));
var eq2 = Expression.Equal(value, constantExpression);
var and = Expression.And(eq1, eq2);
var lambda = Expression.Lambda(and, parameterType);
I have a piece of code that works properly if you pass two strings. For some reason it doesn't work the same if you pass GUID converted to string.
In more details, if I create a new ExpandoObject and pass string value it works but if I pass GUID converted to string it doesn't.
The code below should compare two parameters. In my example I pass the same two strings. With Equal operator it should return true if the strings are the same. If second parameter GUID converted to string it returns false even strings are the same. dynamicObj.Add(memberName, Guid.Parse(value).ToString());
Not sure what I'm missing. Here is my code.
string value = "642188c7-8e10-e111-961b-0ee1388ccc3b";
string memberName = "State";
string contactValue = value;
var dynamicObj = (IDictionary<string, object>)new ExpandoObject(); dynamicObj.Add(memberName, Guid.Parse(value).ToString());
var expression = Expression.Parameter(typeof(object), "arg");
var binder = Binder.GetMember(CSharpBinderFlags.None, memberName, null, new[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) });
var property = Expression.Dynamic(binder, typeof(object), expression);
var isValid = false;
var right = Expression.Constant(contactValue);
var result = Expression.MakeBinary(ExpressionType.Equal, property, right);
var func = typeof(Func<,>).MakeGenericType(dynamicObj.GetType(), typeof(bool));
var expr = Expression.Lambda(func, result, expression).Compile();
isValid = (bool)expr.DynamicInvoke(dynamicObj);
The GUID parsing will end up with the same string (value) as just using a string literal.
The difference however is the way it is stored in the dictionary: it's of type Dictionary<string, object>. This means that the Object class its == operator will be used which does a reference equality check. The String class, however, overloads this by doing a value equality check.
That's why this returns true:
Console.WriteLine(value == Guid.Parse(value).ToString());
While this returns false:
Console.WriteLine((object) value == (object) Guid.Parse(value).ToString());
Since strings are immutable, Guid.Parse(value).ToString() will create a new string object and do a reference equality check compared with contactValue (which is the same as value). This will evidently return false compared to using value all along which returns true because you never create a new object.
In order to make it work you can just cast the dynamic operand to a string so it will use the correct overload:
var castDyn = Expression.Convert(property, typeof(string));
var result = Expression.MakeBinary(ExpressionType.Equal, castDyn, right);
I have a list of PolicyTran objects:
List<PolicyTran> AllTransactions;
I need to run a query filtering by a property, e.g:
var insureds = AllTransactions.Select(x => x.Insured).ToList();
That works fine, but I need to pass the x.Insured property at runtime since that property could take different values.
I tried doing:
ParameterExpression x = Expression.Parameter(typeof (PolicyTran),"x");
MemberExpression body = Expression.Property(x, propertyName);
var lambda = Expression.Lambda(body,x).Compile();
var result = AllTransactions.Select(lambda).ToList();
In this case propertyName contains "Insured" or any other PolicyTran property.
But I get a compilation error saying that "The type arguments cannot be inferred by the ussage..."
I also tried, but no luck:
ParameterExpression x = Expression.Parameter(typeof (PolicyTran),"x");
var result = AllTransactions.Select(Expression.Lambda<Func<PolicyTran, bool>>(x).Compile()).ToList();
Any ideas??
Your first attempt is closer to the solution. You just need to call the generic version of Lambda:
var lambda = Expression.Lambda<Func<PolicyTran, object>>(body, x).Compile();
in order to get a Func<PolicyTran, object> delegate.
Otherwise the labda will return a simple System.Delegate from which the LINQ .Select will be unable to infer the types.
I have an extension method to dynamically filter Linq to Entities results using string values. It works fine until I use it to filter nullable columns. Here's my code:
public static IOrderedQueryable<T> OrderingHelperWhere<T>(this IQueryable<T> source, string columnName, object value)
{
ParameterExpression table = Expression.Parameter(typeof(T), "");
Expression column = Expression.PropertyOrField(table, columnName);
Expression where = Expression.GreaterThanOrEqual(column, Expression.Constant(value));
Expression lambda = Expression.Lambda(where, new ParameterExpression[] { table });
Type[] exprArgTypes = { source.ElementType };
MethodCallExpression methodCall = Expression.Call(typeof(Queryable),
"Where",
exprArgTypes,
source.Expression,
lambda);
return (IOrderedQueryable<T>)source.Provider.CreateQuery<T>(methodCall);
}
Here's how I use it:
var results = (from row in ctx.MyTable select row)
.OrderingHelperWhere("userId", 5);//userId is nullable column
Here's the exception I'm getting when I use this for nullable table columns:
The binary operator GreaterThanOrEqual is not defined for the types 'System.Nullable`1[System.Int32]' and 'System.Int32'
I couldn't figured this out. What should I do?
I had to convert the value type to the column type using Expression.Convert:
Expression where = Expression.GreaterThanOrEqual(column, Expression.Convert(Expression.Constant(value), column.Type));
A type can be passed to the Expression.Constant function as a second argument. Something like typeof(int?) or, in the case of this question, column.Type.
e.g.
Expression.Constant(value, column.Type)
You can check if a type is nullable by doing: if (typeof(T).Equals(typeof(Nullable<>)) I believe and then proceed to handle that specially. If you can invoke the GetValueOrDefault() method somehow, that would work, or programmbly create the comparison value to be of the same type maybe.
HTH.