Costruire un'espressione indicizzata da un'altra espressione

asp.net-mvc c# expression-trees lambda

Domanda

Scenario

public class Element {
   public int Id {get;set;}
}

public class ViewModel {
   public IList<Element> Elements{get;set;}
}

Ho un metodo con un parametro di tipo Expression<Func<Element, int>> , che assomiglia m => m.Id

Mi piacerebbe trasformare

m => m.Id (dove m è un elemento)

a

x => x.Elements[0].Id dove x è un ViewModel, e 0 è un parametro "indice"

Quello che ho ora (è ovviamente generico, ho rimosso la parte generica per chiarezza)

public static class Helpers {
    public static Expression<Func<ViewModel, int>> BuildExpressionArrayFromExpression(
                this Expression<Func<Element, int>> expression,
                ViewModel model,
                int index = 0, 
                string bindingPropertyName = "Elements"//the name of the "List" property in ViewModel class
                ) 
    {
       var parameter = Expression.Parameter(typeof(ViewModel), "x");
       var viewModelProperty = model.GetType().GetProperty(bindingPropertyName);
       Expression member = parameter;//x => x
       member = Expression.Property(member, viewModelProperty);//x => x.Elements

       var test1 =  Expression.Property(member, "Item", new Expression[]{Expression.Constant(index)});
       //x => x.Elements.Item[0], and I don't want Item

       var test2 = Expression.Call(member, viewModelProperty.PropertyType.GetMethod("get_Item"), new Expression[] {Expression.Constant(index)});
       //x 0> x.Elements.get_Item(0), and I don't want get_Item(0)

       //code to add Id property to expression, not problematic
       return Expression.Lambda<Func<ViewModel, int>(member, parameter);
    }
}

MODIFICARE

Ho bisogno di x => x.Elements[0] e non x => x.Elements.Item[0] , perché l'espressione risultante deve essere chiamata con un InputExtensions.TextBoxFor(<myIndexedExpression>)

Immagina una classe come quella

public class Test {
  public int Id {get;set;}
  public IList<Element> Elements {get;set;}
}

e una Post Action

[HttpPost]
public ActionResult Edit(Test model) {
 bla bla bla.
}

Se gli attributi del nome dei miei input non sono ben generati, ho quindi problemi di binding (model.Elements è vuoto nella mia azione Post).

Dovrebbe essere l' attributo Nome del mio input

Elements[0]PropertyName

e ottengo (a seconda dei miei tentativi)

PropertyName

o (forse non esatto, provo a riprodurre questo caso)

Elements.Item[0].PropertyName

EDIT2

Ho anche provato una soluzione diversa, lavorando con ViewData.TemplateInfo.HtmlFieldPrefix, ma poi ottengo

Elements.[0].PropertyName

(e Elements_ 0 _PropertyName as Id).

Il primo punto non è voluto nel nome e il primo "doppio carattere di sottolineatura" dovrebbe essere semplice nell'id.

In realtà uso questa soluzione, lavorando con regex (argh) per rimuovere gli indesiderati. e _, ma vorrei evitarlo.

Risposta accettata

Questo è solo una questione di rappresentazione della stringa dell'albero delle espressioni e non puoi cambiarlo. L'albero delle espressioni che stai costruendo va bene. Puoi vedere lo stesso effetto se usi un'espressione lambda per costruire l'albero delle espressioni:

using System;
using System.Collections.Generic;
using System.Linq.Expressions;

class Test
{
    public static void Main()
    {
        Expression<Func<List<string>, string>> expression = list => list[0];
        Console.WriteLine(expression);
    }
}

Produzione:

list => list.get_Item(0)

Sarei molto sorpreso se il risultato di chiamare ToString() sull'albero delle espressioni fosse davvero il problema che stai affrontando. Invece di dirci il risultato che pensi di aver bisogno e la vaga giustificazione "I need it for MVC binding reason", dovresti spiegare cosa sta veramente andando storto. Sospetto fortemente che il problema non sia dove pensi che sia.


Risposta popolare

Expression<Func<Element, int>> expr1 =
    m => m.Id;
Expression<Func<ViewModel, Element>> expr2 =
    x => x.Elements[0];

Expression<Func<ViewModel, int>> result =
    expr1.ComposeWith(expr2);

Risultato:

expr1 = m => m.Id
expr2 = x => x.Elements.get_Item(0)
result = x => x.Elements.get_Item(0).Id

Sostituisce il parametro di expr1 ( m ) con il corpo di expr2 ( x.Elements[0] ) e sostituisce il parametro di input con quello di expr2 ( x ).

Metodo di estensione ComposeWith :

public static class FunctionalExtensions
{
    public static Expression<Func<TInput,TResult>> ComposeWith<TInput,TParam,TResult>(
        this Expression<Func<TParam,TResult>> left, Expression<Func<TInput,TParam>> right)
    {
        var param = left.Parameters.Single();

        var visitor = new ParameterReplacementVisitor(p => {
            if (p == param)
            {
                return right.Body;
            }
            return null;
        });

        return Expression.Lambda<Func<TInput,TResult>>(
            visitor.Visit(left.Body),
            right.Parameters.Single());
    }

    private class ParameterReplacementVisitor : ExpressionVisitor
    {
        private Func<ParameterExpression, Expression> _replacer;

        public ParameterReplacementVisitor(Func<ParameterExpression, Expression> replacer)
        {
            _replacer = replacer;
        }

        protected override Expression VisitParameter(ParameterExpression node)
        {
            var replaced = _replacer(node);
            return replaced ?? node;
        }
    }
}


Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow
È legale questo KB? Sì, impara il perché
Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow
È legale questo KB? Sì, impara il perché