2008-09-03 2 views
598

Я нашел пример в VS2008 Examples для динамического LINQ, что позволяет использовать SQL-как строка (например, OrderBy("Name, Age DESC")) для заказа К сожалению, метод включенного работает только на IQueryable<T>,.. Есть ли способ чтобы получить эту функцию на IEnumerable<T>Dynamic LINQ OrderBy на IEnumerable <T>

ответ

824

Просто наткнулся на этот Олди ...

Чтобы сделать это без динамической библиотеки LINQ, вам просто нужен код, как показано ниже. Это охватывает наиболее распространенные сценарии, включая вложенные свойства.

Чтобы получить его работу с IEnumerable<T>, вы можете добавить некоторые методы-обертки, которые идут через AsQueryable, - но код ниже - это ядро ​​Expression.


Edit: он получает больше удовольствия, если вы хотите смешать это с dynamic - хотя заметим, что dynamic относится только к LINQ-to-Objects (выражение деревьев для ORMs и т.д., не может действительно представлять dynamic запросы - MemberExpression не поддерживает его). Но вот способ сделать это с помощью LINQ-to-Objects. Обратите внимание, что выбор Hashtable обусловлен благоприятной запирающей семантику:

using Microsoft.CSharp.RuntimeBinder; 
using System; 
using System.Collections; 
using System.Collections.Generic; 
using System.Dynamic; 
using System.Linq; 
using System.Runtime.CompilerServices; 
static class Program 
{ 
    private static class AccessorCache 
    { 
     private static readonly Hashtable accessors = new Hashtable(); 

     private static readonly Hashtable callSites = new Hashtable(); 

     private static CallSite<Func<CallSite, object, object>> GetCallSiteLocked(
      string name) 
     { 
      var callSite = (CallSite<Func<CallSite, object, object>>)callSites[name]; 
      if(callSite == null) 
      { 
       callSites[name] = callSite = CallSite<Func<CallSite, object, object>> 
        .Create(Binder.GetMember(
           CSharpBinderFlags.None, 
           name, 
           typeof(AccessorCache), 
           new CSharpArgumentInfo[] { 
            CSharpArgumentInfo.Create(
             CSharpArgumentInfoFlags.None, 
             null) 
           })); 
      } 
      return callSite; 
     } 

     internal static Func<dynamic,object> GetAccessor(string name) 
     { 
      Func<dynamic, object> accessor = (Func<dynamic, object>)accessors[name]; 
      if (accessor == null) 
      { 
       lock (accessors) 
       { 
        accessor = (Func<dynamic, object>)accessors[name]; 
        if (accessor == null) 
        { 
         if(name.IndexOf('.') >= 0) { 
          string[] props = name.Split('.'); 
          CallSite<Func<CallSite, object, object>>[] arr 
           = Array.ConvertAll(props, GetCallSiteLocked); 
          accessor = target => 
          { 
           object val = (object)target; 
           for (int i = 0; i < arr.Length; i++) 
           { 
            var cs = arr[i]; 
            val = cs.Target(cs, val); 
           } 
           return val; 
          }; 
         } else { 
          var callSite = GetCallSiteLocked(name); 
          accessor = target => 
          { 
           return callSite.Target(callSite, (object)target); 
          }; 
         } 
         accessors[name] = accessor; 
        } 
       } 
      } 
      return accessor; 
     } 
    } 

    public static IOrderedEnumerable<dynamic> OrderBy(
     this IEnumerable<dynamic> source, 
     string property) 
    { 
     return Enumerable.OrderBy<dynamic, object>(
      source, 
      AccessorCache.GetAccessor(property), 
      Comparer<object>.Default); 
    } 

    public static IOrderedEnumerable<dynamic> OrderByDescending(
     this IEnumerable<dynamic> source, 
     string property) 
    { 
     return Enumerable.OrderByDescending<dynamic, object>(
      source, 
      AccessorCache.GetAccessor(property), 
      Comparer<object>.Default); 
    } 

