将多个相似的 SELECT 表达式组合成一个表达式

发布于 2024-11-10 12:58:28 字数 1098 浏览 5 评论 0原文

如何将多个相似的 SELECT 表达式组合成一个表达式?

   private static Expression<Func<Agency, AgencyDTO>> CombineSelectors(params Expression<Func<Agency, AgencyDTO>>[] selectors)
    {

        // ???

        return null;
    }

    private void Query()
    {
        Expression<Func<Agency, AgencyDTO>> selector1 = x => new AgencyDTO { Name = x.Name };
        Expression<Func<Agency, AgencyDTO>> selector2 = x => new AgencyDTO { Phone = x.PhoneNumber };
        Expression<Func<Agency, AgencyDTO>> selector3 = x => new AgencyDTO { Location = x.Locality.Name };
        Expression<Func<Agency, AgencyDTO>> selector4 = x => new AgencyDTO { EmployeeCount = x.Employees.Count() };

        using (RealtyContext context = Session.CreateContext())
        {
            IQueryable<AgencyDTO> agencies = context.Agencies.Select(CombineSelectors(selector3, selector4));

            foreach (AgencyDTO agencyDTO in agencies)
            {
                // do something..;
            }
        }
    }

How to combine several similar SELECT-expressions into a single expression?

   private static Expression<Func<Agency, AgencyDTO>> CombineSelectors(params Expression<Func<Agency, AgencyDTO>>[] selectors)
    {

        // ???

        return null;
    }

    private void Query()
    {
        Expression<Func<Agency, AgencyDTO>> selector1 = x => new AgencyDTO { Name = x.Name };
        Expression<Func<Agency, AgencyDTO>> selector2 = x => new AgencyDTO { Phone = x.PhoneNumber };
        Expression<Func<Agency, AgencyDTO>> selector3 = x => new AgencyDTO { Location = x.Locality.Name };
        Expression<Func<Agency, AgencyDTO>> selector4 = x => new AgencyDTO { EmployeeCount = x.Employees.Count() };

        using (RealtyContext context = Session.CreateContext())
        {
            IQueryable<AgencyDTO> agencies = context.Agencies.Select(CombineSelectors(selector3, selector4));

            foreach (AgencyDTO agencyDTO in agencies)
            {
                // do something..;
            }
        }
    }

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

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

发布评论

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

