IEnumerable <T> / IQueryable <T> ile Dinamik LINQ Siparişi


669

Ben bir örnek buldum VS2008 Örnekler sql benzeri dize (örneğin bir kullanmanızı sağlar Dinamik LINQ için OrderBy("Name, Age DESC"))sipariş için. Ne yazık ki, yöntem dahil üzerinde çalışan tek IQueryable<T>bu işlevselliği almak için herhangi bir yolu var mı. IEnumerable<T>?


1
Bu tarih itibariyle en iyi cevap bence: System.Linq.Dynamic.Core kütüphanesi.
Shahin Dohan

Yanıtlar:


905

Bu ihtiyarla karşılaştım ...

Bunu dinamik LINQ kitaplığı olmadan yapmak için aşağıdaki koda ihtiyacınız vardır. Bu, iç içe geçmiş özellikler de dahil olmak üzere en yaygın senaryoları kapsar.

Çalışmasını sağlamak IEnumerable<T>için bazı sarıcı yöntemleri ekleyebilirsiniz AsQueryable- ancak aşağıdaki kod Expressiongerekli temel mantıktır.

public static IOrderedQueryable<T> OrderBy<T>(
    this IQueryable<T> source, 
    string property)
{
    return ApplyOrder<T>(source, property, "OrderBy");
}

public static IOrderedQueryable<T> OrderByDescending<T>(
    this IQueryable<T> source, 
    string property)
{
    return ApplyOrder<T>(source, property, "OrderByDescending");
}

public static IOrderedQueryable<T> ThenBy<T>(
    this IOrderedQueryable<T> source, 
    string property)
{
    return ApplyOrder<T>(source, property, "ThenBy");
}

public static IOrderedQueryable<T> ThenByDescending<T>(
    this IOrderedQueryable<T> source, 
    string property)
{
    return ApplyOrder<T>(source, property, "ThenByDescending");
}

static IOrderedQueryable<T> ApplyOrder<T>(
    IQueryable<T> source, 
    string property, 
    string methodName) 
{
    string[] props = property.Split('.');
    Type type = typeof(T);
    ParameterExpression arg = Expression.Parameter(type, "x");
    Expression expr = arg;
    foreach(string prop in props) {
        // use reflection (not ComponentModel) to mirror LINQ
        PropertyInfo pi = type.GetProperty(prop);
        expr = Expression.Property(expr, pi);
        type = pi.PropertyType;
    }
    Type delegateType = typeof(Func<,>).MakeGenericType(typeof(T), type);
    LambdaExpression lambda = Expression.Lambda(delegateType, expr, arg);

    object result = typeof(Queryable).GetMethods().Single(
            method => method.Name == methodName
                    && method.IsGenericMethodDefinition
                    && method.GetGenericArguments().Length == 2
                    && method.GetParameters().Length == 2)
            .MakeGenericMethod(typeof(T), type)
            .Invoke(null, new object[] {source, lambda});
    return (IOrderedQueryable<T>)result;
}

Düzenleme: sadece LINQ-to-Objects (ORM'ler için ifade ağaçları gerçekten sorguları temsil edemez - desteklemiyor) için geçerli dynamicolmasına rağmen - ile karıştırmak istiyorsanız daha eğlenceli olur . Ama işte bunu LINQ-to-Objects ile yapmanın bir yolu. Seçiminin uygun kilitleme semantiğinden kaynaklandığına dikkat edin:dynamicdynamicMemberExpressionHashtable

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);
        }
    }
}

109
Gördüğüm en iyi kod parçası :)
Projemde

4
@Dave - başlamanız gerekiyor IQueryable<T>, bu yüzden List<T>(ki IEnumerable<T>) gibi bir şeye sahipseniz kullanmanız gerekebilir AsQueryable()- örneğinvar sorted = someList.AsQueryable().OrderBy("Foo.Bar");
Marc Gravell

7
Bunu gördünüz mü ... bazı insanlara yardımcı olabilir ... stackoverflow.com/questions/557819/… daha güçlü bir çözüm.
anthonyv

28
@MGOwen, kodun doğasını yanlış anlamış görünüyorsunuz. Projenizde herhangi bir yere koyduğunuz 40 satır olsun ya da bu satırlar harici bir kütüphaneye (önceden derlenmiş veya kaynak olarak) gelse de 40 satır aynıdır. Bu olurdu oldukça şaşırtıcı ben Aralık '11 beri var olan Nuget bir kütüphaneye Ekim '08 yılında, bağlantılı olsaydı (az değil Nuget ya o olmasaydı çünkü), ama "ne yaptığını" temel aynısı. Ayrıca, "gerçek çözüm" ifadesini, her kodlama sorusunda iyi tanımlanmış, anlaşılmış tek bir yol varmış gibi kullanırsınız: yoktur.
Marc Gravell