    public static IOrderedEnumerable<dynamic> ThenBy(
     this IOrderedEnumerable<dynamic> source, 
     string property) 
    { 
     return Enumerable.ThenBy<dynamic, object>(
      source, 
      AccessorCache.GetAccessor(property), 
      Comparer<object>.Default); 
    } 

    public static IOrderedEnumerable<dynamic> ThenByDescending(
     this IOrderedEnumerable<dynamic> source, 
     string property) 
    { 
     return Enumerable.ThenByDescending<dynamic, object>(
      source, 
      AccessorCache.GetAccessor(property), 
      Comparer<object>.Default); 
    } 

    static void Main() 
    { 
     dynamic a = new ExpandoObject(), 
       b = new ExpandoObject(), 
       c = new ExpandoObject(); 
     a.X = "abc"; 
     b.X = "ghi"; 
     c.X = "def"; 
     dynamic[] data = new[] { 
      new { Y = a }, 
      new { Y = b }, 
      new { Y = c } 
     }; 

     var ordered = data.OrderByDescending("Y.X").ToArray(); 
     foreach (var obj in ordered) 
     { 
      Console.WriteLine(obj.Y.X); 
     } 
    } 
} 
+94

Лучший проклятый фрагмент кода, который я видел :) Просто решил миллион проблем в моем проекте :) – sajidnizami 2008-11-20 09:37:25

+0

Марк, у вас есть аналогичное расширение для LIKE Condition? – Prasad 2009-11-02 03:30:52

+0

@Prasad - `LIKE` немного отличается, но какой конец? Для LINQ-to-SQL существует `SqlMethods.Like`: http://msdn.microsoft.com/en-us/library/system.data.linq.sqlclient.sqlmethods.like.aspx; можете ли вы добавить больше информации о том, что ищете? – 2009-11-02 05:20:04

39

Я предполагаю, что это будет работать, чтобы использовать отражение, чтобы получить то, что собственность, которую вы хотите отсортировать по:

IEnumerable<T> myEnumerables 
var query=from enumerable in myenumerables 
      where some criteria 
      orderby GetPropertyValue(enumerable,"SomeProperty") 
      select enumerable 

private static object GetPropertyValue(object obj, string property) 
{ 
    System.Reflection.PropertyInfo propertyInfo=obj.GetType().GetProperty(property); 
    return propertyInfo.GetValue(obj, null); 
} 

Обратите внимание, что с помощью отражения значительно медленнее, чем доступ к собственности непосредственно, так производительность d должны быть исследованы.

+0

делает это даже Работа?orderby не хочет значения, а selector lamba/delegate (Func keySelector). – 2008-10-24 12:58:45

+1

Я попробовал этот пример, прежде чем публиковать его, и да, он действительно работает. – 2008-10-28 07:22:16

4

Вы можете добавить его:

public static IEnumerable<T> OrderBy(this IEnumerable<T> input, string queryString) { 
    //parse the string into property names 
    //Use reflection to get and sort by properties 
    //something like 

    foreach(string propname in queryString.Split(',')) 
     input.OrderBy(x => GetPropertyValue(x, propname)); 

    // I used Kjetil Watnedal's reflection example 
} 

GetPropertyValue функция от Kjetil Watnedal's answer

вопрос будет, почему? Любой такой тип генерирует исключения во время выполнения, а не время компиляции (например, ответ D2VIANT).

Если вы имеете дело с Linq to Sql, а orderby - дерево выражений, оно будет преобразовано в SQL для выполнения в любом случае.

76

Я нашел ответ. Я могу использовать метод расширения .AsQueryable<>() для преобразования моего списка в IQueryable, а затем запустить динамический порядок против него.

11

Я наткнуться на этот вопрос ищет Linq множественного OrderBy положения и, может быть, это было то, что автор искал

Вот как сделайте это:

var query = pets.OrderBy(pet => pet.Name).ThenByDescending(pet => pet.Age);  
2

Альтернативное решение использует следующий класс/интерфейс. Это не очень динамично, но это работает.

