Create a Lambda Expression With 3 conditions - c#

I want to create a lambda expression dynamically for this:
(o => o.Year == year && o.CityCode == cityCode && o.Status == status)
and I write this:
var body = Expression.AndAlso(
Expression.Equal(
Expression.PropertyOrField(param, "Year"),
Expression.Constant(year)
),
Expression.Equal(
Expression.PropertyOrField(param, "CityCode"),
Expression.Constant(cityCode)
)
,
Expression.Equal(
Expression.PropertyOrField(param, "Status"),
Expression.Constant(status)
)
);
but for this chunk of code:
Expression.Equal(
Expression.PropertyOrField(param, "Status"),
Expression.Constant(status)
)
I got an error:
Cannot convert from 'System.Linq.Expressions.BinaryExpression' to 'System.Reflection.MethodInfo'
How I can add 3 conditions to a lambda expression?

Expression.AndAlso takes two expressions. There is an overload that takes three arguments, but that third argument is a MethodInfo of a method that implements an and operation on the two operands (there are further restrictions in the case of AndAlso as it doesn't allow the details of truthiness to be overridden, so the first operand will still need to either have a true and false operator or be castable to bool).
So what you want is the equivalent of:
(o => o.Year == year && (o.CityCode == cityCode && o.Status == status))
Which would be:
var body = Expression.AndAlso(
Expression.Equal(
Expression.PropertyOrField(param, "Year"),
Expression.Constant(year)
),
Expression.AndAlso(
Expression.Equal(
Expression.PropertyOrField(param, "CityCode"),
Expression.Constant(cityCode)
),
Expression.Equal(
Expression.PropertyOrField(param, "Status"),
Expression.Constant(status)
)
)
);

There is no method called Expression.AndAlso which can take 3 Expressions as arguments.
Please refer below links,
https://msdn.microsoft.com/en-us/library/bb353520(v=vs.110).aspx
https://msdn.microsoft.com/en-us/library/bb382914(v=vs.110).aspx

Related

How to combine multiple Expressions in a List<Expression> into a single one to use with LINQ?

I have a list with multiple expressions. I had to generate them by analysing the filters (using Kendo UI for MVC).
I succssesfully generate the different parts of the where clause but then i can't combine all of them into one single expression.
For example in my list i have two expressions:
p.N == "t"
p.Y == "x"
At the end my code returns p.N == "t" AndAlso p.Y == "x" so my where clause looks like this: db.Table.Where(p.N == "t" AndAlso p.Y == "x") which is not working and i don't know if it is because im not separating the different criteria with && or some other error i cannot identify.
Here is my code:
var resultExp = expressions.Skip(1).Aggregate(expressions.FirstOrDefault(),
(exp1,exp2) => Expression.And(exp1,exp2));
var r = resultExp as Expression<Func<VwInformacaoUtilizador, bool>>;
My objective is to combine all of the expressions in this list so my where clause correctly queries the database based on the filters i've received.
Thanks in advance
EDIT:
Upon request, here is the code for clarification.
if (filter is CompositeFilterDescriptor complex_Descriptor)
{
foreach (var filterDescriptor in complex_Descriptor.FilterDescriptors)
{
var i = filterDescriptor as FilterDescriptor;
switch (i.Operator)
{
case(FilterOperator.Contains):
body = Expression.Call(
Expression.Property(parameter, i.Member)
, typeof(string).GetMethod("Contains") ?? throw new Exception()
,Expression.Constant(i.Value)
);
expressions.Add(body);
break;
case(FilterOperator.StartsWith):
body = Expression.Call(
Expression.Property(parameter, i.Member)
, typeof(string).GetMethod("StartsWith", new Type[] { typeof(string) }) ?? throw new Exception ()
,Expression.Constant(i.Value)
);
expressions.Add(body);
break;
case(FilterOperator.IsEqualTo):
body = Expression.Equal(Expression.Property(parameter, i.Member),
Expression.Constant(i.Value));
expressions.Add(body);
break;
default:
return exp;
}
}
}
}
var resultExp = expressions.Skip(1).Aggregate(expressions.FirstOrDefault(),
(exp1,exp2) => Expression.And(exp1,exp2));
return resultExp;

