从另一个表达式构建索引表达式

时间:2013-03-13 18:33:47

标签: c# asp.net-mvc lambda expression-trees

情景

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

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

我有一个参数类型为Expression<Func<Element, int>>的方法, 看起来像m => m.Id

我想改造

m => m.Id(其中m是元素)

x => x.Elements[0].Id其中x是ViewModel,0是“索引”参数

我现在拥有的(当然是通用的,为清晰起见,我删除了通用部分)

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

修改

我需要x => x.Elements[0]而不是x => x.Elements.Item[0],因为 必须使用InputExtensions.TextBoxFor(<myIndexedExpression>)

调用生成的表达式

想象一下这样的课程

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

和后期行动

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

如果输入的名称属性生成不好,那么我就会遇到绑定问题(我的Post Action中的model.Elements为空)。

我输入的名称属性应为

Elements[0]PropertyName

我得到(取决于我的尝试)

PropertyName

或(也许不准确,我试图重现这种情况)

Elements.Item[0].PropertyName

EDIT2

还尝试了一个不同的解决方案,使用ViewData.TemplateInfo.HtmlFieldPrefix, 但我接着

Elements.[0].PropertyName

(和Elements_ 0 _PropertyName为Id)。

第一个点在名称中是不需要的,第一个“双下划线”应该是id中的一个简单。

我实际上使用这个解决方案,使用正则表达式(argh)来删除不需要的东西。和_,但我想避免这种情况。

3 个答案:

答案 0 :(得分:6)

这是只是表达式树的字符串表示的问题,你不能改变它。你正在构建的表达式树很好。如果使用lambda表达式构建表达式树,则可以看到相同的效果:

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

输出:

list => list.get_Item(0)

如果在表达式树上调用ToString()的结果确实你正面临的问题,我会感到非常惊讶。而不是告诉我们你认为你需要的结果和模糊的“我需要它以获得MVC绑定理由”的理由,你应该解释实际上出了什么问题。我强烈怀疑问题不在你想象的地方。

答案 1 :(得分:1)

您应该可以使用MakeIndex来制作索引器表达式:

MemberExpression memberExpr = Expression.Property(member, viewModelProperty);//x => x.Elements
var indexProperty = typeof(IList<Element>).GetProperty("Item");
var indexExpr = Expression.MakeIndex(memberExpr, indexProperty, new Expression[]{Expression.Constant(index)});

return Expression.Lambda<Func<ViewModel, int>(indexExpr, parameter);

答案 2 :(得分:1)

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

<强>结果:

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

它将expr1m)的参数替换为expr2x.Elements[0])的正文,并将输入参数替换为expr2的参数(x)。

扩展方法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;
        }
    }
}