public interface IID 
{ 
    int ID 
    { 
     get; set; 
    } 
} 

public static class Utils 
{ 
    public static int GetID<T>(ObjectQuery<T> items) where T:EntityObject, IID 
    { 
     if (items.Count() == 0) return 1; 
     return items.OrderByDescending(u => u.ID).FirstOrDefault().ID + 1; 
    } 
} 
18

Просто строится на том, что говорили другие. Я обнаружил, что следующее работает достаточно хорошо.

public static IEnumerable<T> OrderBy<T>(this IEnumerable<T> input, string queryString) 
{ 
    if (string.IsNullOrEmpty(queryString)) 
     return input; 

    int i = 0; 
    foreach (string propname in queryString.Split(',')) 
    { 
     var subContent = propname.Split('|'); 
     if (Convert.ToInt32(subContent[1].Trim()) == 0) 
     { 
      if (i == 0) 
       input = input.OrderBy(x => GetPropertyValue(x, subContent[0].Trim())); 
      else 
       input = ((IOrderedEnumerable<T>)input).ThenBy(x => GetPropertyValue(x, subContent[0].Trim())); 
     } 
     else 
     { 
      if (i == 0) 
       input = input.OrderByDescending(x => GetPropertyValue(x, subContent[0].Trim())); 
      else 
       input = ((IOrderedEnumerable<T>)input).ThenByDescending(x => GetPropertyValue(x, subContent[0].Trim())); 
     } 
     i++; 
    } 

    return input; 
} 
9

Я пытался это сделать, но возникают проблемы с Kjetil Watnedal's solution, потому что я не использую синтаксис Linq инлайн - Я предпочитаю синтаксис метода стиле. Моя особая проблема заключалась в попытке выполнить динамическую сортировку с использованием настраиваемого IComparer.

Мое решение в конечном итоге, как это:

Учитывая IQueryable запрос следующим образом:

List<DATA__Security__Team> teams = TeamManager.GetTeams(); 
var query = teams.Where(team => team.ID < 10).AsQueryable(); 

И дал время выполнения сортировки поля аргумент:

string SortField; // Set at run-time to "Name" 

Динамические OrderBy внешность например:

query = query.OrderBy(item => item.GetReflectedPropertyValue(SortField)); 

И это с помощью небольшой вспомогательный метод под названием GetReflectedPropertyValue():

public static string GetReflectedPropertyValue(this object subject, string field) 
{ 
    object reflectedValue = subject.GetType().GetProperty(field).GetValue(subject, null); 
    return reflectedValue != null ? reflectedValue.ToString() : ""; 
} 

Одна последняя вещь - я упомянул, что хотел OrderBy использовать пользовательские IComparer - потому что я хотел сделать Natural sorting.

Чтобы сделать это, я просто изменяют OrderBy к:

query = query.OrderBy(item => item.GetReflectedPropertyValue(SortField), new NaturalSortComparer<string>()); 

См this post для кода для NaturalSortComparer().

4

Вот что-то еще, что я нашел интересным. Если источник является DataTable, вы можете использовать динамическую сортировку без использования динамического Linq

DataTable orders = dataSet.Tables["SalesOrderHeader"]; 
EnumerableRowCollection<DataRow> query = from order in orders.AsEnumerable() 
             orderby order.Field<DateTime>("OrderDate") 
             select order; 
DataView view = query.AsDataView(); 
bindingSource1.DataSource = view; 

ссылка: http://msdn.microsoft.com/en-us/library/bb669083.aspx (Использование DataSetExtensions)

Вот еще один способ сделать это путем преобразования его в DataView:

DataTable contacts = dataSet.Tables["Contact"];  
DataView view = contacts.AsDataView();  
view.Sort = "LastName desc, FirstName asc";  
bindingSource1.DataSource = view; 
dataGridView1.AutoResizeColumns(); 
50

Просто наткнулся на этот вопрос.

Использование реализации ApplyOrder Марка, сверху, я ударил вместе метод расширения, который обрабатывает SQL-подобные строки, как:

