¿Cómo combino dos árboles de expresión de miembros?

.net c# expression-trees lambda linq-to-sql

Pregunta

Estoy tratando de combinar las siguientes expresiones en una sola expresión: item => item.sub, sub => sub.key para convertirse en item => item.sub.key. Necesito hacer esto para poder crear un método OrderBy que lleve el selector de elementos por separado al selector de teclas. Esto se puede lograr usando una de las sobrecargas en OrderBy y proporcionando un IComparer<T> , pero no se traducirá a SQL.

A continuación se incluye una firma del método para aclarar aún más lo que estoy tratando de lograr, junto con una implementación que no funciona, pero que debería ilustrar el punto.

    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
                 ));
    } 

que se llamaría como:

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

es posible? ¿Hay alguna manera mejor? La razón por la que quiero un método OrderBy que funcione de esta manera es para admitir una expresión de selección de clave compleja que se aplica a muchas entidades, aunque están expuestas de diferentes maneras. Además, soy consciente de una manera de hacer esto usando representaciones de cadenas de propiedades profundas, pero estoy tratando de mantenerlo fuertemente tipado.

Respuesta aceptada

Ya que esto es LINQ-a-SQL, generalmente puede usar Expression.Invoke para poner en juego una sub-expresión. Veré si puedo dar un ejemplo ( actualización: listo ). Sin embargo, tenga en cuenta que EF no es compatible con esto: deberá reconstruir la expresión desde cero. Tengo un código para hacer esto, pero es bastante largo ...

El código de expresión (usando Invoke ) es bastante 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);

Aquí está el uso del ejemplo en Northwind:

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

Con TSQL (reformateado para encajar):

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]

Respuesta popular

Necesitaba lo mismo así que hice este pequeño método de extensión:

    /// <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;
    }


Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
¿Es esto KB legal? Sí, aprende por qué
Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
¿Es esto KB legal? Sí, aprende por qué