从Expression <Func <TModel,TProperty >>获取该属性的string

我使用了一些强types的expression式,这些expression式被序列化为允许我的UI代码具有强types的sorting和searchexpression式。 这些是typesExpression<Func<TModel,TProperty>>并且被使用为: SortOption.Field = (p => p.FirstName); 。 我已经完成了这个简单的情况下完美的工作。

我用于parsing“FirstName”属性的代码实际上是重用了我们使用的第三方产品中的一些现有function,并且它工作得很好,直到我们开始深入嵌套的属性( SortOption.Field = (p => p.Address.State.Abbreviation); )。 这段代码在支持深度嵌套属性的需求方面有一些非常不同的假设。

至于这段代码是干什么的,我不是很了解它,而不是改变代码,我想我应该从头开始写这个function。 但是,我不知道这样做的方法。 我怀疑我们可以做一些比做一个ToString()和执行stringparsing更好的东西。 那么,如何处理这些微不足道和深层嵌套的情况呢?

要求:

  • 给定expression式p => p.FirstName我需要一个"FirstName"的string。
  • 给定expression式p => p.Address.State.Abbreviation我需要一个"Address.State.Abbreviation"string

虽然对于我的问题的答案并不重要,但是我怀疑我的序列化/反序列化代码对将来发现这个问题的其他人可能是有用的,所以它在下面。 再一次,这个代码对于这个问题并不重要 – 我只是认为这可能对别人有帮助。 请注意, DynamicExpression.ParseLambda来自dynamicLINQ的东西和Property.PropertyToString()是这个问题是关于。

 /// <summary> /// This defines a framework to pass, across serialized tiers, sorting logic to be performed. /// </summary> /// <typeparam name="TModel">This is the object type that you are filtering.</typeparam> /// <typeparam name="TProperty">This is the property on the object that you are filtering.</typeparam> [Serializable] public class SortOption<TModel, TProperty> : ISerializable where TModel : class { /// <summary> /// Convenience constructor. /// </summary> /// <param name="property">The property to sort.</param> /// <param name="isAscending">Indicates if the sorting should be ascending or descending</param> /// <param name="priority">Indicates the sorting priority where 0 is a higher priority than 10.</param> public SortOption(Expression<Func<TModel, TProperty>> property, bool isAscending = true, int priority = 0) { Property = property; IsAscending = isAscending; Priority = priority; } /// <summary> /// Default Constructor. /// </summary> public SortOption() : this(null) { } /// <summary> /// This is the field on the object to filter. /// </summary> public Expression<Func<TModel, TProperty>> Property { get; set; } /// <summary> /// This indicates if the sorting should be ascending or descending. /// </summary> public bool IsAscending { get; set; } /// <summary> /// This indicates the sorting priority where 0 is a higher priority than 10. /// </summary> public int Priority { get; set; } #region Implementation of ISerializable /// <summary> /// This is the constructor called when deserializing a SortOption. /// </summary> protected SortOption(SerializationInfo info, StreamingContext context) { IsAscending = info.GetBoolean("IsAscending"); Priority = info.GetInt32("Priority"); // We just persisted this by the PropertyName. So let's rebuild the Lambda Expression from that. Property = DynamicExpression.ParseLambda<TModel, TProperty>(info.GetString("Property"), default(TModel), default(TProperty)); } /// <summary> /// Populates a <see cref="T:System.Runtime.Serialization.SerializationInfo"/> with the data needed to serialize the target object. /// </summary> /// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo"/> to populate with data. </param> /// <param name="context">The destination (see <see cref="T:System.Runtime.Serialization.StreamingContext"/>) for this serialization. </param> public void GetObjectData(SerializationInfo info, StreamingContext context) { // Just stick the property name in there. We'll rebuild the expression based on that on the other end. info.AddValue("Property", Property.PropertyToString()); info.AddValue("IsAscending", IsAscending); info.AddValue("Priority", Priority); } #endregion } 

这里的诀窍是:这种forms的任何expression式…

 obj => obj.ABC // etc. 

…实际上只是一堆嵌套的MemberExpression对象。

首先你有:

 MemberExpression: obj.ABC Expression: obj.AB // MemberExpression Member: C 

以上评估Expression 为一个MemberExpression给你:

 MemberExpression: obj.AB Expression: obj.A // MemberExpression Member: B 