5
@MGOwen btw, harici lib 2296 kod satırıdır (AssemblyInfo.cs dahil değil); Buradaki 40 satır oldukça makul görünüyor
Marc Gravell

231

Herhangi bir komplikasyon olmadan çok kolay:

  1. using System.Linq.Dynamic;En üste ekleyin .
  2. kullanım vehicles = vehicles.AsQueryable().OrderBy("Make ASC, Year DESC").ToList();

11
ve nereden aldın System.Linq.Dynamic?
Dementic

1
MongoDB ile linq kullanırken de çalışır.
soupy1976

32
Kabul edilen cevap 2008'de doğru cevap olabilir, ancak şu anda bu en kolay, en doğru cevaptır.
EL MOJO

1
Bu gerçekten iyi ve basit kullanım, dahili olarak çok karmaşıklık, onu sevdi
Mrinal Kamboj 28:16

5
"Gelecekte" insanlar için, dotnet çekirdeği kullanıyorsanız, bunu kullanın: nuget.org/packages/System.Linq.Dynamic.Core
Rafael Merlin

78

Cevabı buldum. .AsQueryable<>()Listemi IQueryable'a dönüştürmek için uzantı yöntemini kullanabilirim , sonra dinamik siparişi karşı çalıştırabilirim.


52
Lütfen geri kalanlarımız için bir örnek veriniz.
MGOwen

54

Sadece bu soruya rastladım.

Marc'ın ApplyOrder uygulamasını yukarıdan kullanarak, SQL benzeri dizeleri işleyen bir Extension yöntemini birlikte tokatladım:

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

Ayrıntılar burada bulunabilir: http://aonnull.blogspot.com/2010/08/dynamic-sql-like-linq-orderby-extension.html


1
Harika şeyler, özellik adı büyük / küçük harf duyarsız yapmak için aşağıdaki gibi bir değişiklik ekleyin: PropertyInfo pi = type.GetProperty (prop, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
Mrinal Kamboj

43

Sanırım sıralamak istediğiniz özelliği almak için yansıma kullanmak için işe yarayacak:

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);
}

Yansıma kullanmanın mülke doğrudan erişmekten çok daha yavaş olduğunu, dolayısıyla performansın araştırılması gerektiğini unutmayın.


bu işe yarıyor mu? orderby bir değer istemiyor ama bir seçici lamba / delege (Func <TSource, TKey> keySelector) ..
Davy Landman

2
Bu örneği göndermeden önce denedim ve evet, işe yarıyor.
Kjetil Watnedal

3
+1 Bu tam da aradığım şey! Bu, basit sayfa sıralama sorunları için harika çalışır.
Andrew Siemer

Bu benim için işe yaramadı. Bir şey mi kaçırıyorum? "Bazı mülkler" ne olmalı. Özellik adı yanı sıra property.GetType () vermeye çalıştım. IQueryable <> var ve IEnumerable <>
SO Kullanıcı

2
@Alex Shkor: Elemanları tüm unsurlara bakmadan nasıl sıralamanız gerekiyor? Ancak, diğer cevaplarda daha iyi çözümler vardır.
Kjetil Watnedal

19

Sadece başkalarının söylediklerine dayanarak. Aşağıdakilerin oldukça iyi çalıştığını gördüm.

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;
}

12

Bu soruyu Linq çoklu sipariş cümleleri arayan tökezledim ve belki de bu yazarın aradığı şeydi

Bunu nasıl yapacağınız aşağıda açıklanmıştır:

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

5
+1, açıklama eksikliği nedeniyle oylamayı iptal etti. Ayrıca yazarın birden fazla siparişle ilgilenmiş olabileceğini düşünüyorum. Dinamik bile oldu anahtar kelime, aşağı-oylama için bir neden.
Jason Kleban

11

Bunu yapmaya çalışıyordum ama Kjetil Watnedal'ın çözümü ile ilgili problemler yaşıyorum çünkü satır içi linq sözdizimini kullanmıyorum - yöntem tarzı sözdizimini tercih ederim. Benim özel sorun bir özel kullanarak dinamik sıralama yapmaya çalışırken oldu IComparer.

Çözümüm şu şekilde sonuçlandı:

Böyle bir IQueryable sorgu verildi:

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

Ve çalışma zamanı sıralama alanı argümanı verildi:

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

Dinamik OrderBy şöyle görünür:

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

Ve GetReflectedPropertyValue () adlı küçük bir yardımcı yöntem kullanıyor:

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

Son bir şey - OrderBygelenekleri kullanmak ICompareristediğimi söyledim - çünkü Doğal sıralama yapmak istedim .

Bunu yapmak için, sadece değiştiriyorum OrderBy:

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

İçin kod için bu gönderiye bakın NaturalSortComparer().


5

Dinamik kullanın linq

