从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
所以看起来很明显,解决这个问题的方法是检查MemberExpression
的Expression
属性,直到它不再是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()
,因为需要IEnumerable
的String.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.Age
expression式变成了x => Convert(x.Age)
并打破了这里的其他解决scheme。 不过,我不明白这是什么处理Convert
部分。 : – /
从lambdaexpression式中检索属性名称交叉发布
正如所提到的问题,鬼鬼祟祟的回答是,如果你调用expression.ToString()
,它会给你类似于:
"o => o.ParentProperty.ChildProperty"
然后你可以从第一个时间段开始子串。
基于一些LinqPadtesting ,性能是可比的。