list.OrderBy("MyProperty DESC, MyOtherProperty ASC"); 

Подробности можно найти здесь: http://aonnull.blogspot.com/2010/08/dynamic-sql-like-linq-orderby-extension.html

189

Слишком легко, без каких-либо осложнений:

  1. Добавить using System.Linq.Dynamic; наверху.
  2. Используйте vehicles = vehicles.AsQueryable().OrderBy("Make ASC, Year DESC").ToList();
4

Благодаря Мартене (Query a collection using PropertyInfo object in LINQ) Я получил это решение:

myList.OrderByDescending(x => myPropertyInfo.GetValue(x, null)).ToList(); 

В моем случае я работал над «ColumnHeaderMouseClick» (WindowsForm) так что просто нашел конкретного столбца нажимается и его корреспондент PropertyInfo:

foreach (PropertyInfo column in (new Process()).GetType().GetProperties()) 
{ 
    if (column.Name == dgvProcessList.Columns[e.ColumnIndex].Name) 
    {} 
} 

ИЛИ

PropertyInfo column = (new Process()).GetType().GetProperties().Where(x => x.Name == dgvProcessList.Columns[e.ColumnIndex].Name).First(); 

(обязательно иметь имена столбцов, соответствующих свойств объекта)

Приветствия

4

После долгих поисков это работает для меня:

public static IEnumerable<TEntity> OrderBy<TEntity>(this IEnumerable<TEntity> source, 
                string orderByProperty, bool desc) 
{ 
    string command = desc ? "OrderByDescending" : "OrderBy"; 
    var type = typeof(TEntity); 
    var property = type.GetProperty(orderByProperty); 
    var parameter = Expression.Parameter(type, "p"); 
    var propertyAccess = Expression.MakeMemberAccess(parameter, property); 
    var orderByExpression = Expression.Lambda(propertyAccess, parameter); 
    var resultExpression = Expression.Call(typeof(Queryable), command, 
              new[] { type, property.PropertyType }, 
              source.AsQueryable().Expression, 
              Expression.Quote(orderByExpression)); 
    return source.AsQueryable().Provider.CreateQuery<TEntity>(resultExpression); 
} 
0

Список Преобразовать в IEnumerable или Iquerable , добавьте using System.LINQ.Dynamic namespace, тогда вы можете указать имена свойств в разделенной запятой строке на OrderBy Method, который по умолчанию запускается из System.LINQ.Dynamic.

4

Вы можете преобразовать IEnumerable в IQueryable.

items = items.AsQueryable().OrderBy("Name ASC"); 
-2
var result1 = lst.OrderBy(a=>a.Name);// for ascending order. 
var result1 = lst.OrderByDescending(a=>a.Name);// for desc order. 
0

Этот ответ является ответом на замечания, которые должны служить примером для решения представленной @John Sheehan - Runscope

Пожалуйста, приведите пример для остальных.

в DAL (уровень доступа к данным),

IEnumerable версия:

public IEnumerable<Order> GetOrders() 
    { 
     // i use Dapper to return IEnumerable<T> using Query<T> 
     //.. do stuff 
     return orders // IEnumerable<Order> 
    } 

IQueryable версия

public IQueryable<Order> GetOrdersAsQuerable() 
    { 
     IEnumerable<Order> qry= GetOrders(); 
     //use the built-in extension method AsQueryable in System.Linq namespace 
     return qry.AsQueryable();    
    } 

Теперь вы можете использовать IQueryable версия для связывания, например, Grid Просмотр в Asp.net и удобство для сортировки (вы не можете сортировать с использованием версии IEnumerable)

Я использовал Dapper как ORM и создал версию IQueryable и использовал сортировку в GridView в asp.net так просто.

0

первой установки динамических Tools -> NuGet Package Manager -> Package Manager Console

install-package System.Linq.Dynamic 

Добавить пространство именusing System.Linq.Dynamic;

Теперь вы можете использовать OrderBy("Name, Age DESC")

 Смежные вопросы

  • Нет связанных вопросов^_^