变换表达式m => m.Name to m => M [指数]请将.Name

时间:2012-11-19 10:47:53

标签: asp.net-mvc-3 lambda expression

鉴于Expression<Func<T, TValue>>(例如m => m.Name)和index,我希望能够将表达式转换为m => m[index].Name)。而且我必须承认我被卡住了......

如果你想要“为什么地狱”(并且可能找到更好的方法),我会告诉你实际情况。

场景:想象一下服务器端可编辑网格没有javascript )。

我用帮助器构建我的网格,看起来像这样:

@(Html.Grid(Model)
.Columns(columns => {
   columns.Edit(m => m.Name);
   columns.Edit(m => m.Code);
})
.AsEditable());

模型是IQueryable<T>

m => m.NameExpression<Func<T, TValue>>TValue是字符串)

m => m.CodeExpression<Func<T, TValue>>TValue是int)

渲染我的视图时,我想显示一个html表单。 枚举IQueryable<T>(顺序,分页)。 =&GT;确定

所以我会有5个,10个或20个List<T>T

NameCode应使用经典TextBox表示为HtmlHelper.TextBoxFor(Expression<Func<T, TValue>>)(创建HtmlHelper<T>没问题)

但是,由于我有一个列表,如果我想要正确的模型绑定,我不能直接使用m => m.Name,但应该使用m => m[indexOfItem in List<T>].Name

修改:更多详情:

所以我们假设我们有一个实体类

public class Test {
  public int Id {get;set;}
  public string Name {get;set;}
  public string Code {get;set;}
}

然后,检索IQueryable<Test>

的方法

然后是一个视图

@model IQueryable<Test>

@(Html.Grid(Model)
    .Columns(columns => {
       columns.Edit(m => m.Name);
       columns.Edit(m => m.Code);
    })
    .AsEditable());

如您所见,作为参数给出的模型是IQueryable<Test>

m => m.Name

m => m.Code

只是模型的属性(我想在我的网格中显示为TextBox)。

该模型是IQueryable<T>(不是IEnumerable<T>),因为网格管理排序和分页,因此我的控制器和服务层不需要了解分页和排序。

更清楚吗?

2 个答案:

答案 0 :(得分:1)

通过编写自定义ExpressionVisitor

可以轻松实现这一目标
public static class ExpressionExtensions
{
    private class Visitor : ExpressionVisitor
    {
        private readonly int _index;
        public Visitor(int index)
        {
            _index = index;
        }

        protected override Expression VisitParameter(ParameterExpression node)
        {
            return Expression.ArrayIndex(GetArrayParameter(node), Expression.Constant(_index));
        }

        public ParameterExpression GetArrayParameter(ParameterExpression parameter)
        {
            var arrayType = parameter.Type.MakeArrayType();
            return Expression.Parameter(arrayType, parameter.Name);
        }
    }

    public static Expression<Func<T[], TValue>> BuildArrayFromExpression<T, TValue>(
        this Expression<Func<T, TValue>> expression,
        int index
    )
    {
        var visitor = new Visitor(index);
        var nexExpression = visitor.Visit(expression.Body);
        var parameter = visitor.GetArrayParameter(expression.Parameters.Single());
        return Expression.Lambda<Func<T[], TValue>>(nexExpression, parameter);
    }
}

然后您可以像这样使用此扩展方法:

Expression<Func<Test, string>> ex = m => m.Code;
Expression<Func<Test[], string>> newEx = ex.BuildArrayFromExpression(1);

答案 1 :(得分:0)

嗯,得到的东西(丑陋的,仅用于测试目的)起作用,但它使事情不清楚,所以我会等待更好的解决方案或以另一种方式构建我的输入:

public static Expression<Func<T[], TValue>> BuildArrayFromExpression<T, TValue>(this Expression<Func<T, TValue>> expression, int index)
{
    var parameter = Expression.Parameter(typeof(T[]), "m");
    Expression body = Expression.ArrayIndex(parameter, Expression.Constant(index));
    var type = typeof(T);
    var properties = expression.Body.ToString().Split('.');//ugly shortcut for test only
    foreach (var property in properties.Skip(1))
    {
        var pi = type.GetProperty(property);
        body = Expression.Property(body, type.GetProperty(property));
        type = pi.PropertyType;
    }
    return Expression.Lambda<Func<T[], TValue>>(body, parameter);
}

在RenderMethod中使用,为List<T>的每一行调用(从IQueryable<T>返回的分页/有序列表)

public override ... RenderContent(T dataItem) {
  var helper = new HtmlHelper<T[]>(GridModel.Context, new GridViewDataContainer<T[]>(GridModel.PaginatedItems.ToArray(), GridModel.Context.ViewData));
  var modifiedExpression = Expression.BuildArrayFromExpression(GridModel.PaginatedItems.IndexOf(dataItem));//GridModel.PaginatedItems is the List<T> returned when "executing" the IQueryable<T>
  var textBox = helper.TextBoxFor(modifiedExpression);
  ...
}