Dynamic LINQ OrderDengan IEnumerable <T> / IQueryable <T>

670

Saya menemukan contoh dalam VS2008 Contoh untuk Dynamic LINQ yang memungkinkan Anda untuk menggunakan string seperti sql (misalnya OrderBy("Name, Age DESC"))untuk pemesanan. Sayangnya, metode yang disertakan hanya berfungsi IQueryable<T>. Apakah ada cara untuk mengaktifkan fungsi ini IEnumerable<T>?

John Sheehan
sumber
1
Jawaban terbaik pada tanggal ini, menurut pendapat saya: System.Linq.Dynamic.Core library.
Shahin Dohan

Jawaban:

905

Baru saja tersandung ke dalam ...

Untuk melakukan ini tanpa pustaka LINQ dinamis, Anda hanya perlu kode seperti di bawah ini. Ini mencakup sebagian besar skenario umum termasuk properti bersarang.

Untuk membuatnya bekerja dengan IEnumerable<T>Anda bisa menambahkan beberapa metode pembungkus yang masuk AsQueryable- tetapi kode di bawah ini adalah Expressionlogika inti yang dibutuhkan.

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

Sunting: semakin menyenangkan jika Anda ingin mencampurnya dengan dynamic- meskipun catatan yang dynamichanya berlaku untuk LINQ-to-Objects (pohon ekspresi untuk ORMs dll tidak dapat benar-benar mewakili dynamickueri - MemberExpressiontidak mendukungnya). Tapi inilah cara untuk melakukannya dengan LINQ-to-Objects. Perhatikan bahwa pilihan Hashtableini karena semantik penguncian yang menguntungkan:

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);
        }
    }
}
Marc Gravell
sumber
109
Sepotong kode terbaik yang pernah saya lihat :) Baru saja memecahkan sejuta masalah dalam proyek saya :)
sajidnizami
4
@Dave - Anda harus mulai dengan IQueryable<T>, jadi jika Anda memiliki sesuatu seperti List<T>(yang IEnumerable<T>) Anda mungkin perlu menggunakan AsQueryable()- misalnyavar sorted = someList.AsQueryable().OrderBy("Foo.Bar");
Marc Gravell
7
Pernahkah Anda melihat ini ... ini mungkin membantu beberapa orang ... stackoverflow.com/questions/557819/ ... ini merupakan solusi yang diketik dengan lebih kuat.
anthonyv
28
@ MOGOwen Anda sepertinya salah paham tentang sifat kode. 40 baris itu sama tidak peduli apakah itu 40 baris yang Anda tempatkan di proyek Anda, atau jika baris tersebut datang (pra-kompilasi, atau sebagai sumber) di perpustakaan eksternal. Akan sangat menakjubkan jika saya menautkan, pada Oktober '08 ke perpustakaan di nuget yang telah ada sejak Desember '11 (paling tidak karena nuget juga tidak ada), tetapi dasar "apa yang dilakukannya" adalah sama. Juga, Anda menggunakan frasa "solusi aktual" seolah-olah ada beberapa rute tunggal yang disepakati dengan baik untuk setiap pertanyaan pengkodean: tidak ada.
Marc Gravell
5
@MGOwen btw, lib eksternal adalah 2296 baris kode (tidak termasuk AssemblyInfo.cs); yang membuat 40 baris di sini terlihat cukup masuk akal
Marc Gravell
231

Terlalu mudah tanpa komplikasi:

  1. Tambahkan using System.Linq.Dynamic;di atas.
  2. Menggunakan vehicles = vehicles.AsQueryable().OrderBy("Make ASC, Year DESC").ToList();
Alaa Osta
sumber
11
dan dari mana Anda mendapatkan System.Linq.Dynamic?
Dementic
1
Bekerja saat menggunakan LINQ dengan MongoDB juga.
soupy1976
32
Jawaban yang diterima mungkin merupakan jawaban yang benar di tahun 2008 tetapi saat ini ini adalah jawaban termudah dan paling benar saat ini.
EL MOJO
1
Ini benar-benar penanganan yang baik dan sederhana, sangat rumit secara internal, menyukainya
Mrinal Kamboj
5
Untuk orang-orang di "masa depan", jika Anda menggunakan dotnet core, gunakan ini: nuget.org/packages/System.Linq.Dynamic.Core
Rafael Merlin
78

