How to combine two Expression expression trees?

I am trying to combine the following expressions into a single expression: item => item.sub, sub => sub.key to become item => item.sub.key. I need to do this so that I can create an OrderBy method that translates the element selector separately into a key selector. This can be accomplished using one of the overloads on OrderBy and providing IComparer<T> , but it will not be translated into SQL.

The following is the signature of the method to further clarify what I am trying to achieve, as well as an implementation that does not work, but should illustrate this point.

  public static IOrderedQueryable<TEntity> OrderBy<TEntity, TSubEntity, TKey>( this IQueryable<TEntity> source, Expression<Func<TEntity, TSubEntity>> selectItem, Expression<Func<TSubEntity, TKey>> selectKey) where TEntity : class where TSubEntity : class { var parameterItem = Expression.Parameter(typeof(TEntity), "item"); ... some magic ... var selector = Expression.Lambda(magic, parameterItem); return (IOrderedQueryable<TEntity>)source.Provider.CreateQuery( Expression.Call(typeof(Queryable), "OrderBy", new Type[] { source.ElementType, selector.Body.Type }, source.Expression, selector )); } 

which will be called as:

 .OrderBy(item => item.Sub, sub => sub.Key) 

Is it possible? Is there a better way? The reason I want the OrderBy method to work this way is because of the support for the complex key selection expression, which applies to many objects, although they are expanded differently. In addition, I know a way to do this using string representations of deep properties, but I'm trying to keep it strongly typed.

+4
source share
3 answers

Since this is LINQ-to-SQL, you can usually use Expression.Invoke to bring subexpressions to the game. I will see if I can give an example ( update: done ). Note, however, that EF does not support this — you will need to rebuild the expression from scratch. I have code for this, but it's quite long ...

The expression code (using Invoke ) is pretty simple:

 var param = Expression.Parameter(typeof(TEntity), "item"); var item = Expression.Invoke(selectItem, param); var key = Expression.Invoke(selectKey, item); var lambda = Expression.Lambda<Func<TEntity, TKey>>(key, param); return source.OrderBy(lambda); 

Here's an example usage on Northwind:

 using(var ctx = new MyDataContext()) { ctx.Log = Console.Out; var rows = ctx.Orders.OrderBy(order => order.Customer, customer => customer.CompanyName).Take(20).ToArray(); } 

With TSQL (reformatted to match):

 SELECT TOP (20) [t0].[OrderID], -- snip FROM [dbo].[Orders] AS [t0] LEFT OUTER JOIN [dbo].[Customers] AS [t1] ON [t1].[CustomerID] = [t0].[CustomerID] ORDER BY [t1].[CompanyName] 
+4
source

What you have is sotring and then projecting and then sorting again.

 .OrderBy(x => x.Sub) .Select(x => x.Sub) .OrderBy(x => x.Key) 

Your method might be like this:

 public static IOrderedQueryable<TSubEntity> OrderBy<TEntity, TSubEntity, TKey>( this IQueryable<TEntity> source, Expression<Func<TEntity, TSubEntity>> selectItem, Expression<Func<TSubEntity, TKey>> selectKey) where TEntity : class where TSubEntity : class { return (IOrderedQueryable<TSubEntity>)source. OrderBy(selectItem).Select(selectItem).OrderBy(selectKey) } 

This will be done by SQL, but as you may have noticed, I had to change the return type here to IOrderedQueryable <TSubEntity>. Can you get around this?

+1
source

I also needed such a small extension method:

  /// <summary> /// From ABC and DEF makes ABCDEF D must be a member of C. /// </summary> /// <param name="memberExpression1"></param> /// <param name="memberExpression2"></param> /// <returns></returns> public static MemberExpression JoinExpression(this Expression memberExpression1, MemberExpression memberExpression2) { var stack = new Stack<MemberInfo>(); Expression current = memberExpression2; while (current.NodeType != ExpressionType.Parameter) { var memberAccess = current as MemberExpression; if (memberAccess != null) { current = memberAccess.Expression; stack.Push(memberAccess.Member); } else { throw new NotSupportedException(); } } Expression jointMemberExpression = memberExpression1; foreach (var memberInfo in stack) { jointMemberExpression = Expression.MakeMemberAccess(jointMemberExpression, memberInfo); } return (MemberExpression) jointMemberExpression; } 
+1
source

All Articles