最后,在上面(在“顶部”),你有:

 MemberExpression: obj.A Expression: obj // note: not a MemberExpression Member: A 

所以看起来很明显,解决这个问题的方法是检查MemberExpressionExpression属性,直到它不再是MemberExpression


更新 :似乎有一个额外的旋转你的问题。 这可能是你有一些看起来Func<T, int> lambda …

 p => p.Age 

…但实际上是一个Func<T, object> ; 在这种情况下,编译器会将上述expression式转换为:

 p => Convert(p.Age) 

调整这个问题其实并不像看起来那么困难。 看看我更新的代码,以处理它的一种方法。 请注意,通过将获取MemberExpression的代码抽象到它自己的方法( TryFindMemberExpression )中,这种方法使GetFullPropertyName方法保持相当干净,并允许您在将来添加额外的检查 – 也许,如果您发现自己面临新的场景你原来没有考虑过的 – 不必经过太多的代码。


为了说明:这段代码为我工作。

 // code adjusted to prevent horizontal overflow static string GetFullPropertyName<T, TProperty> (Expression<Func<T, TProperty>> exp) { MemberExpression memberExp; if (!TryFindMemberExpression(exp.Body, out memberExp)) return string.Empty; var memberNames = new Stack<string>(); do { memberNames.Push(memberExp.Member.Name); } while (TryFindMemberExpression(memberExp.Expression, out memberExp)); return string.Join(".", memberNames.ToArray()); } // code adjusted to prevent horizontal overflow private static bool TryFindMemberExpression (Expression exp, out MemberExpression memberExp) { memberExp = exp as MemberExpression; if (memberExp != null) { // heyo! that was easy enough return true; } // if the compiler created an automatic conversion, // it'll look something like... // obj => Convert(obj.Property) [eg, int -> object] // OR: // obj => ConvertChecked(obj.Property) [eg, int -> long] // ...which are the cases checked in IsConversion if (IsConversion(exp) && exp is UnaryExpression) { memberExp = ((UnaryExpression)exp).Operand as MemberExpression; if (memberExp != null) { return true; } } return false; } private static bool IsConversion(Expression exp) { return ( exp.NodeType == ExpressionType.Convert || exp.NodeType == ExpressionType.ConvertChecked ); } 

用法:

 Expression<Func<Person, string>> simpleExp = p => p.FirstName; Expression<Func<Person, string>> complexExp = p => p.Address.State.Abbreviation; Expression<Func<Person, object>> ageExp = p => p.Age; Console.WriteLine(GetFullPropertyName(simpleExp)); Console.WriteLine(GetFullPropertyName(complexExp)); Console.WriteLine(GetFullPropertyName(ageExp)); 

输出:

 FirstName Address.State.Abbreviation Age 

这里有一个方法可以让你得到string表示,即使你有嵌套的属性:

 public static string GetPropertySymbol<T,TResult>(Expression<Func<T,TResult>> expression) { return String.Join(".", GetMembersOnPath(expression.Body as MemberExpression) .Select(m => m.Member.Name) .Reverse()); } private static IEnumerable<MemberExpression> GetMembersOnPath(MemberExpression expression) { while(expression != null) { yield return expression; expression = expression.Expression as MemberExpression; } } 

如果你仍然在.NET 3.5上,你需要在调用Reverse()之后粘贴一个ToArray() Reverse() ,因为需要IEnumerableString.Join的重载首先被添加到了.NET 4中。

对于来自p => p.FirstName的“FirstName”

 Expression<Func<TModel, TProperty>> expression; //your given expression string fieldName = ((MemberExpression)expression.Body).Member.Name; //watch out for runtime casting errors 

我会build议你检查ASP.NET MVC 2代码(从aspnet.codeplex.com),因为它有类似的Html帮手的API … Html.TextBoxFor(p => p.FirstName)等

我为此写了一个小小的代码,而且似乎工作。

鉴于以下三个类定义:

 class Person { public string FirstName { get; set; } public string LastName { get; set; } public Address Address { get; set; } } class State { public string Abbreviation { get; set; } } class Address { public string City { get; set; } public State State { get; set; } } 