Saya menemukan jawabannya. Saya dapat menggunakan .AsQueryable<>()metode ekstensi untuk mengubah daftar saya menjadi IQueryable, kemudian menjalankan urutan dinamis dengan menentangnya.

John Sheehan
sumber
52
Tolong berikan contoh untuk kita semua.
MGOwen
54

Baru saja menemukan pertanyaan ini.

Menggunakan implementasi ApplyOrder Marc dari atas, saya menampar bersama metode Extension yang menangani string seperti-SQL seperti:

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

Detail dapat ditemukan di sini: http://aonnull.blogspot.com/2010/08/dynamic-sql-like-linq-orderby-extension.html

Adam Anderson
sumber
1
Hebat, cukup tambahkan modifikasi sebagai berikut untuk membuat case name properti tidak sensitif: PropertyInfo pi = type.GetProperty (prop, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
Mrinal Kamboj
43

Saya kira itu akan berhasil menggunakan refleksi untuk mendapatkan properti apa pun yang ingin Anda urutkan:

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

Perhatikan bahwa menggunakan refleksi jauh lebih lambat daripada mengakses properti secara langsung, sehingga kinerjanya harus diselidiki.

Kjetil Watnedal
sumber
apakah ini bekerja? orderby tidak menginginkan nilai tetapi selektor lamba / delegate (Func <TSource, TKey> keySelector) ..
Davy Landman
2
Saya sudah mencoba contoh ini sebelum mempostingnya, dan ya, itu berhasil.
Kjetil Watnedal
3
+1 Ini persis apa yang saya cari! Ini akan bekerja dengan baik untuk masalah penyortiran halaman sederhana.
Andrew Siemer
Ini tidak berhasil untuk saya. Apakah saya melewatkan sesuatu? Apa yang seharusnya "SomeProperty" menjadi. Saya mencoba memberikan nama properti dan juga property.GetType (). Saya memiliki IQueryable <> dan tidak IEnumerable <>
SO Pengguna
2
@Alex Shkor: Bagaimana Anda bisa mengurutkan elemen tanpa melihat semua elemen? Namun, ada solusi yang lebih baik dalam jawaban lain.
Kjetil Watnedal
19

Hanya membangun apa yang dikatakan orang lain. Saya menemukan bahwa berikut ini bekerja dengan cukup baik.

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;
}
sangat bagus
sumber
12

Saya telah menemukan pertanyaan ini untuk mencari beberapa klausa orderby Linq dan mungkin inilah yang dicari penulis

Berikut cara melakukannya:

var query = pets.OrderBy(pet => pet.Name).ThenByDescending(pet => pet.Age);    
InfoStatus
sumber
5
+1 membatalkan pemungutan suara karena kurangnya penjelasan. Saya juga berpikir penulis mungkin tertarik pada banyak pesanan. Bahkan jika dinamis adalah kata kunci, tidak ada alasan untuk memilih.
Jason Kleban
11

Saya mencoba melakukan ini tetapi mengalami masalah dengan solusi Kjetil Watnedal karena saya tidak menggunakan sintaks LINQ inline - Saya lebih memilih sintaks metode-gaya. Masalah khusus saya adalah mencoba melakukan penyortiran dinamis menggunakan custom IComparer.

Solusi saya berakhir seperti ini:

Diberi permintaan IQueryable seperti:

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

Dan diberi argumen semacam lapangan run-time:

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

OrderBy dinamis terlihat seperti ini:

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

Dan itu menggunakan metode pembantu kecil yang disebut GetReflectedPropertyValue ():

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

Satu hal terakhir - saya menyebutkan bahwa saya ingin OrderBymenggunakan kebiasaan IComparer- karena saya ingin melakukan penyortiran alami .

Untuk melakukan itu, saya hanya mengubah OrderByke:

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

Lihat posting ini untuk mengetahui kodenya NaturalSortComparer().

James McCormack
sumber
5

Gunakan dinamis linq

tambahkan saja using System.Linq.Dynamic;

Dan gunakan seperti ini untuk memesan semua kolom Anda:

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

Anda bisa menambahkannya:

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
}

