【问题标题】:How do I combine two Member Expression Trees?如何组合两个成员表达式树?
【发布时间】:2009-02-18 06:26:49
【问题描述】:

我正在尝试将以下表达式组合成一个表达式: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 方法的原因是支持适用于许多实体的复杂键选择表达式,尽管它们以不同的方式公开。另外,我知道有一种方法可以使用深层属性的字符串表示来做到这一点,但我试图保持它的强类型。

【问题讨论】:

    标签: c# .net linq-to-sql lambda expression-trees


    【解决方案1】:

    由于这是 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]
    

    【讨论】:

    • 感谢 Marc,Expression.Invoke 是我一直在寻找的魔法。我认为它与 selectItem.Compile().Invoke(...) 完全一样,这显然不起作用。我可以通过分析器确认 SQL 也是按预期创建的。
    • 这与 ctx.Orders.OrderBy(o => o.Customer).OrderBy(o => o.Customer.CompanyName) 或仅 ctx.Orders.OrderBy(o => o.Customer.CompanyName)?
    • 我简化了我的示例,实际上选择键 (CompanyName) 的表达式非常复杂但不变,而选择项目 (Customer) 的部分很简单但变化很大。所以我试图封装常量部分。
    • 你有没有机会链接到这个“冗长”的代码,请马克?我有一个基于此适用于 Linq to SQL 的解决方案,但由于调用而无法使其与 EF 一起使用:-(
    • @DoctaJonez - 试试this - 如果不完整,请告诉我。
    【解决方案2】:

    我也需要这个,所以做了这个小扩展方法:

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

    【讨论】:

      【解决方案3】:

      你所拥有的是排序,然后是投影,然后再次排序。

      .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。你能解决这个问题吗?

      【讨论】:

      • 这实际上与合并到单个投影的效果不同,并且(正如您自己观察到的)返回的数据非常不同......不确定这是否特别有用?
      • 嗯,这取决于 LINQ to SQL 提供程序,不是吗?返回类型已经改变,是的,但我不知道这是否会成为一个真正的问题,这可能就足够了。 LINQ 的美妙之处在于能够编写这样的查询。您不必为这样的事情遍历表达式树。
      • 感谢 John 的回复,您的方法在某些地方就足够了,但在其他地方我需要以 TEntity 的形式返回。对于两种方法都足够的情况,对两者之间的性能差异有何想法?
      • ...继续;当然,您可以只编译表达式,并且在任何一种情况下都可能获得非常相似的性能。
      • 我很确定两者的性能是一样的。 LINQ to SQL 实际上确实在后台编译了一些东西,所以这并不重要。令人惊讶的是,编写好的 LINQ to SQL 代码比人们想象的要简单得多,尤其是当事情变得复杂时。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-04-08
      • 2013-03-21
      • 1970-01-01
      • 2016-08-12
      相关资源
      最近更新 更多