以下方法将给你完整的属性path

 static string GetFullSortName<TModel, TProperty>(Expression<Func<TModel, TProperty>> expression) { var memberNames = new List<string>(); var memberExpression = expression.Body as MemberExpression; while (null != memberExpression) { memberNames.Add(memberExpression.Member.Name); memberExpression = memberExpression.Expression as MemberExpression; } memberNames.Reverse(); string fullName = string.Join(".", memberNames.ToArray()); return fullName; } 

对于这两个电话:

 fullName = GetFullSortName<Person, string>(p => p.FirstName); fullName = GetFullSortName<Person, string>(p => p.Address.State.Abbreviation); 

另一个简单的方法是使用System.Web.Mvc.ExpressionHelper.GetExpressionText方法。 在我的下一个打击,我会写更多的细节。 看看http://carrarini.blogspot.com/

来自MVC的ExpressionHelper源码在这里

https://github.com/ASP-NET-MVC/aspnetwebstack/blob/master/src/System.Web.Mvc/ExpressionHelper.cs

只要参加这个课程,就可以避免依赖MVC,并为您处理特殊的边缘案例。

免责声明:不知道许可证的工作只是像这样的一个类 – 但似乎很无害

基于这个和几个相关的问题/答案在这里,这里是我使用的简单方法:

 protected string propertyNameFromExpression<T>(Expression<Func<T, object>> prop) { // http://stackoverflow.com/questions/2789504/get-the-property-as-a-string-from-an-expressionfunctmodel-tproperty // http://stackoverflow.com/questions/767733/converting-a-net-funct-to-a-net-expressionfunct // http://stackoverflow.com/questions/793571/why-would-you-use-expressionfunct-rather-than-funct MemberExpression expr; if (prop.Body is MemberExpression) // .Net interpreted this code trivially like t => t.Id expr = (MemberExpression)prop.Body; else // .Net wrapped this code in Convert to reduce errors, meaning it's t => Convert(t.Id) - get at the // t.Id inside expr = (MemberExpression)((UnaryExpression)prop.Body).Operand; string name = expr.Member.Name; return name; } 

你可以简单地使用它:

 string name = propertyNameFromExpression(t => t.Id); // returns "Id" 

然而,这个方法的错误检查比在这里发布的其他错误检查更less – 基本上它被认为是正确调用的,这在你的应用中可能不是一个安全的假设。

我现在有100%工作的代码如下,但是我并不真正了解它在做什么(尽pipe事实上我修改了它,使得它能够通过debugging器处理这些深度嵌套的场景)。

  internal static string MemberWithoutInstance(this LambdaExpression expression) { var memberExpression = expression.ToMemberExpression(); if (memberExpression == null) { return null; } if (memberExpression.Expression.NodeType == ExpressionType.MemberAccess) { var innerMemberExpression = (MemberExpression) memberExpression.Expression; while (innerMemberExpression.Expression.NodeType == ExpressionType.MemberAccess) { innerMemberExpression = (MemberExpression) innerMemberExpression.Expression; } var parameterExpression = (ParameterExpression) innerMemberExpression.Expression; // +1 accounts for the ".". return memberExpression.ToString().Substring(parameterExpression.ToString().Length + 1); } return memberExpression.Member.Name; } internal static MemberExpression ToMemberExpression(this LambdaExpression expression) { var memberExpression = expression.Body as MemberExpression; if (memberExpression == null) { var unaryExpression = expression.Body as UnaryExpression; if (unaryExpression != null) { memberExpression = unaryExpression.Operand as MemberExpression; } } return memberExpression; } public static string PropertyToString<TModel, TProperty>(this Expression<Func<TModel, TProperty>> source) { return source.MemberWithoutInstance(); } 

当我的expression式types为Expression<Func<TModel,object>>时,这个解决scheme处理它Expression<Func<TModel,object>>并且为我的参数传入各种对象types。 当我这样做时,我的x => x.Ageexpression式变成了x => Convert(x.Age)并打破了这里的其他解决scheme。 不过,我不明白这是什么处理Convert部分。 : – /

从lambdaexpression式中检索属性名称交叉发布

正如所提到的问题,鬼鬼祟祟的回答是,如果你调用expression.ToString() ,它会给你类似于:

 "o => o.ParentProperty.ChildProperty" 

然后你可以从第一个时间段开始子串。

基于一些LinqPadtesting ,性能是可比的。