Combinez plusieurs expressions SELECT similaires en une seule expression

Comment combiner plusieurs expressions SELECT similaires en une seule expression?

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

Pas simple; vous devez réécrire toutes les expressions – eh bien, à proprement parler, vous pouvez recycler la plupart d’entre elles, mais le problème est que vous avez différents x dans chacune (même s’ils se ressemblent), vous devez donc utiliser un visiteur pour les remplacer. tous les parameters avec le x final . Heureusement, ce n’est pas si mal en 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); } } 

Ceci utilise le constructeur de la première expression trouvée, vous pouvez donc vérifier que tous les autres utilisent des constructeurs sortingviaux dans leurs NewExpression respectives. J’ai cependant laissé cela au lecteur.

Edit: Dans les commentaires, @Slaks note que plus de LINQ pourrait rendre cela plus court. Il a bien sûr raison – un peu dense pour une lecture facile, cependant:

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

Si tous les sélecteurs initialisent AgencyDTO objects AgencyDTO (comme dans votre exemple), vous pouvez NewExpression les expressions en occurrences NewExpression , puis appeler Expression.New avec les Members des expressions.

Vous aurez également besoin d’un ExpressionVisitor pour remplacer les ExpressionVisitor ParameterExpression des expressions d’origine par un seul ParameterExpression pour l’expression que vous créez.