Is List<T> a constant, an expression parameter, or something else in Expression Tree?

Let's say I have this lambda expression that I want to write an expression Tree on:
query.Where(d => (allCk && d.FacilityId == facilityId) //1.
||
(!allCk && allSelected && d.FacilityId == facilityId && !ids.Contains(d.Id)) //2.
||
(!allCk && !allSelected && ids.Contains(d.Id))) //3.
This is how I've managed to write it: For brevity, I'll only show the second condition, which the most complex of all (!allCk && allSelected && d.FacilityId == facilityId && !ids.Contains(d.Id)).
private Expression<Func<Documents, bool>> GetDocumentsPredicate(
int facilityId, bool allCk, bool allSelected, List<int> ids)
{
ParameterExpression pe = Expression.Parameter(typeof(Documents), "d");
var listExpr1 = new List<Expression>();
listExpr1.Add(Expression.IsFalse(Expression.Constant(allCk))); //allCk
listExpr1.Add(Expression.Constant(allSelected)); //allSelected
var facilityParam = Expression.Constant(facilityId); //facility
Expression facilityIdProp = Expression.Property(pe, "FacilityId");
Expression facilityIdEql = Expression.Equal(facilityIdProp, facilityParam);
listExpr1.Add(facilityIdEql);
//This is where I'm having trouble... Is ids a parameter or a constant???
//Assuming it's a parameter...
ParameterExpression peIds = Expression.Parameter(typeof(List<int>), "ids");
Expression listContains = Expression.Call(
pIds,
typeof(List<int>).GetMethod("Contains"),
Expression.Property(pe, "Id"));
listExpr1.Add(Expression.Call(peIds, listContains, Expression.Property(pe, "Id")));
var exp1 = listExpr1
.Skip(1)
.Aggregate(listExpr1[0], Expression.AndAlso);
//...
}
I'm getting an error at this line: listExpr1.Add(Expression.Call(pIds, listContains, Expression.Property(pe, "Id"))); // Cannot convert 'Linq.Expression.ParameterExpression' to 'System.Reflection.MethodInfo'`. So, it's complaining about the pIds.
So What is ids in here, a constant, a parameter, or something else?
Thanks for helping
EDIT
This is how I wrote the entire method
private Expression<Func<Documents, bool>> GetDocumentsPredicate(
int facilityId, bool allCk, bool allSelected, List<int> ids)
{
ParameterExpression pe = Expression.Parameter(typeof(Document), "d");
Expression constIds = Expression.Constant(ids);
Expression allCkParam = Expression.Constant(allCk);
Expression allSelectedParam = Expression.Constant(allSelected);
Expression listContains = Expression.Call(
constIds,
typeof(List<int>).GetMethod("Contains"),
Expression.Property(pe, "Id"));
/*(allCk && d.FacilityId == facilityId) ==> exp0*/
var facilityParam = Expression.Constant(facilityId);
Expression facilityIdProp = Expression.Property(pe, "FacilityId");
Expression facilityIdEql = Expression.Equal(facilityIdProp, facilityParam);
Expression exp0 = Expression.AndAlso(allCkParam, facilityIdEql);
/*(!allCk && allSelected && d.FacilityId == facilityId && !ids.Contains(d.Id)))
==> exp1 */
var listExpr1 = new List<Expression>();
listExpr1.Add(Expression.IsFalse(allCkParam));
listExpr1.Add(allSelectedParam);
listExpr1.Add(facilityIdEql);
listExpr1.Add(Expression.IsFalse(listContains));
var exp1 = listExpr1
.Skip(1)
.Aggregate(listExpr1[0], Expression.AndAlso);
/* (!allCk && !allSelected && ids.Contains(d.Id)) ==> exp2 */
var listExpr2 = new List<Expression>();
listExpr2.Add(Expression.IsFalse(allCkParam));
listExpr2.Add(Expression.IsFalse(allSelectedParam));
listExpr1.Add(listContains);
var exp2 = listExpr2
.Skip(1)
.Aggregate(listExpr2[0], Expression.AndAlso);
var listExpr = new List<Expression> { exp0, exp1, exp2 };
var exp = listExpr
.Skip(1)
.Aggregate(listExpr[0], Expression.OrElse);
var expr =
Expression.Lambda<Func<Document, bool>>(exp,
new ParameterExpression[] { pe });
return expr;
}
This is the result I'm getting when hovering over the returned value
d => (((False AndAlso (d.FacilityId == 1))
OrElse
(((IsFalse(False) AndAlso False) AndAlso (d.FacilityId == 1))
AndAlso
IsFalse(value(System.Collections.Generic.List`1[System.Int32]).Contains(d.Id))))
OrElse
((IsFalse(False) AndAlso IsFalse(False))
AndAlso
value(System.Collections.Generic.List`1[System.Int32]).Contains(d.Id)))
A simple test with this statement var count = query.Count(); produces an exception: Unknown LINQ expression of type 'IsFalse'.
Let's sum up. You are getting an overload resolution error on this call:
Expression.Call(peIds, listContains, Expression.Property(pe, "Id"))
peIds is of type ParameterExpression.
listContains is of type Expression.
The third argument is of type MemberExpression.
Now, look at the list of overloads to Expression.Call. None of those match these three arguments.
The compiler's best guess is that you're trying to call this one:
https://msdn.microsoft.com/en-us/library/dd324092(v=vs.110).aspx
hence the error: that the first argument is not a method info.
Choose which overload you wish to call and then supply arguments of those types.
But frankly I don't see why you're doing this at all; I don't see what portion of the original lambda this is intended to represent. Why is the line not simply listExpr1.Add(listContains); ???
ids is not a parameter, as far as the expression tree is concerned, because it is not passed to Func<...>. The list of ids against which you need to match is built into the structure of the expression, hence Expression.Constant is the proper way to handle it.
However, that's not the whole story: since ids is a reference type, only a reference to it is going into the structure of the expression. Therefore, if you decide to change the content of the list after constructing the expression, the meaning of the ids.Contains is going to change.
If this is not the effect that you would like to achieve, make a copy of the list:
Expression constIds = Expression.Constant(ids.ToList());

Linq-SQL Generic GetById with a custom Expression

I've created a generic to target tables in my database. I've also added a generic .where() with an expression containing only 1 parameter in the lambda expression. Is there a way that I can add more than 1 parameter in the same expression? Please ignore the fact that the below method returns 1 item, I would like to make it return a IList.
Here's my method:
public virtual T GetById( Int32 id, String someStringColumn )
{
ParameterExpression itemParameter = Expression.Parameter( typeof( T ), "item" );
var whereExpression = Expression.Lambda<Func<T, Boolean>>
(
Expression.Equal( Expression.Property( itemParameter, "Id" ), Expression.Constant( id ) ),
new[] { itemParameter }
);
return context.Set<T>().Where( whereExpression ).FirstOrDefault();
}
My actual intentions for this method is to later perform a Contains() on "string" properties of the target table "T". I would like to do something like below and append to the above Expression a check if the String property contains a value in the "someStringColumn". Just an insight, the "someStringColumn" will be a general search string on my page past by Ajax on every back-end call.
var properties = item.GetType().GetProperties().Where( p => p.PropertyType == typeof( string ) ).ToArray();
for ( Int32 i = 0; i < properties.Length; i++ )
{
}
I'm trying to achieve something like this in a non-generic method:
public override List<TableInDatabase> List( PagingModel pm, CustomSearchModel csm )
{
String[] qs = ( pm.Query ?? "" ).Split( ' ' );
return context.TableInDatabase
.Where( t => ( qs.Any( q => q != "" ) ? qs.Contains( t.ColumnName) : true ) )
.OrderBy( String.Format( "{0} {1}", pm.SortBy, pm.Sort ) )
.Skip( pm.Skip )
.Take( pm.Take )
.ToList();
}
If I understand correctly, you are seeking for something like this:
var item = Expression.Parameter(typeof(T), "item");
Expression body = Expression.Equal(Expression.Property(item, "Id"), Expression.Constant(id));
if (!string.IsNullOrEmpty(someStringColumn))
{
var properties = typeof(T).GetProperties().Where(p => p.PropertyType == typeof(string)).ToList();
if (properties.Any())
body = Expression.AndAlso(body,
properties.Select(p => (Expression)Expression.Call(
Expression.Property(item, p), "Contains", Type.EmptyTypes, Expression.Constant(someStringColumn))
).Aggregate(Expression.OrElse));
}
var whereExpression = Expression.Lambda<Func<T, bool>>(body, item);
i.e. build Contains expression for each string property, combine them using Or and finally combine the result with the first condition using And.

Create a Linq Tree -> Any method Expression

I'm trying to write manually this linq sentence:
IEnumerable<C> classes = this.cs
.Where(c => c.Properties.Any(p =>
p.Key.Equals("key1") &&
p.Value.Equals("v1")
)
);
As you can see I'm calling to Any extension method on a c.Properties property of C class.
Then, inside Any method I've written an And with two Equals.
I need to create this sentence using Expression builders.
Up to now, I've been able to write this:
Type entityType = typeof(T);
PropertyInfo collectionPropertyInfo = entityType.GetProperty("Properties");
if (collectionPropertyInfo == null)
throw new ArgumentException(
string.Format(
"{0} collection doesn't appear in {1}",
"Properties",
entityType.Name
)
);
Type collGenericType = collectionPropertyInfo
.GetType()
.GetGenericArguments()
.FirstOrDefault();
MemberExpression collectionMemberExpression = Expression.Property(
Expression.Parameter(entityType),
collectionPropertyInfo
);
MethodInfo anyMethod = typeof(Enumerable)
.GetMethods()
.Where(m =>
m.Name.Equals("Any") &&
m.GetParameters().Length == 2
)
.Single()
.MakeGenericMethod(collGenericType);
BinaryExpression innerConditionExpression = Expression.AndAlso(
Expression.Equal(
Expression.Property(Expression.Parameter(collGenericType, "p"), "Key"),
Expression.Constant("key1")
),
Expression.Equal(
Expression.Property(Expression.Parameter(collGenericType, "p"), "Value"),
Expression.Constant("v1")
)
);
I don't know how to write Call to Any method where instance is collectionMemberExpression and iiner condition is innerConditionExpression.

How do I build Expression Call for Any Method with generic parameter

I'm just trying make the same expression like below using Linq.Expression:
Expression<Func<Organization, bool>> expression = #org =>
#org.OrganizationFields.Any(a =>
a.CustomField.Name == field.Name &&
values.Contains(a.Value));
In this example above I have an entity called Organization and it has a property called OrganizationsFields as IEnumerable and I want to find any occurrence that match with Any parameter expression.
I just using the code below to generate expression dynamically:
string[] values = filter.GetValuesOrDefault();
ParameterExpression parameter = Expression.Parameter(typeof(T), "org");
Expression organizationFields = Expression.Property(parameter, "OrganizationFields");
MethodInfo any = typeof(Enumerable)
.GetMethods()
.FirstOrDefault(a => a.Name == "Any" && a.GetParameters().Count() == 2)
.MakeGenericMethod(typeof(OrganizationField));
Func<OrganizationField, bool> functionExpression = a =>
a.CustomField.Name == filter.Name && values.Contains(a.Value);
Expression functionParam = Expression.Constant(
functionExpression,
typeof(Func<OrganizationField, bool>));
Expression call = Expression.Call(organizationFields, any, functionParam);
return Expression.Lambda<Func<T, bool>>(call, parameter);
The problems occur when I call the method Expression.Call it throw an ArgumentExeption
Can anyone help me?
Regards
Here you go
var org = Expression.Parameter(typeof(Organization), "org");
Expression<Func<OrganizationField, bool>> predicate =
a => a.CustomField.Name == filter.Name && values.Contains(a.Value);
var body = Expression.Call(typeof(Enumerable), "Any", new[] { typeof(OrganizationField) },
Expression.PropertyOrField(org, "OrganizationFields"), predicate);
var lambda = Expression.Lambda<Func<Organization, bool>>(body, org);
The essential part (covering your post title) is the following useful Expression.Call overload
public static MethodCallExpression Call(
Type type,
string methodName,
Type[] typeArguments,
params Expression[] arguments
)
Also note that the predicate argument of Any must be passed to Expression.Call as Expression<Func<...>>.

Categories