获取的财产,作为字符串,从防爆pression&LT;&Func键LT;的TModel,TProperty&GT;&GT; [英] Get the property, as a string, from an Expression<Func<TModel,TProperty>>
问题描述
我使用一些强类型的前pressions是得到系列化,让我的用户界面code有强类型的排序和搜索前pressions。这些类型的防爆pression&LT; Func键&LT;的TModel,TProperty&GT;&GT;
和用作这样的: SortOption.Field =(P = &GT; p.FirstName);
。我已经得到这个工作完全为这个简单的例子。
在code,我使用的解析名字财产离开那里实际上是重复使用的,我们使用第三方产品的一些现有的功能和它的伟大工程,直到我们开始deeply-工作嵌套属性( SortOption.Field =(p =&GT; p.Address.State.Abbreviation);
)。这code有一些非常不同的假设在需要支持深层嵌套的属性。
至于这code做什么,我真的不明白它,而不是改变了code,我想我应该从头开始编写此功能。但是,我不知道一个的好的方式做到这一点。我想我们可以做的事情比做toString()和执行字符串解析更好。那么,这样做是为了处理琐碎和深度嵌套的情况下,一个好办法?
要求:
- 鉴于前pression
P =&GT; p.FirstName
我需要名字
的字符串。 - 鉴于前pression
P =&GT; p.Address.State.Abbreviation
我需要的字符串Address.State.Abbreviation
虽然它不是一个回答我的问题很重要,我怀疑我的序列化/反序列化code可能是别人谁发现在未来的这个问题很有用,所以下面是。同样,这code是没有问题的重要 - 我只是想这可能帮助别人。需要注意的是 DynamicEx pression.ParseLambda
来自<一个href=\"http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx\">Dynamic LINQ 东西, Property.PropertyToString()
是这个问题是关于什么的。
///&LT;总结&gt;
///这定义了一个框架,通过跨越序列层,排序逻辑被执行。
///&LT; /总结&gt;
///&LT; typeparam名=的TModel&gt;这是您要筛选对象的类型&lt; / typeparam&GT;
///&LT; typeparam NAME =TProperty&gt;这是LT您过滤对象的属性&; / typeparam&GT;
[Serializable接口]
公共类SortOption&LT;的TModel,TProperty&GT; :ISerializable的,其中的TModel:类
{
///&LT;总结&gt;
///便捷构造。
///&LT; /总结&gt;
///&LT; PARAM NAME =财产方式&gt;排序酒店&LT; /参数&GT;
///&LT; PARAM NAME =isAscending&gt;表示如果排序应升序或降序&LT; /参数&GT;
///&所述; PARAM NAME =优先权&gt;表示的排序优先级,其中0是大于10℃的更高的优先级; /参数&GT;
公共SortOption(前pression&LT;&Func键LT;的TModel,TProperty&GT;&GT;物业,布尔isAscending = TRUE,INT优先级= 0)
{
属性=财产;
IsAscending = isAscending;
优先级=优先权;
} ///&LT;总结&gt;
///默认构造函数。
///&LT; /总结&gt;
公共SortOption()
:这个(空)
{
} ///&LT;总结&gt;
///这是在物体上的字段筛选。
///&LT; /总结&gt;
公共防爆pression&LT;&Func键LT;的TModel,TProperty&GT;&GT;物业{搞定;组; } ///&LT;总结&gt;
///这表明,如果排序应升序或降序。
///&LT; /总结&gt;
公共BOOL IsAscending {搞定;组; } ///&LT;总结&gt;
///这表示排序优先级,其中0是比10更高的优先级。
///&LT; /总结&gt;
公众诠释优先{搞定;组; } ISerializable的的#区域实施 ///&LT;总结&gt;
///这是反序列化SortOption时调用的构造函数。
///&LT; /总结&gt;
保护SortOption(的SerializationInfo信息,的StreamingContext上下文)
{
IsAscending = info.GetBoolean(IsAscending);
优先= info.GetInt32(优先级); //我们只是在属性名坚持这一点。因此,让我们重新从拉姆达防爆pression。
属性= DynamicEx pression.ParseLambda&LT;的TModel,TProperty&GT;(info.GetString(「该物业」),默认(的TModel),默认(TProperty));
} ///&LT;总结&gt;
///填充一个&LT;见CREF =T:System.Runtime.Serialization.SerializationInfo/&GT;用序列化目标对象所需的数据。
///&LT; /总结&gt;
///&LT; PARAM NAME =信息&GT;的&lt;见CREF =T:System.Runtime.Serialization.SerializationInfo/&GT;来填充数据。 &LT; /参数&GT;
///&LT; PARAM NAME =背景&gt;在目的地(参见LT;见CREF =T:System.Runtime.Serialization.StreamingContext/&)对于此序列化。 &LT; /参数&GT;
公共无效GetObjectData使用(的SerializationInfo信息,的StreamingContext上下文)
{
//在那里只要坚持属性名称。我们将基于在另一端重建前pression。
info.AddValue(属性,Property.PropertyToString());
info.AddValue(IsAscending,IsAscending);
info.AddValue(优先级,优先);
} #endregion
}
下面的诀窍:这种形式的任何前pression ...
的obj =&GT; obj.A.B.C //等。
...实际上只是一堆嵌套的 MemberEx pression
的对象。
首先你得:
MemberEx pression:obj.A.B.C
防爆pression:// obj.A.B MemberEx pression
会员:C
评估防爆pression
上面的为 MemberEx pression
的为您提供:
MemberEx pression:obj.A.B
防爆pression:// obj.A MemberEx pression
会员:乙
最后,上面的是的(在顶),你有:
MemberEx pression:obj.A
防爆pression:OBJ //注意:不是MemberEx pression
会员:一
因此,似乎很清楚,要解决这个问题的方法是通过检查的在防爆pression
属性 MemberEx pression
,直到它不再是本身的点 MemberEx pression
。
更新:似乎有对你的问题的补充旋。这可能是因为你有一些拉姆达的看起来的像 Func键&LT; T,INT&GT;
...
P =&GT;页
...但的实际的一个 Func键&LT; T,对象&gt;
;在这种情况下,编译器将上述前pression转换为:
P =&GT;转换(p.Age)
调整为这个问题其实并不一样坚韧,因为它看起来。看看我的更新code为对付它的方法之一。请注意,通过抽象code为得到一个 MemberEx pression
掳到其自己的方法( TryFindMemberEx pression
),这种方法保持了 GetFullPropertyName
方法还算干净,并允许您在将来添加额外的检查 - 如果,也许,你会发现自己面临着的新的的,你没有原先占的场景 - 而不必通过太多code涉水
要说明:此code为我工作
// code调整为prevent水平溢出
静态字符串GetFullPropertyName&LT; T,TProperty&GT;
(防爆pression&LT;&Func键LT; T,TProperty&GT;&GT; EXP)
{
MemberEx pression memberExp;
如果(!TryFindMemberEx pression(exp.Body,出memberExp))
返回的String.Empty; VAR memberNames =新的堆栈&LT;串GT;();
做
{
memberNames.Push(memberExp.Member.Name);
}
而(TryFindMemberEx pression(memberExp.Ex pression,出memberExp)); 返回的string.join(,memberNames.ToArray()。);
}// code调整为prevent水平溢出
私人静态布尔TryFindMemberEx pression
(防爆pression EXP,出MemberEx pression memberExp)
{
memberExp = EXP作为MemberEx pression;
如果(memberExp!= NULL)
{
// heyo!这是很容易做到
返回true;
} //如果编译器创建的自动转换,
//它会是这个样子?
// OBJ =&GT;转换(obj.Property)[例如,INT - &GT;目的]
// 要么:
// OBJ =&GT; ConvertChecked(obj.Property)[例如,INT - &GT;长]
// ...这是在IsConversion检查情况
如果(IsConversion(实验)及&放大器; exp为UnaryEx pression)
{
memberExp =((UnaryEx pression)EXP).Operand作为MemberEx pression;
如果(memberExp!= NULL)
{
返回true;
}
} 返回false;
}私人静态布尔IsConversion(前pression EXP)
{
返回(
exp.NodeType ==防爆pressionType.Convert ||
exp.NodeType ==防爆pressionType.ConvertChecked
);
}
用法:
防爆pression&LT;&Func键LT;人,串GT;&GT; simpleExp = P =&GT; p.FirstName;
防爆pression&LT;&Func键LT;人,串GT;&GT; complexExp = P =&GT; p.Address.State.Abbreviation;
防爆pression&LT;&Func键LT;人,对象&gt;&GT; ageExp = P =&GT;页;Console.WriteLine(GetFullPropertyName(simpleExp));
Console.WriteLine(GetFullPropertyName(complexExp));
Console.WriteLine(GetFullPropertyName(ageExp));
输出:
姓
Address.State.Abbreviation
年龄
I use some strongly-typed expressions that get serialized to allow my UI code to have strongly-typed sorting and searching expressions. These are of type Expression<Func<TModel,TProperty>>
and are used as such: SortOption.Field = (p => p.FirstName);
. I've gotten this working perfectly for this simple case.
The code that I'm using for parsing the "FirstName" property out of there is actually reusing some existing functionality in a third-party product that we use and it works great, until we start working with deeply-nested properties(SortOption.Field = (p => p.Address.State.Abbreviation);
). This code has some very different assumptions in the need to support deeply-nested properties.
As for what this code does, I don't really understand it and rather than changing that code, I figured I should just write from scratch this functionality. However, I don't know of a good way to do this. I suspect we can do something better than doing a ToString() and performing string parsing. So what's a good way to do this to handle the trivial and deeply-nested cases?
Requirements:
- Given the expression
p => p.FirstName
I need a string of"FirstName"
. - Given the expression
p => p.Address.State.Abbreviation
I need a string of"Address.State.Abbreviation"
While it's not important for an answer to my question, I suspect my serialization/deserialization code could be useful to somebody else who finds this question in the future, so it is below. Again, this code is not important to the question - I just thought it might help somebody. Note that DynamicExpression.ParseLambda
comes from the Dynamic LINQ stuff and Property.PropertyToString()
is what this question is about.
/// <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
}
Here's the trick: any expression of this form...
obj => obj.A.B.C // etc.
...is really just a bunch of nested MemberExpression
objects.
First you've got:
MemberExpression: obj.A.B.C
Expression: obj.A.B // MemberExpression
Member: C
Evaluating Expression
above as a MemberExpression
gives you:
MemberExpression: obj.A.B
Expression: obj.A // MemberExpression
Member: B
Finally, above that (at the "top") you have:
MemberExpression: obj.A
Expression: obj // note: not a MemberExpression
Member: A
So it seems clear that the way to approach this problem is by checking the Expression
property of a MemberExpression
up until the point where it is no longer itself a MemberExpression
.
UPDATE: It seems there is an added spin on your problem. It may be that you have some lambda that looks like a Func<T, int>
...
p => p.Age
...but is actually a Func<T, object>
; in this case, the compiler will convert the above expression to:
p => Convert(p.Age)
Adjusting for this issue actually isn't as tough as it might seem. Take a look at my updated code for one way to deal with it. Notice that by abstracting the code for getting a MemberExpression
away into its own method (TryFindMemberExpression
), this approach keeps the GetFullPropertyName
method fairly clean and allows you to add additional checks in the future -- if, perhaps, you find yourself facing a new scenario which you hadn't originally accounted for -- without having to wade through too much code.
To illustrate: this code worked for me.
// 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
);
}
Usage:
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));
Output:
FirstName
Address.State.Abbreviation
Age
这篇关于获取的财产,作为字符串,从防爆pression&LT;&Func键LT;的TModel,TProperty&GT;&GT;的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!