sadece ekle using System.Linq.Dynamic;

Ve tüm sütunlarınızı sipariş etmek için şu şekilde kullanın:

string sortTypeStr = "ASC"; // or DESC
string SortColumnName = "Age"; // Your column name
query = query.OrderBy($"{SortColumnName} {sortTypeStr}");

4

Ekleyebilirsin:

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
}

GetPropertyValueFonksiyon dan Kjetil Watnedal cevabı

Sorun neden olabilir? Bu tür herhangi bir tür derleme zamanı yerine çalışma zamanında istisnalar atacaktır (D2VIANT'ın cevabı gibi).

Linq to Sql ile uğraşıyorsanız ve orderby bir ifade ağacı ise, yine de yürütme için SQL'e dönüştürülecektir.


GetPropertyValue mehotod tüm öğeler için yürütülecek, bu kötü bir çözüm.
Alex Shkor

2
OrderByönceki sipariş korumak yok !!
Amir Ismail

4

İşte ilginç bulduğum başka bir şey. Kaynağınız bir DataTable ise, Dinamik Linq kullanmadan dinamik sıralama kullanabilirsiniz

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;

başvuru: http://msdn.microsoft.com/tr-tr/library/bb669083.aspx (DataSetExtensions Kullanımı)

DataView'e dönüştürerek yapmanın bir yolu daha:

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

4

Maarten ( LINQ PropertyInfo nesnesini kullanarak bir koleksiyon sorgu ) sayesinde bu çözümü aldım:

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

Benim durumumda bir "ColumnHeaderMouseClick" (WindowsForm) üzerinde çalışıyordum, bu yüzden sadece belirli Sütunu basılı ve muhabiri PropertyInfo'yu buldum:

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

VEYA

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

(Adlarınız sütununun nesne Özellikleri ile eşleştiğinden emin olun)

Şerefe


4

Birçok arama yaptıktan sonra benim için çalıştı:

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);
}

4

IEnumerable'ı IQueryable'a dönüştürebilirsiniz.

items = items.AsQueryable().OrderBy("Name ASC");

3

Alternatif bir çözüm aşağıdaki sınıfı / arayüzü kullanır. Gerçekten dinamik değil, ama işe yarıyor.

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;
    }
}

2

Bu cevap @John Sheehan - Runscope tarafından sağlanan çözüm için örnek olması gereken yorumlara bir cevaptır

Lütfen geri kalanlarımız için bir örnek veriniz.

DAL'da (Veri Erişim Katmanı),

IEnumerable sürümü:

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

IQueryable sürümü

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

Artık bağlanmak için IQueryable sürümünü kullanabilirsiniz, örneğin Asp.net'teki GridView ve sıralama avantajı (IEnumerable sürümünü kullanarak sıralama yapamazsınız)

Dapper'i ORM olarak kullandım ve IQueryable sürümünü oluşturdum ve asp.net'te GridView'de sıralamayı çok kolay kullandım.


2

İlk Dinamik Araçları Kur -> NuGet Paket Yöneticisi -> Paket Yöneticisi Konsolu

install-package System.Linq.Dynamic

Ad Alanı Ekle using System.Linq.Dynamic;

Şimdi kullanabilirsiniz OrderBy("Name, Age DESC")


İç özellik sıralama ile nasıl kullanabilirim - OrderBy ("Branch.BranchName", "Descending") gibi
devC

Bu benim için çalışıyor. Belki de soru 10 yaşında olduğu için ve bu daha kolay yöntem sadece daha sonra geldi.
kosherjellyfish

1

Bunu kullanabilirsiniz:

        public List<Book> Books(string orderField, bool desc, int skip, int take)
{
    var propertyInfo = typeof(Book).GetProperty(orderField);

    return _context.Books
        .Where(...)
        .OrderBy(p => !desc ? propertyInfo.GetValue(p, null) : 0)
        .ThenByDescending(p => desc ? propertyInfo.GetValue(p, null) : 0)
        .Skip(skip)
        .Take(take)
        .ToList();
}

Birkaç yıl sonra bununla karşılaşıyorum; bu benim için çalıştı, bir rüya gibi. 1-3 mülk üzerinde dinamik sıralama var ve bu bir rüya gibi çalışır. Uygulaması kolay ve sorunsuz.
Bazïnga

0

Listeyi IEnumerable veya Iquerable'a dönüştürün, System.LINQ.Dynamic ad alanını kullanarak ekleyin, sonra u varsayılan olarak System.LINQ.Dynamic'den gelen OrderBy Yöntemine virgülle ayrılmış dizedeki özellik adlarından bahsedebiliriz.


-3
var result1 = lst.OrderBy(a=>a.Name);// for ascending order. 
 var result1 = lst.OrderByDescending(a=>a.Name);// for desc order. 
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.