I followed this thread: link text
Jason gives an example:
public static Expression<TDelegate> AndAlso<TDelegate>(this Expression<TDelegate> left, Expression<TDelegate> right)
{
return Expression.Lambda<TDelegate>(Expression.AndAlso(left, right), left.Parameters);
}
and its usage as such:
Expression<Func<Client, bool>> clientWhere = c => true;
if (filterByClientFName)
{
clientWhere = clientWhere.AndAlso(c => c.ClientFName == searchForClientFName);
}
if (filterByClientLName)
{
clientWhere = clientWhere.AndAlso(c => c.ClientLName == searchForClientLName);
}
I have a orders table and i followed the above example, changing column names, and i get the similar error that the post creator had
The binary operator AndAlso is not defined for the types 'System.Func2[Models.Order,System.Boolean]' and 'System.Func2[Models.Order,System.Boolean]'.
Anyone have any thoughts on what I am missing?
UPDATED:
Eric, I further followed what the user of the previous post was asking, here link text
The user has this
Expression<Func<Client, bool>> clientWhere = c => true;
Expression<Func<Order, bool>> orderWhere = o => true;
Expression<Func<Product, bool>> productWhere = p => true;
if (filterByClient)
{
clientWhere = c => c.ClientID == searchForClientID;
}
Now if he were to have various conditions in filterByClient, say he either has clientid and/or some other column name, how would one build the clientWhere expression?
You're attempting to build an expression tree that represents this:
c => true && c.ClientFName == searchForClientFName
You are actually building an expression tree that represents this:
c => c=> true && c => c.ClientFName == searchForClientFName
which makes no sense at all.
Now, you might naively think that this will work:
public static Expression<TDelegate> AndAlso<TDelegate>(this Expression<TDelegate> left, Expression<TDelegate> right)
{
// NOTICE: Combining BODIES:
return Expression.Lambda<TDelegate>(Expression.AndAlso(left.Body, right.Body), left.Parameters);
}
That would produce in your case something representing
c => true && c.ClientFName == searchForClientFName
Which looks right. But in fact this is fragile. Suppose you had
... d => d.City == "London" ...
... c => c.ClientName == "Fred Smith" ...
and you used this method to combine them. You'd get an object representing
c => d.City == "London" && c.ClientName == "Fred Smith"
What the heck is d doing in there?
Furthermore, parameters are matched by object identity, not by parameter name. If you do this
... c => c.City == "London" ...
... c => c.ClientName == "Fred Smith" ...
and combine them into
c => c.City == "London" && c.ClientName == "Fred Smith"
you're in the same boat; the "c" in "c.City" is a different c than the other two.
What you actually need to do is make a third parameter object, substitute it in the bodies of both lambdas for every occurence of their parameters, and then build up a new lambda expression tree from the resulting substituted bodies.
You can build a substitution engine by writing a visitor that passes over the expression tree body, rewriting it as it goes.
It was difficult for me to understand hvd's answer so I created some code to explain it in a different way. hvd should get the credit for suggesting the ExpressionVisitor. I just couldn't understand the example in the context of Linq to X type input functions I was using.
I hope this helps somebody else coming to the question from that perspective.
Also, I created the combining code as extension methods to make it a little easier to use.
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
namespace ConsoleApplication3
{
class Program
{
static void Main(string[] args)
{
var combined = TryCombiningExpressions(c => c.FirstName == "Dog", c => c.LastName == "Boy");
Console.WriteLine("Dog Boy should be true: {0}", combined(new FullName { FirstName = "Dog", LastName = "Boy" }));
Console.WriteLine("Cat Boy should be false: {0}", combined(new FullName { FirstName = "Cat", LastName = "Boy" }));
Console.ReadLine();
}
public class FullName
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
public static Func<FullName, bool> TryCombiningExpressions(Expression<Func<FullName, bool>> func1, Expression<Func<FullName, bool>> func2)
{
return func1.CombineWithAndAlso(func2).Compile();
}
}
public static class CombineExpressions
{
public static Expression<Func<TInput, bool>> CombineWithAndAlso<TInput>(this Expression<Func<TInput, bool>> func1, Expression<Func<TInput, bool>> func2)
{
return Expression.Lambda<Func<TInput, bool>>(
Expression.AndAlso(
func1.Body, new ExpressionParameterReplacer(func2.Parameters, func1.Parameters).Visit(func2.Body)),
func1.Parameters);
}
public static Expression<Func<TInput, bool>> CombineWithOrElse<TInput>(this Expression<Func<TInput, bool>> func1, Expression<Func<TInput, bool>> func2)
{
return Expression.Lambda<Func<TInput, bool>>(
Expression.AndAlso(
func1.Body, new ExpressionParameterReplacer(func2.Parameters, func1.Parameters).Visit(func2.Body)),
func1.Parameters);
}
private class ExpressionParameterReplacer : ExpressionVisitor
{
public ExpressionParameterReplacer(IList<ParameterExpression> fromParameters, IList<ParameterExpression> toParameters)
{
ParameterReplacements = new Dictionary<ParameterExpression, ParameterExpression>();
for (int i = 0; i != fromParameters.Count && i != toParameters.Count; i++)
ParameterReplacements.Add(fromParameters[i], toParameters[i]);
}
private IDictionary<ParameterExpression, ParameterExpression> ParameterReplacements { get; set; }
protected override Expression VisitParameter(ParameterExpression node)
{
ParameterExpression replacement;
if (ParameterReplacements.TryGetValue(node, out replacement))
node = replacement;
return base.VisitParameter(node);
}
}
}
}
If you need it i created a small fluent library to create lambda functions on the fly without directly coping with System.Linq.Expressions. And it can easily handle the kind of situation. Just to give an example:
static void Main(string[] args)
{
var firstNameCompare = ExpressionUtil.GetComparer<FullName>((a) => a.FirstName);
var lastNameCompare = ExpressionUtil.GetComparer<FullName>((a) => a.LastName);
Func<FullName, bool> combined = (a) => firstNameCompare(a, "Dog") && lastNameCompare(a, "Boy");
var toCheck = new FullName {FirstName = "Dog", LastName = "Boy"};
Console.WriteLine("Dog Boy should be true: {0}", combined(toCheck));
toCheck = new FullName {FirstName = "Cat", LastName = "Boy"};
Console.WriteLine("Cat Boy should be false: {0}", combined(toCheck));
Console.ReadLine();
}
The GetComparer methods seek for the property passed as expression and find ho to get its value, then it builds a new Expression that will handle the comparaison.
At the end the two functions are evaluated calling the "combined" function.
If you need more verifications you could use an array and iterate on it inside the "combined lambda"
The code and documentation for the library are here: Kendar Expression Builder
While the nuget package is here: Nuget Expression Builder
I tried to implement this kind of stuff. Took me a day to find out.
My solution is based on filter in a loop based on a Array of predicate.
As a note, it s totally Generic and based Reflection because the only information about class and field are String.
To make it simple, i call directly the Model class but in a project you should go by a controler who is calling the Model.
So here we go :
The Model part where T is a Generic in the class
public class DALXmlRepository<T> where T : class
{
public T GetItem(Array predicate)
{
IQueryable<T> QueryList = null;
QueryList = ObjectList.AsQueryable<T>().Where((Expression<Func<T, bool>>)predicate.GetValue(0));
for (int i = 1; i < predicate.GetLength(0); i++)
{
QueryList = QueryList.Where((Expression<Func<T, bool>>)predicate.GetValue(i));
}
if (QueryList.FirstOrDefault() == null)
throw new InvalidOperationException(this.GetType().GetGenericArguments().First().Name + " not found.");
return QueryList.FirstOrDefault();
}
}
Now the LambdaExpression Builder, it's a base one(with String type or something else) , you can improve it with more functionnality :
private static Expression BuildLambdaExpression(Type GenericArgument, string FieldName, string FieldValue)
{
LambdaExpression lambda = null;
Expression Criteria = null;
Random r = new Random();
ParameterExpression predParam = Expression.Parameter(GenericArgument, r.Next().ToString());
if (GenericArgument.GetProperty(FieldName).PropertyType == typeof(string))
{
Expression left = Expression.PropertyOrField(predParam, FieldName);
Expression LefttoUpper = Expression.Call(left, "ToUpper", null, null);
//Type du champ recherché
Type propType = GenericArgument.GetProperty(FieldName).PropertyType;
Expression right = Expression.Constant(FieldValue, propType);
Expression RighttoUpper = Expression.Call(right, "ToUpper", null, null);
Criteria = Expression.Equal(LefttoUpper, RighttoUpper);
}
else
{
Expression left = Expression.PropertyOrField(predParam, FieldName);
Type propType = GenericArgument.GetProperty(FieldName).PropertyType;
Expression right = Expression.Constant(Convert.ChangeType(FieldValue, propType), propType);
Criteria = Expression.Equal(left, right);
}
lambda = Expression.Lambda(Criteria, predParam);
return lambda;
}
Now the Calling function :
public static Hashtable GetItemWithFilter(string Entity, XMLContext contextXML, Hashtable FieldsNameToGet, Hashtable FieldFilter)
{
//Get the type
Type type = Type.GetType("JP.Model.BO." + Entity + ", JPModel");
Type CtrlCommonType = typeof(CtrlCommon<>).MakeGenericType( type );
//Making an instance DALXmlRepository<xxx> XMLInstance = new DALXmlRepository<xxx>(contextXML);
ConstructorInfo ci = CtrlCommonType.GetConstructor(new Type[] { typeof(XMLContext), typeof(String) });
IControleur DalInstance = (IControleur)ci.Invoke(new object[] { contextXML, null });
//Building the string type Expression<func<T,bool>> to init the array
Type FuncType = typeof(Func<,>).MakeGenericType( type ,typeof(bool));
Type ExpressType = typeof(Expression<>).MakeGenericType(FuncType);
Array lambda = Array.CreateInstance(ExpressType,FieldFilter.Count);
MethodInfo method = DalInstance.GetType().GetMethod("GetItem", new Type[] { lambda.GetType() });
if (method == null)
throw new InvalidOperationException("GetItem(Array) doesn't exist for " + DalInstance.GetType().GetGenericArguments().First().Name);
int j = 0;
IDictionaryEnumerator criterias = FieldFilter.GetEnumerator();
criterias.Reset();
while (criterias.MoveNext())
{
if (!String.IsNullOrEmpty(criterias.Key.ToString()))
{
lambda.SetValue(BuildLambdaExpression(type, criterias.Key.ToString(), criterias.Value.ToString()),j);
}
else
{
throw new JPException(JPException.MessageKey.CONTROLER_PARAMFIELD_EMPTY, "GetItemWithFilter", criterias.Key.ToString());
}
j++;
}
Object item = method.Invoke(DalInstance, new object[] { lambda });
}
The argument are :
String Entity : Entity class name.
XMLContext : it s the unit of work of the repository, argument i use to initialize the Model class
Hashtable FieldsNameToGet : Index/value of the list of the field i want to get back
Hashtable FieldFilter : the key/Value with FieldName/Content used to make the Lambda expression
Good Luck.
Related
I have a method, GetSearchExpression, defined as:
private Expression<Func<T, bool>> GetSearchExpression(
string targetField, ExpressionType comparison, object value, IEnumerable<EnumerableResultQualifier> qualifiers = null);
At a high level, the method takes in a Field or Property (such as Order.Customer.Name), a comparison type (like Expression.Equals), and a value (like "Billy"), then returns a lambda expression suitable for input to a Where statement o => o.Customer.Name == "Billy"}.
Recently, I discovered an issue. Sometimes, the field I need is actually the field of an item in a collection (like Order.StatusLogs.First().CreatedDate).
I feel like that should be easy. The code that creates the left side of the expression (above, o => o.Customer.Name) is as follows:
var param = Expression.Parameter(typeof(T), "t");
Expression left = null;
//turn "Order.Customer.Name" into List<string> { "Customer", "Name" }
var deQualifiedFieldName = DeQualifyFieldName(targetField, typeof(T));
//loop through each part and grab the specified field or property
foreach (var part in deQualifiedFieldName)
left = Expression.PropertyOrField(left == null ? param : left, part);
It seems like I should be able to revise this to check if the field/property exists, and if not, try to call a method by that name instead. It would look like this:
var param = Expression.Parameter(typeof(T), "t");
Expression left = null;
var deQualifiedFieldName = DeQualifyFieldName(targetField, typeof(T));
var currentType = typeof(T);
foreach (var part in deQualifiedFieldName)
{
//this gets the Type of the current "level" we're at in the hierarchy passed via TargetField
currentType = SingleLevelFieldType(currentType, part);
if (currentType != null) //if the field/property was found
{
left = Expression.PropertyOrField(left == null ? param : left, part);
}
else
{ //if the field or property WASN'T found, it might be a method
var method = currentType.GetMethod(part, Type.EmptyTypes); //doesn't accept parameters
left = Expression.Call(left, method);
currentType = method.ReturnType;
}
}
The problem is that statement near the end (var method currentType.GetMethod(part, Type.EmptyTypes);). Turns out "First" and "Last" don't exist for IEnumerable objects, so I get a null exception when I try to use my Method object. In fact, the only way I can EVER them to show up in a GetMethod() call is by calling typeof(Enumerable).GetMethod(). That's useless of course, because then I get a static method in return rather than the instance method I need.
As a side-note: I tried using the static method, but Entity Framework throws a fit and won't accept it as part of the lambda.
I need help getting the instance MethodInfo of IEnumerable.First() & Last(). Please help!
My first attempt would be to identify if the instance is Enumerable<T> and treat the member name as method instead of a property/field like this
public static class ExpressionUtils
{
public static Expression<Func<T, bool>> MakePredicate<T>(
string memberPath, ExpressionType comparison, object value)
{
var param = Expression.Parameter(typeof(T), "t");
var right = Expression.Constant(value);
var left = memberPath.Split('.').Aggregate((Expression)param, (target, memberName) =>
{
if (typeof(IEnumerable).IsAssignableFrom(target.Type))
{
var enumerableType = target.Type.GetInterfaces()
.Single(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>));
return Expression.Call(typeof(Enumerable), memberName, enumerableType.GetGenericArguments(), target);
}
return Expression.PropertyOrField(target, memberName);
});
var body = Expression.MakeBinary(comparison, left, right);
return Expression.Lambda<Func<T, bool>>(body, param);
}
}
and try use it as follows
var predicate = ExpressionUtils.MakePredicate<Order>(
"StatusLogs.First.CreatedDate", ExpressionType.GreaterThanOrEqual, new DateTime(2016, 1, 1));
The possible methods are First, FirstOrDefault, Last, LastOrDefault, Singe and SingleOrDefault.
But then you'll find that from the above methods only FirstOrDefault is supported in EF predicates.
Hence we can hardcode that call for collection types and do not include it in the accessors like this
public static class ExpressionUtils
{
public static Expression<Func<T, bool>> MakePredicate2<T>(
string memberPath, ExpressionType comparison, object value)
{
var param = Expression.Parameter(typeof(T), "t");
var right = Expression.Constant(value);
var left = memberPath.Split('.').Aggregate((Expression)param, (target, memberName) =>
{
if (typeof(IEnumerable).IsAssignableFrom(target.Type))
{
var enumerableType = target.Type.GetInterfaces()
.Single(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>));
target = Expression.Call(typeof(Enumerable), "FirstOrDefault", enumerableType.GetGenericArguments(), target);
}
return Expression.PropertyOrField(target, memberName);
});
var body = Expression.MakeBinary(comparison, left, right);
return Expression.Lambda<Func<T, bool>>(body, param);
}
}
and use it as follows
var predicate = ExpressionUtils.MakePredicate<Order>(
"StatusLogs.CreatedDate", ExpressionType.GreaterThanOrEqual, new DateTime(2016, 1, 1));
P.S. While this will work, it might not produce the intended result. IEnumerable<T> navigation property means one-to-many relationship and assuming that the condition should apply only for the first (whatever that means in database, it's rather random) element does not make much sense. I would rather imply Any and try to build expression like this in the above case
t => t.StatusLogs.Any(s => s.CreatedDate >= new DateTime(2016, 1, 1))
or support FirstOrDefault, Any, All, (eventually Count, Sum, Min, Max) and handle them differently inside the builder.
Still IMO for collections Any is the most logical equivalent of the single entity criteria.
But all that will be another story (question).
UPDATE: Initially I was thinking to stop here, but for the sake of completeness, here is a sample implementation of the Any concept:
public static class ExpressionUtils
{
public static Expression<Func<T, bool>> MakePredicate<T>(string memberPath, ExpressionType comparison, object value)
{
return (Expression<Func<T, bool>>)MakePredicate(
typeof(T), memberPath.Split('.'), 0, comparison, value);
}
static LambdaExpression MakePredicate(Type targetType, string[] memberNames, int index, ExpressionType comparison, object value)
{
var parameter = Expression.Parameter(targetType, targetType.Name.ToCamel());
Expression target = parameter;
for (int i = index; i < memberNames.Length; i++)
{
if (typeof(IEnumerable).IsAssignableFrom(target.Type))
{
var itemType = target.Type.GetInterfaces()
.Single(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable<>))
.GetGenericArguments()[0];
var itemPredicate = MakePredicate(itemType, memberNames, i, comparison, value);
return Expression.Lambda(
Expression.Call(typeof(Enumerable), "Any", new[] { itemType }, target, itemPredicate),
parameter);
}
target = Expression.PropertyOrField(target, memberNames[i]);
}
if (value != null && value.GetType() != target.Type)
value = Convert.ChangeType(value, target.Type);
return Expression.Lambda(
Expression.MakeBinary(comparison, target, Expression.Constant(value)),
parameter);
}
static string ToCamel(this string s)
{
if (string.IsNullOrEmpty(s) || char.IsLower(s[0])) return s;
if (s.Length < 2) return s.ToLower();
var chars = s.ToCharArray();
chars[0] = char.ToLower(chars[0]);
return new string(chars);
}
}
so for this sample model
public class Foo
{
public ICollection<Bar> Bars { get; set; }
}
public class Bar
{
public ICollection<Baz> Bazs { get; set; }
}
public class Baz
{
public ICollection<Detail> Details { get; set; }
}
public class Detail
{
public int Amount { get; set; }
}
the sample expression
var predicate = ExpressionUtils.MakePredicate<Foo>(
"Bars.Bazs.Details.Amount", ExpressionType.GreaterThan, 1234);
produces
foo => foo.Bars.Any(bar => bar.Bazs.Any(baz => baz.Details.Any(detail => detail.Amount > 1234)))
What you are possibly looking for is System.Linq.Enumerable.First<T>(this IEnumerable<T> source) etc, so: start at typeof(System.Linq.Enumerable) and work from there. Note: you mention IEnumerable<T>, but it is possible that you actually mean IQueryable<T>, in which case you want Queryable.First<T>(this IQueryable<T> source) etc. Maybe this difference (between Enumerable and Queryable) is why EF "throws a fit".
Thank you to Marc and Ivan for their input. They deserve credit as without their help I would have spent much longer finding a solution. However, as neither answer solved the issue I was having, I'm posting the solution that worked for me (successfully applying criteria as well as successfully querying against an EF data source):
private Expression<Func<T, bool>> GetSearchExpression(string targetField, ExpressionType comparison, object value, string enumMethod)
{
return (Expression<Func<T, bool>>)MakePredicate(DeQualifyFieldName(targetField, typeof(T)), comparison, value, enumMethod);
}
private LambdaExpression MakePredicate(string[] memberNames, ExpressionType comparison, object value, string enumMethod = "Any")
{
//create parameter for inner lambda expression
var parameter = Expression.Parameter(typeof(T), "t");
Expression left = parameter;
//Get the value against which the property/field will be compared
var right = Expression.Constant(value);
var currentType = typeof(T);
for (int x = 0; x < memberNames.Count(); x++)
{
string memberName = memberNames[x];
if (FieldExists(currentType, memberName))
{
//assign the current type member type
currentType = SingleLevelFieldType(currentType, memberName);
left = Expression.PropertyOrField(left == null ? parameter : left, memberName);
//mini-loop for non collection objects
if (!currentType.IsGenericType || (!(currentType.GetGenericTypeDefinition() == typeof(IEnumerable<>) ||
currentType.GetGenericTypeDefinition() == typeof(ICollection<>))))
continue;
///Begin loop for collection objects -- this section can only run once
//get enum method
if (enumMethod.Length < 2) throw new Exception("Invalid enum method target.");
bool negateEnumMethod = enumMethod[0] == '!';
string methodName = negateEnumMethod ? enumMethod.Substring(1) : enumMethod;
//get the interface sub-type
var itemType = currentType.GetInterfaces()
.Single(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable<>))
.GetGenericArguments()[0];
//generate lambda for single item
var itemPredicate = MakeSimplePredicate(itemType, memberNames[++x], comparison, value);
//get method call
var staticMethod = typeof(Enumerable).GetMember(methodName).OfType<MethodInfo>()
.Where(m => m.GetParameters().Length == 2)
.First()
.MakeGenericMethod(itemType);
//generate method call, then break loop for return
left = Expression.Call(null, staticMethod, left, itemPredicate);
right = Expression.Constant(!negateEnumMethod);
comparison = ExpressionType.Equal;
break;
}
}
//build the final expression
var binaryExpression = Expression.MakeBinary(comparison, left, right);
return Expression.Lambda<Func<T, bool>>(binaryExpression, parameter);
}
static LambdaExpression MakeSimplePredicate(Type inputType, string memberName, ExpressionType comparison, object value)
{
var parameter = Expression.Parameter(inputType, "t");
Expression left = Expression.PropertyOrField(parameter, memberName);
return Expression.Lambda(Expression.MakeBinary(comparison, left, Expression.Constant(value)), parameter);
}
private static Type SingleLevelFieldType(Type baseType, string fieldName)
{
Type currentType = baseType;
MemberInfo match = (MemberInfo)currentType.GetField(fieldName) ?? currentType.GetProperty(fieldName);
if (match == null) return null;
return GetFieldOrPropertyType(match);
}
public static Type GetFieldOrPropertyType(MemberInfo field)
{
return field.MemberType == MemberTypes.Property ? ((PropertyInfo)field).PropertyType : ((FieldInfo)field).FieldType;
}
/// <summary>
/// Remove qualifying names from a target field. For example, if targetField is "Order.Customer.Name" and
/// targetType is Order, the de-qualified expression will be "Customer.Name" split into constituent parts
/// </summary>
/// <param name="targetField"></param>
/// <param name="targetType"></param>
/// <returns></returns>
public static string[] DeQualifyFieldName(string targetField, Type targetType)
{
return DeQualifyFieldName(targetField.Split('.'), targetType);
}
public static string[] DeQualifyFieldName(string[] targetFields, Type targetType)
{
var r = targetFields.ToList();
foreach (var p in targetType.Name.Split('.'))
if (r.First() == p) r.RemoveAt(0);
return r.ToArray();
}
I included related methods in case someone actually needs to sort through this at some point. :)
Thanks again!
public List<DbSet> Get(String q = null)
{
List<DbSet> objs = new List<DbSet>();
if (!string.IsNullOrEmpty(q) && q != "undefined")
{
objs = from dealer in db.Dealers
where dealer.Contains
......(?????)
}
}
I have some 4 DbSets in my dbcontext class. I am able to run a search like this
objs = from dealer in db.Dealers
where dealer.Name.Contains(q)
However I want to be able to do something similar except do the search against all the fields in dealer, and not just name. All the fields are strings
EDIT
Okay so I am starting to think this is not the best way to do what I'm trying to achieve. I'm looking into something called "Full Text Search". Can someone either explain to me how this works in entity or give me a link to a good resource
You could write a linq extension method:
Checkout my blog post.
http://jnye.co/Posts/7/generic-iqueryable-or-search-on-multiple-properties-using-expression-trees
(classes are also in github: https://github.com/ninjanye/SearchExtensions)
public static IQueryable<T> Search<T>(this IQueryable<T> source, string searchTerm, params Expression<Func<T, string>>[] stringProperties)
{
if (String.IsNullOrEmpty(searchTerm))
{
return source;
}
var searchTermExpression = Expression.Constant(searchTerm);
//Variable to hold merged 'OR' expression
Expression orExpression = null;
//Retrieve first parameter to use accross all expressions
var singleParameter = stringProperties[0].Parameters.Single();
//Build a contains expression for each property
foreach (var stringProperty in stringProperties)
{
//Syncronise single parameter accross each property
var swappedParamExpression = SwapExpressionVisitor.Swap(stringProperty, stringProperty.Parameters.Single(), singleParameter);
//Build expression to represent x.[propertyX].Contains(searchTerm)
var containsExpression = BuildContainsExpression(swappedParamExpression, searchTermExpression);
orExpression = BuildOrExpression(orExpression, containsExpression);
}
var completeExpression = Expression.Lambda<Func<T, bool>>(orExpression, singleParameter);
return source.Where(completeExpression);
}
private static Expression BuildOrExpression(Expression existingExpression, Expression expressionToAdd)
{
if (existingExpression == null)
{
return expressionToAdd;
}
//Build 'OR' expression for each property
return Expression.OrElse(existingExpression, expressionToAdd);
}
private static MethodCallExpression BuildContainsExpression<T>(Expression<Func<T, string>> stringProperty, ConstantExpression searchTermExpression)
{
return Expression.Call(stringProperty.Body, typeof(string).GetMethod("Contains"), searchTermExpression);
}
You will also need this class:
//Create SwapVisitor to merge the parameters from each property expression into one
public class SwapVisitor : ExpressionVisitor
{
private readonly Expression from, to;
public SwapVisitor(Expression from, Expression to)
{
this.from = from;
this.to = to;
}
public override Expression Visit(Expression node)
{
return node == from ? to : base.Visit(node);
}
public static Expression Swap(Expression body, Expression from, Expression to)
{
return new SwapVisitor(from, to).Visit(body);
}
}
You can then write something like:
db.Dealers.Search(q, x => x.Field1,
x => x.Field2,
...
x => x.Field20)
Sorry, no short cuts here:
objs = from dealer in db.Dealers
where dealer.Name.Contains(q) ||
dealer.Field2.Contains(q) ||
...
dealer.Field20.Contains(q)
select dealer;
You have to specify which fields you are going to search the value in.
You can manually write conditions for all fields:
objs = from dealer in db.Dealers
where dealer.Name.Contains(q) ||
dealer.Foo.Contains(q) ||
// etc
dealer.Bar.Contains(q)
select dealer;
There is no simple way to tell Entity Framework to check all properties of entity for some condition.
I have created a generic search extension method for IQueryable that enables you to search for a single property to see if a search term is contained within it.
http://jnye.co/Posts/6/c%23-generic-search-extension-method-for-iqueryable
I now want to enable the user to select multiple properties to search within each, matching if any property contains the text.
The code:
The user enters the following code to perform this search:
string searchTerm = "Essex";
context.Clubs.Search(searchTerm, club => club.Name, club => club.County)
//Note: If possible I would rather something closer to the following syntax...
context.Clubs.Search(club => new[]{ club.Name, club.County}, searchTerm);
// ... or, even better, something similar to this...
context.Clubs.Search(club => new { club.Name, club.County}, searchTerm);
This will return any golf club with 'Essex' in the Name or as the County.
public static IQueryable<TSource> Search<TSource>(this IQueryable<TSource> source, string searchTerm, params Expression<Func<TSource, string>>[] stringProperties)
{
if (String.IsNullOrEmpty(searchTerm))
{
return source;
}
// The lamda I would like to reproduce:
// source.Where(x => x.[property1].Contains(searchTerm)
// || x.[property2].Contains(searchTerm)
// || x.[property3].Contains(searchTerm)...)
//Create expression to represent x.[property1].Contains(searchTerm)
var searchTermExpression = Expression.Constant(searchTerm);
//Build parameters
var parameters = stringProperties.SelectMany(prop => prop.Parameters);
Expression orExpression = null;
//Build a contains expression for each property
foreach (var stringProperty in stringProperties)
{
var checkContainsExpression = Expression.Call(stringProperty.Body, typeof(string).GetMethod("Contains"), searchTermExpression);
if (orExpression == null)
{
orExpression = checkContainsExpression;
}
//Build or expression for each property
orExpression = Expression.OrElse(orExpression, checkContainsExpression);
}
var methodCallExpression = Expression.Call(typeof(Queryable),
"Where",
new Type[] { source.ElementType },
source.Expression,
Expression.Lambda<Func<TSource, bool>>(orExpression, parameters));
return source.Provider.CreateQuery<TSource>(methodCallExpression);
}
The error
If I change the number of parameters supplied to 1:
Expression.Lambda<Func<TSource, bool>>(orExpression, parameters.First()));
I get a new error:
UPDATE
I have written a post on the work discussed in this question. Check it out on GitHub too.
Here we go; you were pretty close - as I noted in comments, the key piece here is to use ExpressionVisitor to re-write the trees in terms of the single parameter you want to keep:
using System;
using System.Linq;
using System.Linq.Expressions;
static class Program
{
static void Main()
{
var data = new[] { new Foo { A = "x1", B = "y1", C = "y1" }, new Foo { A = "y2", B = "y2", C = "y2" },
new Foo { A = "y3", B = "y3", C = "x3" } }.AsQueryable();
var result = data.Search("x", x => x.A, x => x.B, x => x.C);
foreach (var row in result)
{
Console.WriteLine("{0}, {1}, {2}", row.A, row.B, row.C);
}
}
class Foo
{
public string A { get; set; }
public string B { get; set; }
public string C { get; set; }
}
public class SwapVisitor : ExpressionVisitor
{
private readonly Expression from, to;
public SwapVisitor(Expression from, Expression to)
{
this.from = from;
this.to = to;
}
public override Expression Visit(Expression node)
{
return node == from ? to : base.Visit(node);
}
public static Expression Swap(Expression body, Expression from, Expression to)
{
return new SwapVisitor(from, to).Visit(body);
}
}
public static IQueryable<TSource> Search<TSource>(this IQueryable<TSource> source, string searchTerm, params Expression<Func<TSource, string>>[] stringProperties)
{
if (String.IsNullOrEmpty(searchTerm))
{
return source;
}
if (stringProperties.Length == 0) return source.Where(x => false);
// The lamda I would like to reproduce:
// source.Where(x => x.[property1].Contains(searchTerm)
// || x.[property2].Contains(searchTerm)
// || x.[property3].Contains(searchTerm)...)
//Create expression to represent x.[property1].Contains(searchTerm)
var searchTermExpression = Expression.Constant(searchTerm);
var param = stringProperties[0].Parameters.Single();
Expression orExpression = null;
//Build a contains expression for each property
foreach (var stringProperty in stringProperties)
{
// re-write the property using the param we want to keep
var body = SwapVisitor.Swap(stringProperty.Body, stringProperty.Parameters.Single(), param);
var checkContainsExpression = Expression.Call(
body, typeof(string).GetMethod("Contains"), searchTermExpression);
if (orExpression == null)
{
orExpression = checkContainsExpression;
}
else
{ // compose
orExpression = Expression.OrElse(orExpression, checkContainsExpression);
}
}
var lambda = Expression.Lambda<Func<TSource, bool>>(orExpression, param);
return source.Where(lambda);
}
}
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
LINQ To SQL exception: Local sequence cannot be used in LINQ to SQL implementation of query operators except the Contains operator
I am trying the following query:
var data = (from bk in DataContext.Book
where ((searchArray.Count() == 0 || searchArray.ToList().Any(x => bk.Name.Contains(x))) ||
(searchArray.Count() == 0 || searchArray.ToList().Any(x => bk.Genre.Contains(x)))))
where searchArray is a Array containing the individual words that I want to search for, I split the string the user entered and put the results in this array. Whenever I try to run this I get the following error:
"Local sequence cannot be used in LINQ to SQL implementations of query operators except the Contains operator."
Can anyone tell me what I am doing wrong and what is the correct way to perform this search?
In a nutshell, I am trying to allow a user to enter a string like "Hello World" and for a query to be generated that will look for either hello or world or both. But, a user can enter any number of words.
The simplest option is probably to build the lambda expression by hand:
static class ContainsAny
{
private static readonly MethodInfo StringContains
= typeof(string).GetMethod("Contains", new[] { typeof(string) });
public static Builder<T> Words<T>(IEnumerable<string> words)
{
return new Builder<T>(words);
}
public static Builder<T> Words<T>(params string[] words)
{
return new Builder<T>(words);
}
public sealed class Builder<T>
{
private static readonly ParameterExpression Parameter
= Expression.Parameter(typeof(T), "obj");
private readonly List<Expression> _properties = new List<Expression>();
private readonly List<ConstantExpression> _words;
internal Builder(IEnumerable<string> words)
{
_words = words
.Where(word => !string.IsNullOrEmpty(word))
.Select(word => Expression.Constant(word))
.ToList();
}
public Builder<T> WithProperty(Expression<Func<T, string>> property)
{
if (_words.Count != 0)
{
_properties.Add(ReplacementVisitor.Transform(
property, property.Parameters[0], Parameter));
}
return this;
}
private Expression BuildProperty(Expression prop)
{
return _words
.Select(w => (Expression)Expression.Call(prop, StringContains, w))
.Aggregate(Expression.OrElse);
}
public Expression<Func<T, bool>> Build()
{
if (_words.Count == 0) return (T obj) => true;
var body = _properties
.Select(BuildProperty)
.Aggregate(Expression.OrElse);
return Expression.Lambda<Func<T, bool>>(body, Parameter);
}
}
private sealed class ReplacementVisitor : ExpressionVisitor
{
private ICollection<ParameterExpression> Parameters { get; set; }
private Expression Find { get; set; }
private Expression Replace { get; set; }
public static Expression Transform(
LambdaExpression source,
Expression find,
Expression replace)
{
var visitor = new ReplacementVisitor
{
Parameters = source.Parameters,
Find = find,
Replace = replace,
};
return visitor.Visit(source.Body);
}
private Expression ReplaceNode(Expression node)
{
return (node == Find) ? Replace : node;
}
protected override Expression VisitConstant(ConstantExpression node)
{
return ReplaceNode(node);
}
protected override Expression VisitBinary(BinaryExpression node)
{
var result = ReplaceNode(node);
if (result == node) result = base.VisitBinary(node);
return result;
}
protected override Expression VisitParameter(ParameterExpression node)
{
if (Parameters.Contains(node)) return ReplaceNode(node);
return Parameters.FirstOrDefault(p => p.Name == node.Name) ?? node;
}
}
}
With this code in place, you can call:
Expression<Func<Book, bool>> filter = ContainsAny
.Words<Book>(searchArray)
.WithProperty(book => book.Name)
.WithProperty(book => book.Genre)
.Build();
var data = DataContext.Book.Where(filter);
For example, if the searchArray contains { "Hello", "World" }, the generated lambda will be:
obj => (obj.Name.Contains("Hello") || obj.Name.Contains("World"))
|| (obj.Genre.Contains("Hello") || obj.Genre.Contains("World")))
If I understand what you're trying to do correctly, you should be able to condense your query down to:
from bk in DataContext.Book
where searchArray.Contains(bk.Name) || searchArray.Contains(bk.Genre)
select bk
This is basically equivalent to the SQL:
select bk.*
from Book bk
where bk.Name in (...) or bk.Genre in (...)
In your case you must combine interpreted and local queries which can hurt performance or use SQL CLR integration by creating CLR function on database.
Given a class structure like this:
public class GrandParent
{
public Parent Parent { get; set;}
}
public class Parent
{
public Child Child { get; set;}
}
public class Child
{
public string Name { get; set;}
}
and the following method signature:
Expression<Func<TOuter, TInner>> Combine (Expression<Func<TOuter, TMiddle>>> first, Expression<Func<TMiddle, TInner>> second);
How can I implement said method so that I can call it like this:
Expression<Func<GrandParent, Parent>>> myFirst = gp => gp.Parent;
Expression<Func<Parent, string>> mySecond = p => p.Child.Name;
Expression<Func<GrandParent, string>> output = Combine(myFirst, mySecond);
such that output ends up as:
gp => gp.Parent.Child.Name
Is this possible?
The contents of each Func will only ever be a MemberAccess. I'd rather not end up with output being a nested function call.
Thanks
OK; pretty long snippet, but here's a starter for an expression-rewriter; it doesn't handle a few cases yet (I'll fix it later), but it works for the example given and a lot of others:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text.RegularExpressions;
public class GrandParent
{
public Parent Parent { get; set; }
}
public class Parent
{
public Child Child { get; set; }
public string Method(string s) { return s + "abc"; }
}
public class Child
{
public string Name { get; set; }
}
public static class ExpressionUtils
{
public static Expression<Func<T1, T3>> Combine<T1, T2, T3>(
this Expression<Func<T1, T2>> outer, Expression<Func<T2, T3>> inner, bool inline)
{
var invoke = Expression.Invoke(inner, outer.Body);
Expression body = inline ? new ExpressionRewriter().AutoInline(invoke) : invoke;
return Expression.Lambda<Func<T1, T3>>(body, outer.Parameters);
}
}
public class ExpressionRewriter
{
internal Expression AutoInline(InvocationExpression expression)
{
isLocked = true;
if(expression == null) throw new ArgumentNullException("expression");
LambdaExpression lambda = (LambdaExpression)expression.Expression;
ExpressionRewriter childScope = new ExpressionRewriter(this);
var lambdaParams = lambda.Parameters;
var invokeArgs = expression.Arguments;
if (lambdaParams.Count != invokeArgs.Count) throw new InvalidOperationException("Lambda/invoke mismatch");
for(int i = 0 ; i < lambdaParams.Count; i++) {
childScope.Subst(lambdaParams[i], invokeArgs[i]);
}
return childScope.Apply(lambda.Body);
}
public ExpressionRewriter()
{
subst = new Dictionary<Expression, Expression>();
}
private ExpressionRewriter(ExpressionRewriter parent)
{
if (parent == null) throw new ArgumentNullException("parent");
subst = new Dictionary<Expression, Expression>(parent.subst);
inline = parent.inline;
}
private bool isLocked, inline;
private readonly Dictionary<Expression, Expression> subst;
private void CheckLocked() {
if(isLocked) throw new InvalidOperationException(
"You cannot alter the rewriter after Apply has been called");
}
public ExpressionRewriter Subst(Expression from,
Expression to)
{
CheckLocked();
subst.Add(from, to);
return this;
}
public ExpressionRewriter Inline() {
CheckLocked();
inline = true;
return this;
}
public Expression Apply(Expression expression)
{
isLocked = true;
return Walk(expression) ?? expression;
}
private static IEnumerable<Expression> CoalesceTerms(
IEnumerable<Expression> sourceWithNulls, IEnumerable<Expression> replacements)
{
if(sourceWithNulls != null && replacements != null) {
using(var left = sourceWithNulls.GetEnumerator())
using (var right = replacements.GetEnumerator())
{
while (left.MoveNext() && right.MoveNext())
{
yield return left.Current ?? right.Current;
}
}
}
}
private Expression[] Walk(IEnumerable<Expression> expressions) {
if(expressions == null) return null;
return expressions.Select(expr => Walk(expr)).ToArray();
}
private static bool HasValue(Expression[] expressions)
{
return expressions != null && expressions.Any(expr => expr != null);
}
// returns null if no need to rewrite that branch, otherwise
// returns a re-written branch
private Expression Walk(Expression expression)
{
if (expression == null) return null;
Expression tmp;
if (subst.TryGetValue(expression, out tmp)) return tmp;
switch(expression.NodeType) {
case ExpressionType.Constant:
case ExpressionType.Parameter:
{
return expression; // never a need to rewrite if not already matched
}
case ExpressionType.MemberAccess:
{
MemberExpression me = (MemberExpression)expression;
Expression target = Walk(me.Expression);
return target == null ? null : Expression.MakeMemberAccess(target, me.Member);
}
case ExpressionType.Add:
case ExpressionType.Divide:
case ExpressionType.Multiply:
case ExpressionType.Subtract:
case ExpressionType.AddChecked:
case ExpressionType.MultiplyChecked:
case ExpressionType.SubtractChecked:
case ExpressionType.And:
case ExpressionType.Or:
case ExpressionType.ExclusiveOr:
case ExpressionType.Equal:
case ExpressionType.NotEqual:
case ExpressionType.AndAlso:
case ExpressionType.OrElse:
case ExpressionType.Power:
case ExpressionType.Modulo:
case ExpressionType.GreaterThan:
case ExpressionType.GreaterThanOrEqual:
case ExpressionType.LessThan:
case ExpressionType.LessThanOrEqual:
case ExpressionType.LeftShift:
case ExpressionType.RightShift:
case ExpressionType.Coalesce:
case ExpressionType.ArrayIndex:
{
BinaryExpression binExp = (BinaryExpression)expression;
Expression left = Walk(binExp.Left), right = Walk(binExp.Right);
return (left == null && right == null) ? null : Expression.MakeBinary(
binExp.NodeType, left ?? binExp.Left, right ?? binExp.Right, binExp.IsLiftedToNull,
binExp.Method, binExp.Conversion);
}
case ExpressionType.Not:
case ExpressionType.UnaryPlus:
case ExpressionType.Negate:
case ExpressionType.NegateChecked:
case ExpressionType.Convert:
case ExpressionType.ConvertChecked:
case ExpressionType.TypeAs:
case ExpressionType.ArrayLength:
{
UnaryExpression unExp = (UnaryExpression)expression;
Expression operand = Walk(unExp.Operand);
return operand == null ? null : Expression.MakeUnary(unExp.NodeType, operand,
unExp.Type, unExp.Method);
}
case ExpressionType.Conditional:
{
ConditionalExpression ce = (ConditionalExpression)expression;
Expression test = Walk(ce.Test), ifTrue = Walk(ce.IfTrue), ifFalse = Walk(ce.IfFalse);
if (test == null && ifTrue == null && ifFalse == null) return null;
return Expression.Condition(test ?? ce.Test, ifTrue ?? ce.IfTrue, ifFalse ?? ce.IfFalse);
}
case ExpressionType.Call:
{
MethodCallExpression mce = (MethodCallExpression)expression;
Expression instance = Walk(mce.Object);
Expression[] args = Walk(mce.Arguments);
if (instance == null && !HasValue(args)) return null;
return Expression.Call(instance, mce.Method, CoalesceTerms(args, mce.Arguments));
}
case ExpressionType.TypeIs:
{
TypeBinaryExpression tbe = (TypeBinaryExpression)expression;
tmp = Walk(tbe.Expression);
return tmp == null ? null : Expression.TypeIs(tmp, tbe.TypeOperand);
}
case ExpressionType.New:
{
NewExpression ne = (NewExpression)expression;
Expression[] args = Walk(ne.Arguments);
if (HasValue(args)) return null;
return ne.Members == null ? Expression.New(ne.Constructor, CoalesceTerms(args, ne.Arguments))
: Expression.New(ne.Constructor, CoalesceTerms(args, ne.Arguments), ne.Members);
}
case ExpressionType.ListInit:
{
ListInitExpression lie = (ListInitExpression)expression;
NewExpression ctor = (NewExpression)Walk(lie.NewExpression);
var inits = lie.Initializers.Select(init => new
{
Original = init,
NewArgs = Walk(init.Arguments)
}).ToArray();
if (ctor == null && !inits.Any(init => HasValue(init.NewArgs))) return null;
ElementInit[] initArr = inits.Select(init => Expression.ElementInit(
init.Original.AddMethod, CoalesceTerms(init.NewArgs, init.Original.Arguments))).ToArray();
return Expression.ListInit(ctor ?? lie.NewExpression, initArr);
}
case ExpressionType.NewArrayBounds:
case ExpressionType.NewArrayInit:
/* not quite right... leave as not-implemented for now
{
NewArrayExpression nae = (NewArrayExpression)expression;
Expression[] expr = Walk(nae.Expressions);
if (!HasValue(expr)) return null;
return expression.NodeType == ExpressionType.NewArrayBounds
? Expression.NewArrayBounds(nae.Type, CoalesceTerms(expr, nae.Expressions))
: Expression.NewArrayInit(nae.Type, CoalesceTerms(expr, nae.Expressions));
}*/
case ExpressionType.Invoke:
case ExpressionType.Lambda:
case ExpressionType.MemberInit:
case ExpressionType.Quote:
throw new NotImplementedException("Not implemented: " + expression.NodeType);
default:
throw new NotSupportedException("Not supported: " + expression.NodeType);
}
}
}
static class Program
{
static void Main()
{
Expression<Func<GrandParent, Parent>> myFirst = gp => gp.Parent;
Expression<Func<Parent, string>> mySecond = p => p.Child.Name;
Expression<Func<GrandParent, string>> outputWithInline = myFirst.Combine(mySecond, false);
Expression<Func<GrandParent, string>> outputWithoutInline = myFirst.Combine(mySecond, true);
Expression<Func<GrandParent, string>> call =
ExpressionUtils.Combine<GrandParent, Parent, string>(
gp => gp.Parent, p => p.Method(p.Child.Name), true);
unchecked
{
Expression<Func<double, double>> mathUnchecked =
ExpressionUtils.Combine<double, double, double>(x => (x * x) + x, x => x - (x / x), true);
}
checked
{
Expression<Func<double, double>> mathChecked =
ExpressionUtils.Combine<double, double, double>(x => x - (x * x) , x => (x / x) + x, true);
}
Expression<Func<int,int>> bitwise =
ExpressionUtils.Combine<int, int, int>(x => (x & 0x01) | 0x03, x => x ^ 0xFF, true);
Expression<Func<int, bool>> logical =
ExpressionUtils.Combine<int, bool, bool>(x => x == 123, x => x != false, true);
Expression<Func<int[][], int>> arrayAccess =
ExpressionUtils.Combine<int[][], int[], int>(x => x[0], x => x[0], true);
Expression<Func<string, bool>> isTest =
ExpressionUtils.Combine<string,object,bool>(s=>s, s=> s is Regex, true);
Expression<Func<List<int>>> f = () => new List<int>(new int[] { 1, 1, 1 }.Length);
Expression<Func<string, Regex>> asTest =
ExpressionUtils.Combine<string, object, Regex>(s => s, s => s as Regex, true);
var initTest = ExpressionUtils.Combine<int, int[], List<int>>(i => new[] {i,i,i},
arr => new List<int>(arr.Length), true);
var anonAndListTest = ExpressionUtils.Combine<int, int, List<int>>(
i => new { age = i }.age, i => new List<int> {i, i}, true);
/*
var arrBoundsInit = ExpressionUtils.Combine<int, int[], int[]>(
i => new int[i], arr => new int[arr[0]] , true);
var arrInit = ExpressionUtils.Combine<int, int, int[]>(
i => i, i => new int[1] { i }, true);*/
}
}
I am assuming that your goal is to obtain the expression tree that you would have obtained, had you actually compiled the "combined" lambda. It's much easier to construct a new expression tree that simply invokes the given expression trees appropriately, but I assume that's not what you want.
extract the body of first, cast it to MemberExpression. Call this firstBody.
extract the body of second, call this secondBody
extract the parameter of first. Call this firstParam.
extract the parameter of second. Call this secondParam.
Now, the hard part. Write a visitor pattern implementation which searches through secondBody looking for the single usage of secondParam. (This will be much easier if you know that it's only member access expressions, but you can solve the problem in general.) When you find it, construct a new expression of the same type as its parent, substituting in firstBody for the parameter. Continue to rebuild the transformed tree on the way back out; remember, all you have to rebuild is the "spine" of the tree that contains the parameter reference.
the result of the visitor pass will be a rewritten secondBody with no occurrences of secondParam, only occurences of expressions involving firstParam.
construct a new lambda expression with that body as its body, and firstParam as its param.
and you're done!
Matt Warren's blog might be a good thing for you to read. He designed and implemented all this stuff and has written a lot about ways to rewrite expression trees effectively. (I only did the compiler end of things.)
UPDATE:
As this related answer points out, in .NET 4 there is now a base class for expression rewriters that makes this sort of thing a lot easier.
I'm not sure what you mean by it not being a nested function call, but this will do the trick - with an example:
using System;
using System.IO;
using System.Linq.Expressions;
class Test
{
static Expression<Func<TOuter, TInner>> Combine<TOuter, TMiddle, TInner>
(Expression<Func<TOuter, TMiddle>> first,
Expression<Func<TMiddle, TInner>> second)
{
var parameter = Expression.Parameter(typeof(TOuter), "x");
var firstInvoke = Expression.Invoke(first, new[] { parameter });
var secondInvoke = Expression.Invoke(second, new[] { firstInvoke} );
return Expression.Lambda<Func<TOuter, TInner>>(secondInvoke, parameter);
}
static void Main()
{
Expression<Func<int, string>> first = x => (x + 1).ToString();
Expression<Func<string, StringReader>> second = y => new StringReader(y);
Expression<Func<int, StringReader>> output = Combine(first, second);
Func<int, StringReader> compiled = output.Compile();
var reader = compiled(10);
Console.WriteLine(reader.ReadToEnd());
}
}
I don't know how efficient the generated code will be compared with a single lambda expression, but I suspect it won't be too bad.
For a complete solution have a look at LINQKit:
Expression<Func<GrandParent, string>> output = gp => mySecond.Invoke(myFirst.Invoke(gp));
output = output.Expand().Expand();
output.ToString() prints out
gp => gp.Parent.Child.Name
whereas Jon Skeet's solution yields
x => Invoke(p => p.Child.Name,Invoke(gp => gp.Parent,x))
I guess that's what you're referring to as 'nested function calls'.
Try this:
public static Expression<Func<TOuter, TInner>> Combine<TOuter, TMiddle, TInner>(
Expression<Func<TOuter, TMiddle>> first,
Expression<Func<TMiddle, TInner>> second)
{
return x => second.Compile()(first.Compile()(x));
}
and the usage:
Expression<Func<GrandParent, Parent>> myFirst = gp => gp.Parent;
Expression<Func<Parent, string>> mySecond = p => p.Child.Name;
Expression<Func<GrandParent, string>> output = Combine(myFirst, mySecond);
var grandParent = new GrandParent
{
Parent = new Parent
{
Child = new Child
{
Name = "child name"
}
}
};
var childName = output.Compile()(grandParent);
Console.WriteLine(childName); // prints "child name"
public static Expression<Func<T, TResult>> And<T, TResult>(this Expression<Func<T, TResult>> expr1, Expression<Func<T, TResult>> expr2)
{
var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>());
return Expression.Lambda<Func<T, TResult>>(Expression.AndAlso(expr1.Body, invokedExpr), expr1.Parameters);
}
public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2)
{
var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>());
return Expression.Lambda<Func<T, bool>>(Expression.OrElse(expr1.Body, invokedExpr), expr1.Parameters);
}
After a half-day's digging came up with the following solution (much simpler than the accepted answer):
For generic lambda composition:
public static Expression<Func<X, Z>> Compose<X, Y, Z>(Expression<Func<Y, Z>> f, Expression<Func<X, Y>> g)
{
return Expression.Lambda<Func<X, Z>>(Expression.Invoke(f, Expression.Invoke(g, g.Parameters[0])), g.Parameters);
}
This combines two expressions in one, i.e. applies the first expression to the result of the second.
So if we have f(y) and g(x), combine(f,g)(x) === f(g(x))
Transitive and associative, so the combinator can be chained
More specifically, for property access (needed for MVC/EF):
public static Expression<Func<X, Z>> Property<X, Y, Z>(Expression<Func<X, Y>> fObj, Expression<Func<Y, Z>> fProp)
{
return Expression.Lambda<Func<X, Z>>(Expression.Property(fObj.Body, (fProp.Body as MemberExpression).Member as PropertyInfo), fObj.Parameters);
}
Note: fProp must be a simple property access expression, such as x => x.Prop.
fObj can be any expression (but must be MVC-compatible)
With a toolkit called Layer Over LINQ, there's an extension method that does exactly this, combines two expressions to create a new one suitable for use in LINQ to Entities.
Expression<Func<GrandParent, Parent>>> myFirst = gp => gp.Parent;
Expression<Func<Parent, string>> mySecond = p => p.Child.Name;
Expression<Func<GrandParent, string>> output = myFirst.Chain(mySecond);