2013-07-21 124 views
1

我有一組擴展方法,允許在LINQ OrderBy()方法中使用魔術字符串。我知道第一個問題是爲什麼,但它是通用存儲庫的一部分,並且存在靈活性,以便字符串可以從UI發送並直接使用。C#將魔術字符串轉換爲lambda表達式

如果你傳遞一個表示你正在查詢的主要實體的屬性的魔術字符串,但它有效,但我很難使它更通用,因此它可以處理多層深度魔法字符串。例如:

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然後一個屬性,但我掙扎。任何幫助或指向正確的方向將不勝感激。

+1

這適用於子屬性,IIRC:http://stackoverflow.com/a/233505/23354 - 看'property.Split( '');'和隨後的環 –

+0

這完美。這絕對是答案,所以我提出了評論,因爲我不能將其標記爲答案。謝謝。 –

回答

2

我寫了自己的謂詞生成器類型的東西。我試圖修改這裏發佈的代碼。這會返回一個表達式來訪問一個屬性,並且可以用來構建更復雜的表達式 - 只需確保表達式的所有組件都使用完全相同的param對象實例。

這不會起到代碼的作用。它將需要一些輕微的適應性,以使其適用於我的想法。

此輸出param => (param.Child.IntProperty == 42)

您可以在where子句中使用predicate變量。假設你有一個叫做parentsList<Parent>,你可以撥打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; 
    } 
} 
相關問題