Как объединить два дерева выражений членов? - PullRequest
4 голосов
/ 18 февраля 2009

Я пытаюсь объединить следующие выражения в одно выражение: item => item.sub, sub => sub.key, чтобы стать item => item.sub.key. Мне нужно сделать это, чтобы я мог создать метод OrderBy, который передает селектор элемента отдельно к ключевому селектору. Это может быть выполнено с использованием одной из перегрузок OrderBy и предоставлением IComparer<T>, но это не преобразуется в SQL.

Ниже приведена подпись метода, чтобы уточнить, чего я пытаюсь достичь, а также реализацию, которая не работает, но должна проиллюстрировать это.

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

который будет называться:

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

Возможно ли это? Есть ли способ лучше? Причина, по которой я хочу, чтобы метод OrderBy, работающий таким образом, состоял в том, чтобы поддерживать сложное выражение выбора ключа, которое применяется ко многим объектам, хотя они представлены различными способами. Кроме того, мне известен способ сделать это, используя строковые представления глубоких свойств, но я стараюсь держать его строго типизированным.

Ответы [ 3 ]

4 голосов
/ 18 февраля 2009

Поскольку это LINQ-to-SQL, вы обычно можете использовать Expression.Invoke, чтобы ввести в игру подвыражение. Я посмотрю, смогу ли я привести пример ( update: done ). Обратите внимание, однако, что EF не поддерживает это - вам нужно перестроить выражение с нуля. У меня есть код для этого, но он довольно длинный ...

Код выражения (с использованием Invoke) довольно прост:

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

Вот пример использования на Northwind:

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

С TSQL (переформатировано, чтобы соответствовать):

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]
1 голос
/ 29 октября 2015

Мне нужно то же самое, поэтому сделал этот маленький метод расширения:

    /// <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;
    }
1 голос
/ 18 февраля 2009

То, что у вас есть, это sotring, затем проецируется и затем снова сортируется.

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

Ваш метод может быть таким:

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

Это будет выполняться SQL, но, как вы могли заметить, мне пришлось изменить здесь тип возвращаемого значения на IOrderedQueryable . Вы можете обойти это?

...