The GetPropertyValuefungsi dari jawaban Kjetil Watnedal ini

Masalahnya adalah mengapa? Jenis apa pun akan membuang pengecualian pada waktu berjalan, daripada mengkompilasi waktu (seperti jawaban D2VIANT).

Jika Anda berurusan dengan Linq ke Sql dan orderby adalah pohon ekspresi itu akan dikonversi menjadi SQL untuk eksekusi.

Keith
sumber
Mehotod GetPropertyValue akan dieksekusi untuk semua elemen, itu solusi buruk.
Alex Shkor
2
OrderByjangan pertahankan pesanan sebelumnya !!
Amir Ismail
4

Inilah sesuatu yang menurut saya menarik. Jika sumber Anda adalah DataTable, Anda dapat menggunakan penyortiran dinamis tanpa menggunakan Linq Dinamis

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;

referensi: http://msdn.microsoft.com/en-us/library/bb669083.aspx (Menggunakan DataSetExtensions)

Berikut ini satu cara lagi untuk melakukannya dengan mengubahnya menjadi DataView:

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

Terima kasih kepada Maarten ( Permintaan koleksi menggunakan objek PropertyInfo di LINQ ) saya mendapat solusi ini:

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

Dalam kasus saya, saya sedang mengerjakan "ColumnHeaderMouseClick" (WindowsForm) jadi baru saja menemukan Kolom khusus ditekan dan PropertyInfo korespondennya:

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

ATAU

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

(pastikan memiliki Nama kolom Anda yang cocok dengan properti Objek)

Bersulang

joaopintocruz
sumber
4

Setelah banyak pencarian, ini berhasil bagi saya:

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);
}
Sanchitos
sumber
4

Anda dapat mengonversi IEnumerable ke IQueryable.

items = items.AsQueryable().OrderBy("Name ASC");
Richard YS
sumber
3

Solusi alternatif menggunakan kelas / antarmuka berikut. Itu tidak benar-benar dinamis, tetapi berhasil.

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;
    }
}
Mike Christiansen
sumber
2

Jawaban ini merupakan tanggapan terhadap komentar yang membutuhkan contoh untuk solusi yang disediakan oleh @John Sheehan - Runscope

Tolong berikan contoh untuk kita semua.

dalam DAL (Lapisan Akses Data),

Versi IEnumerable:

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

Versi IQueryable

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

Sekarang Anda dapat menggunakan versi IQueryable untuk mengikat, misalnya GridView di Asp.net dan manfaat untuk menyortir (Anda tidak dapat menyortir menggunakan versi IEnumerable)

Saya menggunakan Dapper sebagai ORM dan membangun versi IQueryable dan menggunakan sorting di GridView di asp.net dengan sangat mudah.

M.Hassan
sumber
2

Pertama Instal Alat Dinamis -> NuGet Package Manager -> Package Manager Console

install-package System.Linq.Dynamic

Tambahkan Namespace using System.Linq.Dynamic;

Sekarang kamu bisa menggunakannya OrderBy("Name, Age DESC")

Aminur Rahman
sumber
Bagaimana saya bisa menggunakannya dengan pemilahan properti dalam - seperti OrderBy ("Branch.BranchName", "Descending")
devC
Ini bekerja untuk saya. Mungkin karena pertanyaannya adalah 10 tahun, dan metode yang lebih mudah ini baru muncul kemudian.
kosherjellyfish
1

Anda bisa menggunakan ini:

        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();
}
k1developer
sumber
Beberapa tahun kemudian dan saya menemukan ini; ini bekerja untuk saya, seperti mimpi. Saya memiliki pengurutan dinamis pada 1 hingga 3 properti, dan ini berfungsi seperti mimpi. Mudah diimplementasikan dan tidak repot.
Bazïnga
0

Konversi Daftar ke IEnumerable atau Iquerable, tambahkan menggunakan namespace System.LINQ.Dynamic, maka Anda dapat menyebutkan nama properti dalam string yang dipisah koma ke Metode OrderBy yang datang secara default dari System.LINQ.Dynamic.

pengguna145610
sumber
-3
var result1 = lst.OrderBy(a=>a.Name);// for ascending order. 
 var result1 = lst.OrderByDescending(a=>a.Name);// for desc order. 
Arindam
sumber