我使用一些经过序列化的强类型表达式,以允许我的UI代码具有强类型的排序和搜索表达式。这些类型为Expression<Func<TModel,TProperty>>,并按以下方式使用:SortOption.Field = (p => p.FirstName);。对于这个简单的案例,我已经做到了这一点。

我用来解析“FirstName”属性的代码实际上是在重用我们使用的第三方产品中的某些现有功能,并且效果很好,直到我们开始使用深度嵌套的属性(SortOption.Field = (p => p.Address.State.Abbreviation);)为止。对于支持深度嵌套的属性,此代码有一些非常不同的假设。

至于这段代码的作用,我并不太了解,而不是更改该代码,我认为我应该从头开始编写此功能。但是,我不知道这样做的好方法。我怀疑我们可以做一些比做ToString()和执行字符串解析更好的事情。那么,这样做是什么好方法来处理琐碎而深层的案件呢?

要求:

  • 给定表达式p => p.FirstName,我需要一个字符串"FirstName"
  • 给定表达式p => p.Address.State.Abbreviation,我需要一个字符串"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
    }
    

    最佳答案

    诀窍是:这种形式的任何表达方式...

    obj => obj.A.B.C // etc.
    

    ...实际上只是一堆嵌套的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
    

    因此,似乎很明显,解决此问题的方法是通过检查ExpressionMemberExpression属性,直到不再是MemberExpression为止。

    更新:您的问题似乎有一个额外的旋转。可能您有一些看起来像Func<T, int>的lambda ...
    p => p.Age
    

    ...但实际上是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
    

    09-27 17:41