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

发布于 2024-07-13 23:52:09 字数 1321 浏览 10 评论 0原文

我正在尝试将以下表达式组合成一个表达式: item => item.sub, sub => sub.key 成为 item => 项目.子键。 我需要这样做,以便我可以创建一个 OrderBy 方法,该方法将项目选择器单独作为键选择器。 这可以使用 OrderBy 上的重载之一并提供 IComparer 来完成,但它不会转换为 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 方法的原因是支持适用于许多实体的复杂键选择表达式,尽管它们以不同的方式公开。 另外,我知道有一种方法可以使用深层属性的字符串表示来做到这一点,但我试图保持它的强类型化。

I'm trying to combine the following expressions into a single expression: item => item.sub, sub => sub.key to become 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. This can be accomplished using one of the overloads on OrderBy and providing an IComparer<T>, but it won't translate to 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. 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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

久光 2024-07-20 23:52:10

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

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

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;
    }
满意归宿 2024-07-20 23:52:10

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

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

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>. Can you work around that?

小鸟爱天空丶 2024-07-20 23:52:09

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

Since this is LINQ-to-SQL, you can usually use Expression.Invoke to bring a sub-expression into play. 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. I have some code to do this, but it is quite lengthy...

The expression code (using Invoke) is quite simple:

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:

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

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]
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文