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

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

private static Expression<Func> CombineSelectors(params Expression<Func>[] selectors) { // ??? return null; } private void Query() { Expression<Func> selector1 = x => new AgencyDTO { Name = x.Name }; Expression<Func> selector2 = x => new AgencyDTO { Phone = x.PhoneNumber }; Expression<Func> selector3 = x => new AgencyDTO { Location = x.Locality.Name }; Expression<Func> selector4 = x => new AgencyDTO { EmployeeCount = x.Employees.Count() }; using (RealtyContext context = Session.CreateContext()) { IQueryable agencies = context.Agencies.Select(CombineSelectors(selector3, selector4)); foreach (AgencyDTO agencyDTO in agencies) { // do something..; } } } 

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

 static void Main() { Expression> selector1 = x => new AgencyDTO { Name = x.Name }; Expression> selector2 = x => new AgencyDTO { Phone = x.PhoneNumber }; Expression> selector3 = x => new AgencyDTO { Location = x.Locality.Name }; Expression> 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 { 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> Combine( params Expression>[] selectors) { var zeroth = ((MemberInitExpression)selectors[0].Body); var param = selectors[0].Parameters[0]; List bindings = new List(zeroth.Bindings.OfType()); 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()) { bindings.Add(Expression.Bind(binding.Member, replace.VisitAndConvert(binding.Expression, "Combine"))); } } return Expression.Lambda>( 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使用了简单的构造函数。 不过,我已经把它留给了读者。

编辑:在评论中,@ Scott说明更多的LINQ可以缩短它。 他当然是正确的 – 虽然容易阅读有点密集:

 static Expression> Combine( params Expression>[] selectors) { var param = Expression.Parameter(typeof(TSource), "x"); return Expression.Lambda>( 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() select Expression.Bind(binding.Member, replace.VisitAndConvert(binding.Expression, "Combine"))) , param); } 

如果所有选择器初始化AgencyDTO对象(如您的示例),则可以将表达式AgencyDTO实例,然后使用Expression.NewMembers调用Expression.New

您还需要一个ExpressionVisitor来替换原始表达式中的ParameterExpression ,其中只有一个ParameterExpression用于您正在创建的表达式。