【问题标题】:C# Turning magic string into lambda expressionC# 将魔术字符串转换为 lambda 表达式
【发布时间】:2013-07-21 07:37:22
【问题描述】:

我有一组扩展方法,允许在 LINQ OrderBy() 方法中使用魔术字符串。我知道第一个问题是为什么,但它是通用存储库的一部分,并且具有灵活性,因此可以从 UI 发送字符串并直接使用。

如果您传入一个代表您正在查询的主要实体上的属性的魔术字符串,我就可以正常工作,但是我无法使其更通用,以便它可以处理多个级别的深层魔术字符串。例如:

IQueryable<Contact> contacts = GetContacts();

contacts.OrderByProperty("Name"); // works great

// can't figure out how to handle this
contacts.OrderByProperty("ContactType.Name");

这是我目前的代码:

public static class LinqHelpers
{
    private static readonly MethodInfo OrderByMethod = typeof(Queryable).GetMethods().Single(method => method.Name == "OrderBy" && method.GetParameters().Length == 2);
    private static readonly MethodInfo OrderByDescendingMethod = typeof(Queryable).GetMethods().Single(method => method.Name == "OrderByDescending" && method.GetParameters().Length == 2);
    private static readonly MethodInfo ThenByMethod = typeof(Queryable).GetMethods().Single(method => method.Name == "ThenBy" && method.GetParameters().Length == 2);
    private static readonly MethodInfo ThenByDescendingMethod = typeof(Queryable).GetMethods().Single(method => method.Name == "ThenByDescending" && method.GetParameters().Length == 2);

    public static IOrderedQueryable<TSource> ApplyOrdering<TSource>(IQueryable<TSource> source, string propertyName, MethodInfo orderingMethod)
    {
        var parameter = Expression.Parameter(typeof(TSource), "x");
        var orderByProperty = Expression.Property(parameter, propertyName);

        var lambda = Expression.Lambda(orderByProperty, new[] { parameter });

        var genericMethod = orderingMethod.MakeGenericMethod(new[] { typeof(TSource), orderByProperty.Type });

        return (IOrderedQueryable<TSource>)genericMethod.Invoke(null, new object[] { source, lambda });
    }

    public static IOrderedQueryable<TSource> OrderByProperty<TSource>(this IQueryable<TSource> source, string propertyName)
    {
        return ApplyOrdering(source, propertyName, OrderByMethod);
    }

    public static IOrderedQueryable<TSource> OrderByDescendingProperty<TSource>(this IQueryable<TSource> source, string propertyName)
    {
        return ApplyOrdering(source, propertyName, OrderByDescendingMethod);
    }

    public static IOrderedQueryable<TSource> ThenByProperty<TSource>(this IOrderedQueryable<TSource> source, string propertyName)
    {
        return ApplyOrdering(source, propertyName, ThenByMethod);
    }

    public static IOrderedQueryable<TSource> ThenByDescendingProperty<TSource>(this IOrderedQueryable<TSource> source, string propertyName)
    {
        return ApplyOrdering(source, propertyName, ThenByDescendingMethod);
    }
}

我很确定我需要在期间拆分propertyName,然后使用这些部分来构建一个更复杂的表达式,其中涉及一个MemberExpression,然后是一个属性,但我很挣扎。任何帮助或指出正确的方向将不胜感激。

【问题讨论】:

  • 这适用于子属性,iirc:stackoverflow.com/a/233505/23354 - 请参阅property.Split('.'); 和后续循环
  • 效果很好。这绝对是答案,所以我赞成评论,因为我无法将其标记为答案。谢谢。

标签: c# linq lambda expression


【解决方案1】:

不久前我写了我自己的谓词构建器类型的东西。我试图调整代码以在此处发布。这会返回一个表达式来访问属性,并可用于构建更复杂的表达式 - 只需确保表达式的所有组件都使用完全相同的 param 对象实例。

这不适用于您的代码。我认为它需要一些细微的调整才能使其适合您的使用。

这会输出param =&gt; (param.Child.IntProperty == 42)

您可以在 where 子句中使用 predicate 变量。假设您有一个名为 parentsList&lt;Parent&gt;,您可以调用 parents.Where(predicate)

public class Parent {
    public string StringProperty { get; set; }

    public Child Child { get; set; }
}

public class Child {
    public int IntProperty { get; set; }
}

internal class Program {

    private static void Main(string[] args) {
        var param = Expression.Parameter(typeof(Parent), "param");
        var accessExpression = GetAccessExpression(param, "Child.IntProperty", typeof(Parent));
        var constantExpression = Expression.Constant(42);
        var condition = Expression.Equal(accessExpression, constantExpression);
        var predicate = Expression.Lambda<Func<Parent, bool>>(condition, param);

        Console.WriteLine(predicate.ToString());
    }

    /// <summary>
    /// Returns an Expression that represents member access for the specified property on the specified type. Uses recursion
    /// to find the full expression.
    /// </summary>
    /// <param name="property">The property path.</param>
    /// <param name="type">The type that contains the first part of the property path.</param>
    /// <returns></returns>
    private static Expression GetAccessExpression(Expression param, string property, Type type) {
        if (property == null)
            throw new ArgumentNullException("property");
        if (type == null)
            throw new ArgumentNullException("type");

        string[] propPath = property.Split('.');
        var propInfo = type.GetProperty(propPath[0]);

        if (propInfo == null)
            throw new Exception(String.Format("Could not find property '{0}' on type {1}.", propPath[0], type.FullName));

        var propAccess = Expression.MakeMemberAccess(param, type.GetProperty(propPath[0]));

        if (propPath.Length > 1)
            return GetAccessExpression(propAccess, string.Join(".", propPath, 1, propPath.Length - 1), type.GetProperty(propPath[0]).PropertyType);
        else
            return propAccess;
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-17
    • 2010-12-12
    • 2012-09-14
    • 1970-01-01
    相关资源
    最近更新 更多