4

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

4

3 回答 3

4

由于这是 LINQ-to-SQL,您通常可以使用Expression.Invoke子表达式来发挥作用。我会看看我是否能想出一个例子(更新:完成)。但是请注意,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]
于 2009-02-18T08:15:07.977 回答
3

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

    /// <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;
    }
于 2015-10-29T15:49:30.983 回答
1

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

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

于 2009-02-18T07:02:16.530 回答