从 Expression<Func<TModel,TProperty>> 以字符串形式获取属性. [英] Get the property, as a string, from an Expression<Func<TModel,TProperty>>
问题描述
我使用了一些序列化的强类型表达式,以允许我的 UI 代码具有强类型的排序和搜索表达式.它们属于 Expression
类型,并按如下方式使用:SortOption.Field = (p => p.FirstName);
.对于这个简单的案例,我已经完美地工作了.
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.
我用来解析FirstName"属性的代码实际上是重用了我们使用的第三方产品中的一些现有功能,并且效果很好,直到我们开始使用深度嵌套的属性(SortOption.Field = (p => p.Address.State.Abbreviation);
).这段代码在需要支持深度嵌套的属性方面有一些非常不同的假设.
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.
至于这段代码的作用,我并不真正理解它,与其更改该代码,我想我应该从头开始编写此功能.但是,我不知道有什么好的方法可以做到这一点.我怀疑我们可以做一些比做 ToString() 和执行字符串解析更好的事情.那么有什么好的方法可以处理这些琐碎的和深层嵌套的案例呢?
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?
要求:
- 给定表达式
p =>p.FirstName
我需要一串"FirstName"
. - 给定表达式
p =>p.Address.State.Abbreviation
我需要一串"Address.State.Abbreviation"
- 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"
虽然回答我的问题并不重要,但我怀疑我的序列化/反序列化代码可能对将来发现这个问题的其他人有用,所以它在下面.同样,这段代码对问题并不重要 - 我只是认为它可能对某人有所帮助.请注意,DynamicExpression.ParseLambda
来自 Dynamic LINQ 东西和 Property.PropertyToString()
就是这个问题的内容.
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.
...实际上只是一堆嵌套的 MemberExpression
对象.
...is really just a bunch of nested MemberExpression
objects.
首先你有:
MemberExpression: obj.A.B.C
Expression: obj.A.B // MemberExpression
Member: C
评估上面的 Expression
作为 MemberExpression
给你:
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
所以很明显,解决这个问题的方法是检查 MemberExpression
的 Expression
属性,直到它本身不再是 会员表达
.
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
.
更新:您的问题似乎有一个额外的问题.可能您有一些 看起来 像 Func
...
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
...但实际上是一个Func
;在这种情况下,编译器会将上述表达式转换为:
...but is actually a Func<T, object>
; in this case, the compiler will convert the above expression to:
p => Convert(p.Age)
针对这个问题进行调整实际上并不像看起来那么困难.看看我更新的代码,了解一种处理方法.请注意,通过将用于获取 MemberExpression
的代码抽象为它自己的方法 (TryFindMemberExpression
),这种方法使 GetFullPropertyName
方法保持相当干净,并允许您可以在未来添加额外的检查——如果,也许,你发现自己面临着一个你最初没有考虑过的新场景——而不必费力地研究太多代码.
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
);
}
用法:
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
这篇关于从 Expression<Func<TModel,TProperty>> 以字符串形式获取属性.的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!