0

给定一个Expression<Func<T, TValue>>(like m => m.Name) 和 an 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.Name是一个Expression<Func<T, TValue>>TValue是字符串)

m => m.Code是一个Expression<Func<T, TValue>>TValue是 int)

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

所以我将有List<T>5、10 或 20 个T项目。

并且应该表示为Name,使用经典 (创建没有问题)CodeTextBoxHtmlHelper.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>),因为 Grid 管理排序和分页,所以我的控制器和服务层不需要知道分页和排序。

是不是更清楚了?

4

2 回答 2

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);
于 2012-11-19T17:15:50.737 回答
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 中使用,为 my 的每一行调用List<T>(从 my 返回的分页/有序列表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);
  ...
}
于 2012-11-19T13:49:46.157 回答