Wie kombiniere ich zwei Member Expression Trees?

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

Frage

Ich versuche, die folgenden Ausdrücke zu einem einzigen Ausdruck zu kombinieren: item => item.sub, sub => sub.key wird zu item => item.key. Ich muss dies tun, damit ich eine OrderBy-Methode erstellen kann, die den Elementselektor separat zum Schlüsselselektor bringt. Dies kann erreicht werden, indem eine der Überladungen von OrderBy verwendet wird und ein IComparer<T> , aber es wird nicht in SQL übersetzt.

Es folgt eine Methodensignatur, um zu verdeutlichen, was ich zu erreichen versuche, und eine Implementierung, die nicht funktioniert, aber den Punkt verdeutlichen sollte.

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

was würde heißen:

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

Ist das möglich? Gibt es einen besseren Weg? Der Grund, warum ich eine OrderBy-Methode verwenden möchte, die auf diese Weise funktioniert, besteht darin, einen komplexen Schlüsselauswahlausdruck zu unterstützen, der für viele Entitäten gilt, obwohl sie auf verschiedene Arten verfügbar gemacht werden. Außerdem bin ich mir bewusst, dass ich dafür String-Repräsentationen von tiefen Eigenschaften verwenden kann, aber ich versuche, sie stark typisiert zu halten.

Akzeptierte Antwort

Da dies LINQ-to-SQL ist, können Sie Expression.Invoke normalerweise verwenden, um einen Unterausdruck ins Spiel zu bringen. Ich werde sehen, ob ich ein Beispiel finden kann ( update: done ). Beachten Sie jedoch, dass EF dies nicht unterstützt - Sie müssten den Ausdruck von Grund auf neu erstellen. Ich habe einen Code, um dies zu tun, aber es ist ziemlich lang ...

Der Ausdruck Code (mit Invoke ) ist ziemlich einfach:

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

Hier ist ein Beispiel für die Verwendung auf Northwind:

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

Mit TSQL (umformatiert):

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

Beliebte Antwort

Ich brauchte das gleiche, also habe ich diese kleine Erweiterungsmethode gemacht:

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



Lizenziert unter: CC-BY-SA with attribution
Nicht verbunden mit Stack Overflow
Ist diese KB legal? Ja, lerne warum
Lizenziert unter: CC-BY-SA with attribution
Nicht verbunden mit Stack Overflow
Ist diese KB legal? Ja, lerne warum