【问题标题】:Building an indexed expression from another expression从另一个表达式构建索引表达式
【发布时间】:2013-03-13 18:33:47
【问题描述】:

场景

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

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

我有一个带有Expression&lt;Func&lt;Element, int&gt;&gt;类型参数的方法, 看起来像m =&gt; m.Id

我想变身

m =&gt; m.Id(其中 m 是一个元素)

x =&gt; 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 =&gt; x.Elements[0] 而不是x =&gt; x.Elements.Item[0],因为 必须使用 InputExtensions.TextBoxFor(&lt;myIndexedExpression&gt;)

调用生成的表达式

想象一下这样的课程

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

还有一个帖子操作

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

如果我的输入的名称属性没有很好地生成,我就会遇到绑定问题(model.Elements 在我的 Post Action 中是空的)。

我输入的名称属性应该是

Elements[0]PropertyName

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

PropertyName

或者(可能不准确,我试着重现这个案例)

Elements.Item[0].PropertyName

EDIT2

还尝试了不同的解决方案,使用 ViewData.TemplateInfo.HtmlFieldPrefix, 但我得到了

Elements.[0].PropertyName

(和 Elements_0_PropertyName 作为 Id)。

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

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

【问题讨论】:

  • 您在哪里使用Expression&lt;Func&lt;Element, int&gt;&gt; expression 来调用分机?
  • @HamletHakobyan 有点长解释,但它是一个可编辑的网格助手。如果“list”元素是模型,没问题。如果它是 ViewModel 的一部分(我现在面临的),并且我想将包含网格元素的模型绑定到我的 post 操作中,我需要解决这个问题...
  • 你不懂我。您对 Expression&lt;Func&lt;Element, int&gt;&gt; 类型进行了扩展,但您没有在扩展中使用扩展对象。
  • @HamletHakobyan 哦...好吧,我在我的扩展程序末尾使用它(未显示且没有问题的代码)(m =&gt; m.a.b.Id 应该变为 x =&gt; x.list[0].a.b.Id :我从 @ 获取 a.b.Id 987654341@

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


【解决方案1】:

只是表达式树的字符串表示的问题,你不能改变它。您正在构建的表达式树很好。如果使用 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 绑定原因”的理由,不如说明实际出了什么问题。我强烈怀疑问题不在你认为的地方。

【讨论】:

  • 您说的完全正确,我的“约束性原因”根本不清楚。真正的问题是我尝试在这些生成的表达式上使用 InputExtensions :myHelper_.TextBoxFor(myArrayExpression) :这就是我遇到问题的地方,因为 id 和 name 没有很好地生成,这就是为什么我有绑定问题......是吗更清楚?
  • 哦哦。想我错过了什么。实际上,它似乎使用Expression.Call(member, viewModelProperty.PropertyType.GetMethod("get_Item"), new Expression[] {Expression.Constant(index)});,但不适用于Expression.Property(member, "Item", new Expression[]{Expression.Constant(index)});(InputExtensions 使用ExpressionHelper.GetExpressionText(expression),结果确实不同。而且我还认为当我问的时候,我使用了 MVC3,现在我'm on MVC4. 也许它工作的原因,必须承认我很惊讶......
  • 你需要很多清楚什么是“工作”,什么是“不工作”。你说InputExtensions 使用GetExpressionText(),但你没有给出如何使用它的指示。我不想将表达式树的文本表示用于诊断以外的任何内容。
  • 好吧,我只是使用public static MvcHtmlString TextBoxFor&lt;TModel, TProperty&gt;(this HtmlHelper&lt;TModel&gt; htmlHelper, Expression&lt;Func&lt;TModel, TProperty&gt;&gt; expression);(以我的表达式为参数),这是System.Web.Mvc.Html中InputExtensions类的一个方法。查看源代码,我注意到它在表达式上使用GetExpressionText 来构建我输入的名称和ID。所以我测试了我的解决方案,将我的表达式传递给ExpressionHelpers.GetExpressionText(LambdaExpression expression),并注意到根据用于创建我的表达式的方法不同。
  • @RaphaëlAlthaus:随着您的更新 - only 问题是在生成的 HTML 中没有正确生成 name 属性吗?您如何确定“正确”在这里的真正含义?整个问题对我来说似乎仍然很困惑。
【解决方案2】:
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])的主体,并将输入参数替换为来自expr2x)的参数。

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

【讨论】:

    【解决方案3】:

    您应该可以使用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);
    

    【讨论】:

    • 谢谢,但这会在 Expression.MakeIndex Method 'System.Collections.Generic.IList1[Element] get_Elements()' declared on type 'ViewModel' cannot be called with instance of type 'System.Collections.Generic.IList1[Element]' 上引发 ArgumentException
    • @RaphaëlAlthaus - 我认为索引属性信息是错误的。我已更新代码以使用 IList&lt;T&gt;.Item 属性。这能解决问题吗?
    • 嗯,没有更多的例外,但我得到了......我在 test1 中拥有的东西:x =&gt; x.Elements.Item[0]。物品回来了。这是一个有效的表达式,但我想避免Item。这可能是不可能的,但是......
    • @RaphaëlAlthaus:听起来你在争论的只是在表达式上调用ToString 的结果。您将无法更改它,如果这真的影响了 MVC 绑定,我会感到非常惊讶。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-06-06
    • 1970-01-01
    • 2011-12-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多