评论(3

冰雪之触 2024-11-17 12:58:28

不简单;你需要重写所有表达式 - 严格来说,你可以回收其中的大部分表达式,但问题是每个表达式都有不同的 x (即使它看起来相同),因此你需要使用访问者将所有参数替换为final x。幸运的是,这在 4.0 中还不算太糟糕:

static void Main() {
    Expression<Func<Agency, AgencyDTO>> selector1 = x => new AgencyDTO { Name = x.Name };
    Expression<Func<Agency, AgencyDTO>> selector2 = x => new AgencyDTO { Phone = x.PhoneNumber };
    Expression<Func<Agency, AgencyDTO>> selector3 = x => new AgencyDTO { Location = x.Locality.Name };
    Expression<Func<Agency, AgencyDTO>> selector4 = x => new AgencyDTO { EmployeeCount = x.Employees.Count() };

    // combine the assignments from the 4 selectors
    var convert = Combine(selector1, selector2, selector3, selector4);

    // sample data
    var orig = new Agency
    {
        Name = "a",
        PhoneNumber = "b",
        Locality = new Location { Name = "c" },
        Employees = new List<Employee> { new Employee(), new Employee() }
    };

    // check it
    var dto = new[] { orig }.AsQueryable().Select(convert).Single();
    Console.WriteLine(dto.Name); // a
    Console.WriteLine(dto.Phone); // b
    Console.WriteLine(dto.Location); // c
    Console.WriteLine(dto.EmployeeCount); // 2
}
static Expression<Func<TSource, TDestination>> Combine<TSource, TDestination>(
    params Expression<Func<TSource, TDestination>>[] selectors)
{
    var zeroth = ((MemberInitExpression)selectors[0].Body);
    var param = selectors[0].Parameters[0];
    List<MemberBinding> bindings = new List<MemberBinding>(zeroth.Bindings.OfType<MemberAssignment>());
    for (int i = 1; i < selectors.Length; i++)
    {
        var memberInit = (MemberInitExpression)selectors[i].Body;
        var replace = new ParameterReplaceVisitor(selectors[i].Parameters[0], param);
        foreach (var binding in memberInit.Bindings.OfType<MemberAssignment>())
        {
            bindings.Add(Expression.Bind(binding.Member,
                replace.VisitAndConvert(binding.Expression, "Combine")));
        }
    }

    return Expression.Lambda<Func<TSource, TDestination>>(
        Expression.MemberInit(zeroth.NewExpression, bindings), param);
}
class ParameterReplaceVisitor : ExpressionVisitor
{
    private readonly ParameterExpression from, to;
    public ParameterReplaceVisitor(ParameterExpression from, ParameterExpression to)
    {
        this.from = from;
        this.to = to;
    }
    protected override Expression VisitParameter(ParameterExpression node)
    {
        return node == from ? to : base.VisitParameter(node);
    }
}

它使用找到的第一个表达式中的构造函数,因此您可能想要健全性检查所有其他表达式是否在各自的 NewExpression 中使用了简单的构造函数 。不过,我已经把这个留给读者了。

编辑:在评论中,@Slaks 指出更多的 LINQ 可以使这个时间更短。他当然是对的——不过为了便于阅读,有点晦涩难懂:

static Expression<Func<TSource, TDestination>> Combine<TSource, TDestination>(
    params Expression<Func<TSource, TDestination>>[] selectors)
{
    var param = Expression.Parameter(typeof(TSource), "x");
    return Expression.Lambda<Func<TSource, TDestination>>(
        Expression.MemberInit(
            Expression.New(typeof(TDestination).GetConstructor(Type.EmptyTypes)),
            from selector in selectors
            let replace = new ParameterReplaceVisitor(
                  selector.Parameters[0], param)
            from binding in ((MemberInitExpression)selector.Body).Bindings
                  .OfType<MemberAssignment>()
            select Expression.Bind(binding.Member,
                  replace.VisitAndConvert(binding.Expression, "Combine")))
        , param);        
}

Not simple; you need to rewrite all the expressions - well, strictly speaking you can recycle most of one of them, but the problem is that you have different x in each (even though it looks the same), hence you need to use a visitor to replace all the parameters with the final x. Fortunately this isn't too bad in 4.0:

static void Main() {
    Expression<Func<Agency, AgencyDTO>> selector1 = x => new AgencyDTO { Name = x.Name };
    Expression<Func<Agency, AgencyDTO>> selector2 = x => new AgencyDTO { Phone = x.PhoneNumber };
    Expression<Func<Agency, AgencyDTO>> selector3 = x => new AgencyDTO { Location = x.Locality.Name };
    Expression<Func<Agency, AgencyDTO>> selector4 = x => new AgencyDTO { EmployeeCount = x.Employees.Count() };

    // combine the assignments from the 4 selectors
    var convert = Combine(selector1, selector2, selector3, selector4);

    // sample data
    var orig = new Agency
    {
        Name = "a",
        PhoneNumber = "b",
        Locality = new Location { Name = "c" },
        Employees = new List<Employee> { new Employee(), new Employee() }
    };

    // check it
    var dto = new[] { orig }.AsQueryable().Select(convert).Single();
    Console.WriteLine(dto.Name); // a
    Console.WriteLine(dto.Phone); // b
    Console.WriteLine(dto.Location); // c
    Console.WriteLine(dto.EmployeeCount); // 2
}
static Expression<Func<TSource, TDestination>> Combine<TSource, TDestination>(
    params Expression<Func<TSource, TDestination>>[] selectors)
{
    var zeroth = ((MemberInitExpression)selectors[0].Body);
    var param = selectors[0].Parameters[0];
    List<MemberBinding> bindings = new List<MemberBinding>(zeroth.Bindings.OfType<MemberAssignment>());
    for (int i = 1; i < selectors.Length; i++)
    {
        var memberInit = (MemberInitExpression)selectors[i].Body;
        var replace = new ParameterReplaceVisitor(selectors[i].Parameters[0], param);
        foreach (var binding in memberInit.Bindings.OfType<MemberAssignment>())
        {
            bindings.Add(Expression.Bind(binding.Member,
                replace.VisitAndConvert(binding.Expression, "Combine")));
        }
    }

    return Expression.Lambda<Func<TSource, TDestination>>(
        Expression.MemberInit(zeroth.NewExpression, bindings), param);
}
class ParameterReplaceVisitor : ExpressionVisitor
{
    private readonly ParameterExpression from, to;
    public ParameterReplaceVisitor(ParameterExpression from, ParameterExpression to)
    {
        this.from = from;
        this.to = to;
    }
    protected override Expression VisitParameter(ParameterExpression node)
    {
        return node == from ? to : base.VisitParameter(node);
    }
}

This uses the constructor from the first expression found, so you might want to sanity-check that all of the others use trivial constructors in their respective NewExpressions. I've left that for the reader, though.

Edit: In the comments, @Slaks notes that more LINQ could make this shorter. He is of course right - a bit dense for easy reading, though:

static Expression<Func<TSource, TDestination>> Combine<TSource, TDestination>(
    params Expression<Func<TSource, TDestination>>[] selectors)
{
    var param = Expression.Parameter(typeof(TSource), "x");
    return Expression.Lambda<Func<TSource, TDestination>>(
        Expression.MemberInit(
            Expression.New(typeof(TDestination).GetConstructor(Type.EmptyTypes)),
            from selector in selectors
            let replace = new ParameterReplaceVisitor(
                  selector.Parameters[0], param)
            from binding in ((MemberInitExpression)selector.Body).Bindings
                  .OfType<MemberAssignment>()
            select Expression.Bind(binding.Member,
                  replace.VisitAndConvert(binding.Expression, "Combine")))
        , param);        
}
看春风乍起 2024-11-17 12:58:28

如果所有选择器初始化 AgencyDTO 对象(如您的示例),您可以将表达式转换为 NewExpression 实例,然后调用 Expression.New 以及表达式的Members

您还需要一个 ExpressionVisitor,将原始表达式中的 ParameterExpression 替换为您要创建的表达式的单个 ParameterExpression

If all of the selectors will only initialize AgencyDTO objects (like your example), you can cast the expressions to NewExpression instances, then call Expression.New with the Members of the expressions.

You'll also need an ExpressionVisitor to replace the ParameterExpressions from the original expressions with a single ParameterExpression for the expression you're creating.

青衫负雪 2024-11-17 12:58:28

万一其他人偶然发现了与我类似的用例(我根据所需的详细程度选择了不同的目标类):

简化的场景:

public class BlogSummaryViewModel
{
    public string Name { get; set; }

    public static Expression<Func<Data.Blog, BlogSummaryViewModel>> Map()
    {
        return (i => new BlogSummaryViewModel
        {
            Name = i.Name
        });
    }
}

public class BlogViewModel : BlogSummaryViewModel
{
    public int PostCount { get; set; }

    public static Expression<Func<Data.Blog, BlogViewModel>> Map()
    {
        return (i => new BlogViewModel
        {
            Name = i.Name,
            PostCount = i.Posts.Count()
        });
    }
}

我调整了 @Marc Gravell 提供的解决方案,如下所示:

public static class ExpressionMapExtensions
{
    public static Expression<Func<TSource, TTargetB>> Concat<TSource, TTargetA, TTargetB>(
        this Expression<Func<TSource, TTargetA>> mapA, Expression<Func<TSource, TTargetB>> mapB)
        where TTargetB : TTargetA
    {
        var param = Expression.Parameter(typeof(TSource), "i");

        return Expression.Lambda<Func<TSource, TTargetB>>(
            Expression.MemberInit(
                ((MemberInitExpression)mapB.Body).NewExpression,
                (new LambdaExpression[] { mapA, mapB }).SelectMany(e =>
                {
                    var bindings = ((MemberInitExpression)e.Body).Bindings.OfType<MemberAssignment>();
                    return bindings.Select(b =>
                    {
                        var paramReplacedExp = new ParameterReplaceVisitor(e.Parameters[0], param).VisitAndConvert(b.Expression, "Combine");
                        return Expression.Bind(b.Member, paramReplacedExp);
                    });
                })),
            param);
    }

    private class ParameterReplaceVisitor : ExpressionVisitor
    {
        private readonly ParameterExpression original;
        private readonly ParameterExpression updated;

        public ParameterReplaceVisitor(ParameterExpression original, ParameterExpression updated)
        {
            this.original = original;
            this.updated = updated;
        }

        protected override Expression VisitParameter(ParameterExpression node) => node == original ? updated : base.VisitParameter(node);
    }
}

Map<扩展类的 /code> 方法则变为:

    public static Expression<Func<Data.Blog, BlogViewModel>> Map()
    {
        return BlogSummaryViewModel.Map().Concat(i => new BlogViewModel
        {
            PostCount = i.Posts.Count()
        });
    }

In case anyone else stumbles upon this with a similar use case as mine (my selects targeted different classes based on the level of detail needed):

Simplified scenario:

public class BlogSummaryViewModel
{
    public string Name { get; set; }

    public static Expression<Func<Data.Blog, BlogSummaryViewModel>> Map()
    {
        return (i => new BlogSummaryViewModel
        {
            Name = i.Name
        });
    }
}

public class BlogViewModel : BlogSummaryViewModel
{
    public int PostCount { get; set; }

    public static Expression<Func<Data.Blog, BlogViewModel>> Map()
    {
        return (i => new BlogViewModel
        {
            Name = i.Name,
            PostCount = i.Posts.Count()
        });
    }
}

I adapted the solution provided by @Marc Gravell like so:

public static class ExpressionMapExtensions
{
    public static Expression<Func<TSource, TTargetB>> Concat<TSource, TTargetA, TTargetB>(
        this Expression<Func<TSource, TTargetA>> mapA, Expression<Func<TSource, TTargetB>> mapB)
        where TTargetB : TTargetA
    {
        var param = Expression.Parameter(typeof(TSource), "i");

        return Expression.Lambda<Func<TSource, TTargetB>>(
            Expression.MemberInit(
                ((MemberInitExpression)mapB.Body).NewExpression,
                (new LambdaExpression[] { mapA, mapB }).SelectMany(e =>
                {
                    var bindings = ((MemberInitExpression)e.Body).Bindings.OfType<MemberAssignment>();
                    return bindings.Select(b =>
                    {
                        var paramReplacedExp = new ParameterReplaceVisitor(e.Parameters[0], param).VisitAndConvert(b.Expression, "Combine");
                        return Expression.Bind(b.Member, paramReplacedExp);
                    });
                })),
            param);
    }

    private class ParameterReplaceVisitor : ExpressionVisitor
    {
        private readonly ParameterExpression original;
        private readonly ParameterExpression updated;

        public ParameterReplaceVisitor(ParameterExpression original, ParameterExpression updated)
        {
            this.original = original;
            this.updated = updated;
        }

        protected override Expression VisitParameter(ParameterExpression node) => node == original ? updated : base.VisitParameter(node);
    }
}

The Map method of the extended class then becomes:

    public static Expression<Func<Data.Blog, BlogViewModel>> Map()
    {
        return BlogSummaryViewModel.Map().Concat(i => new BlogViewModel
        {
            PostCount = i.Posts.Count()
        });
    }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文