2015-08-12 7 views
6

Chcę, aby wygenerować wyrażenie przez parametr ciąg, niektóre kod jak:generować EF orderby wyrażenie przez ciąg

private Expression<Func<Task, T>> Generate(string orderby) 
    { 
     switch (orderby) 
     { 
      case "Time": 
       return t => t.Time; 
      case "Money": 
       return t => t.RewardMoney; 
      default: 
       return t => t.Id; 
     } 
    } 

następnie zadzwonić:

_context.Items.OrderBy(Generate("Money")); 

ale nie można skompilować! Zmieniam T na obiekt.

private Expression<Func<Task, object>> Generate(string orderby) 

Następnie może się skompilować, ale nie działa.

System.NotSupportedException: Nie można rzutować typu "System.Int32" na typ "System.Object". LINQ to Entities obsługuje tylko prymitywne lub wyliczeniowe typy EDM do rzutowania.

+0

Możliwy duplikat [Jak określić dynamicznie argument LinB OrderBy?] (Http://stackoverflow.com/questions/7265186/how-do-i-specify-the-linq-orderby-argument-dynamicznie) –

Odpowiedz

17

Korzystanie i można podać parametry, a następnie zadzwonić OrderBy funkcję, zamiast wracać Expression<Func<Task, T>> a następnie wywołanie OrderBy.

Należy pamiętać, że OrderBy jest metodą rozszerzenia i zaimplementowano w klasach System.Linq.Enumarable i System.Linq.Queryable. Pierwszy z nich to , a drugi dotyczy . potrzebuje drzewa wyrażeń zapytania, aby przetłumaczyć je na polecenia SQL. Dlatego używamy implementacji Queryable.

Można to zrobić za pomocą metody wydłużania (objaśnienia dodanych komentarzach):

public static IOrderedQueryable<TSource> OrderBy<TSource>(
     this IEnumerable<TSource> query, string propertyName) 
{ 
    var entityType = typeof(TSource); 

    //Create x=>x.PropName 
    var propertyInfo = entityType.GetProperty(propertyName); 
    ParameterExpression arg = Expression.Parameter(entityType, "x"); 
    MemberExpression property = Expression.Property(arg, propertyName); 
    var selector = Expression.Lambda(property, new ParameterExpression[] { arg }); 

    //Get System.Linq.Queryable.OrderBy() method. 
    var enumarableType = typeof(System.Linq.Queryable); 
    var method = enumarableType.GetMethods() 
     .Where(m => m.Name == "OrderBy" && m.IsGenericMethodDefinition) 
     .Where(m => 
     { 
      var parameters = m.GetParameters().ToList(); 
      //Put more restriction here to ensure selecting the right overload     
      return parameters.Count == 2;//overload that has 2 parameters 
     }).Single(); 
    //The linq's OrderBy<TSource, TKey> has two generic types, which provided here 
    MethodInfo genericMethod = method 
     .MakeGenericMethod(entityType, propertyInfo.PropertyType); 

    /*Call query.OrderBy(selector), with query and selector: x=> x.PropName 
     Note that we pass the selector as Expression to the method and we don't compile it. 
     By doing so EF can extract "order by" columns and generate SQL for it.*/ 
    var newQuery = (IOrderedQueryable<TSource>)genericMethod 
     .Invoke(genericMethod, new object[] { query, selector }); 
    return newQuery; 
} 

Teraz można nazwać to przeciążenie OrderBy jak każdy inny przeciążenie niego.
Na przykład:

var cheapestItems = _context.Items.OrderBy("Money").Take(10).ToList(); 

co przekłada się na:

SELECT TOP (10) {coulmn names} FROM [dbo].[Items] AS [Extent1] 
     ORDER BY [Extent1].[Money] ASC 

Takie podejście może być wykorzystane do określenia wszystkich przeciążeniem OrderBy i OrderByDescending metod mieć string selektor nieruchomości.

+3

Doskonałe przedłużenie. Chciałbym zauważyć, że pierwszym parametrem powinien być "IQueryable ", a nie "IEnumerable ".Jeśli OP lub dowolny inny czytnik potrzebuje więcej, istnieje cała biblioteka metod Linq opartych na stringach dostępnych w NuGet (wyszukaj System.Linq.Dynamic) lub tutaj: https://msdn.microsoft.com/en-us/ vstudio/bb894665.aspx – KeithS

2

Można spróbować konwersji metody Generate w ogólny sposób:

private Expression<Func<Task, TResult>> Generate<TResult>(string orderby) 
{ 
    switch (orderby) 
    { 
     case "Time": 
      return t => t.Time; 
     case "Money": 
      return t => t.RewardMoney; 
     default: 
     return t => t.Id; 
    } 
} 

Tak więc, jeśli wywołanie tej metody, należy określić rodzaj obiektu, który chcesz zamówić przez:

_context.Items.OrderBy(Generate<decimal>("Money")); 

Teraz należy pamiętać, że TResult może być tylko typem pierwotnym lub typem wyliczeniowym.

+1

dziękuję za odpowiedź, ale gdy przekażesz parametr "Czas", połączenie zmieni się: _context.Items.OrderBy (Wygeneruj ("Czas")). Ta funkcja traci swoją funkcję? – yubaolee