简体   繁体   English

如何组合两个成员表达式树?

[英]How do I combine two Member Expression Trees?

I'm trying to combine the following expressions into a single expression: item => item.sub, sub => sub.key to become item => item.sub.key. 我正在尝试将以下表达式组合成单个表达式:item => item.sub,sub => sub.key成为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. 我需要这样做,所以我可以创建一个OrderBy方法,它将项目选择器分别带到键选择器。 This can be accomplished using one of the overloads on OrderBy and providing an IComparer<T> , but it won't translate to SQL. 这可以使用OrderBy上的一个重载并提供IComparer<T> ,但它不会转换为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. 我希望以这种方式工作的OrderBy方法的原因是支持适用于许多实体的复杂键选择表达式,尽管它们以不同方式公开。 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. 由于这是LINQ-to-SQL,因此通常可以使用Expression.Invoke来使用子表达式。 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. 但请注意,EF不支持此功能 - 您需要从头开始重建表达式。 I have some code to do this, but it is quite lengthy... 我有一些代码可以做到这一点,但它很冗长......

The expression code (using Invoke ) is quite simple: 表达式代码(使用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);

Here's example usage on Northwind: 以下是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): 使用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]

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

What you have there is sotring, followed by projecting and then sorting again. 你有什么东西,然后投射,然后再次排序。

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

Your method could be like this: 你的方法可能是这样的:

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

This will be executed by SQL but as you might have noticed I had to change the return type here to IOrderedQueryable<TSubEntity>. 这将由SQL执行,但您可能已经注意到我必须将返回类型更改为IOrderedQueryable <TSubEntity>。 Can you work around that? 你可以解决这个问题吗?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM