我使用一些经过序列化的强类型表达式,以允许我的UI代码具有强类型的排序和搜索表达式。它们的类型为Expression<Func<TModel,TProperty>>
,使用方式如下:SortOption.Field = (p => p.FirstName);
。对于这个简单的案例,我已经让它完美地工作了。
在我们开始使用深度嵌套属性(SortOption.Field = (p => p.Address.State.Abbreviation);
)之前,我用来解析"FirstName“属性的代码实际上是重用了我们使用的第三方产品中的一些现有功能,而且工作得很好。这段代码在需要支持深度嵌套的属性方面有一些非常不同的假设。
至于这段代码做了什么,我并不是真的理解它,而不是改变代码,我认为我应该从头开始编写这个功能。然而,我不知道有什么好方法可以做到这一点。我怀疑我们可以做一些比执行ToString()和执行字符串解析更好的事情。那么,处理琐碎和深度嵌套的情况的好方法是什么呢?
要求:
给定表达式,我需要一个字符串"FirstName"
.
p => p.FirstName
,我需要一个字符串,p => p.FirstName
,p => p.Address.State.Abbreviation
。虽然我的问题的答案并不重要,但我怀疑我的序列化/反序列化代码可能对将来发现这个问题的其他人有用,所以它如下所示。同样,这段代码对这个问题并不重要--我只是认为它可能会对某些人有所帮助。请注意,DynamicExpression.ParseLambda
来自Dynamic LINQ的东西,而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
}
发布于 2010-05-07 23:14:14
诀窍是:任何这种形式的表达式..。
obj => obj.A.B.C // etc.
...is实际上只是一堆嵌套的MemberExpression
对象。
首先,你需要:
MemberExpression: obj.A.B.C
Expression: obj.A.B // MemberExpression
Member: C
将上面的Expression
作为MemberExpression
进行评估,可以得到以下结果:
MemberExpression: obj.A.B
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
...but实际上是一个Func<T, object>
;在这种情况下,编译器会将上面的表达式转换为:
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) [e.g., int -> object]
// OR:
// obj => ConvertChecked(obj.Property) [e.g., 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
发布于 2010-05-07 23:28:20
下面是一个方法,可以让你获得字符串表示,即使你有嵌套的属性:
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()
,因为接受IEnumerable
的String.Join
重载是在.NET 4中首次添加的。
发布于 2010-05-07 23:12:56
对于来自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
我建议你看看HTMLMVC2代码(来自aspnet.codeplex.com),因为它有类似的用于ASP.NET helpers的API……Html.TextBoxFor( p => p.FirstName )
等
https://stackoverflow.com/questions/2789504
复制相似问题