Come combinare due alberi di espressione membri?

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

Domanda

Sto cercando di combinare le seguenti espressioni in una singola espressione: item => item.sub, sub => sub.key per diventare item => item.sub.key. Ho bisogno di fare questo in modo da poter creare un metodo OrderBy che prende il selettore oggetto separatamente per il selettore di chiave. Questo può essere realizzato utilizzando uno degli overload su OrderBy e fornendo un IComparer<T> , ma non si tradurrà in SQL.

Di seguito è riportata una firma del metodo per chiarire ulteriormente cosa sto cercando di ottenere, insieme a un'implementazione che non funziona, ma dovrebbe illustrare il 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
                 ));
    } 

quale sarebbe chiamato come:

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

È possibile? C'è un modo migliore? La ragione per cui desidero un metodo OrderBy che funzioni in questo modo è supportare una complessa espressione di selezione di chiavi che si applica a molte entità, sebbene siano esposte in modi diversi. Inoltre, sono a conoscenza di un modo per farlo utilizzando rappresentazioni String di proprietà profonde, ma sto cercando di mantenerlo fortemente digitato.

Risposta accettata

Poiché si tratta di LINQ-to-SQL, in genere è possibile utilizzare Expression.Invoke per mettere in gioco una sottoespressione. Vedrò se riesco a trovare un esempio ( aggiornamento: fatto ). Nota, tuttavia, che EF non supporta questo: dovrai ricostruire l'espressione da zero. Ho del codice per farlo, ma è piuttosto lungo ...

Il codice dell'espressione (usando Invoke ) è abbastanza semplice:

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

Ecco l'esempio di utilizzo su 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 (formattato per adattarsi):

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]

Risposta popolare

Mi serviva lo stesso, quindi ho realizzato questo piccolo metodo di estensione:

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


Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow
È legale questo KB? Sì, impara il perché
Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow
È legale questo KB? Sì, impara il perché