将几个类似的SELECT表达式组合成一个表达式

Bor*_*nko 9 c# linq linq-expressions

如何将几个类似的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..;
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

Mar*_*ell 17

不简单; 你需要重写所有的表达式 - 严格来说,你可以回收其中的大部分表达式,但问题是你们x每个都有不同的(即使看起来相同),因此你需要使用访问者来替换所有最后 的参数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);
    }
}
Run Code Online (Sandbox Code Playgroud)

这使用了找到的第一个表达式中的构造函数,因此您可能需要进行完整性检查,以确保所有其他构造函数在各自的NewExpressions中使用了一些简单的构造函数.不过,我已经把它留给了读者.

编辑:在评论中,@ 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);        
}
Run Code Online (Sandbox Code Playgroud)