I'm 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 I can create an OrderBy method which takes the item selector separately to the key selector. This can be accomplished using one of the overloads on OrderBy and providing an IComparer<T>
, but it won't translate to SQL.
Following is a method signature to further clarify what I am trying to achive, along with an implementation that doesn't work, but should illustrate the 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 would be called as:
.OrderBy(item => item.Sub, sub => sub.Key)
Is this possible? Is there a better way? The reason I want an OrderBy method that works this way is to support a complex key selection expression that applies to many entities, though they are exposed in different ways. Also, I'm aware of a way to do this using String representations of deep properties, but I'm trying to keep it strongly typed.
Since this is LINQ-to-SQL, you can usually use Expression.Invoke
to bring a sub-expression into play. I'll see if I can come up with an example (update: done). Note, however, that EF doesn't support this - you'd need to rebuild the expression from scratch. I have some code to do this, but it is quite lengthy...
The expression code (using Invoke
) is quite 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 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 fit):
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]
I needed the same so made this small extension method:
/// <summary>
/// From A.B.C and D.E.F makes A.B.C.D.E.F. 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;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With