IEnumerable <T> / IQueryable <T>の動的LINQ OrderBy


668

動的LINQのVS2008ので、SQLのような文字列を使用できるを見つけました(例:OrderBy("Name, Age DESC"))順序付け。残念ながら、含まれているメソッドはでのみIQueryable<T>機能しIEnumerable<T>ます。この機能を取得する方法はありますか?


1
私の意見では、この日付の時点での最良の答えはSystem.Linq.Dynamic.Coreライブラリです。
Shahin Dohan、2018年

回答:


903

ちょうどこのオールディーズに出会った...

動的LINQライブラリなしでこれを行うには、以下のコードが必要です。これは、ネストされたプロパティを含む最も一般的なシナリオをカバーしています。

それを動作させるIEnumerable<T>には、経由するいくつかのラッパーメソッドを追加できAsQueryableますが、以下のコードはExpression必要なコアロジックです。

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

編集:LINQ-to-Objectsにのみ適用されることにdynamic注意してくださいdynamic(ORMの式ツリーなどは実際には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);
        }
    }
}

109
私が見た中で最高の気の利いたコード:)私のプロジェクトで100万の問題を解決しただけです:)
sajidnizami 2008年

4
@Dave-で始める必要があるIQueryable<T>ため、List<T>(などIEnumerable<T>)のようなものがある場合は、使用する必要があるかもしれませんAsQueryable()-たとえばvar sorted = someList.AsQueryable().OrderBy("Foo.Bar");
Marc Gravell

7
これを見たことありますか?それは一部の人々を助けるかもしれません... stackoverflow.com/questions/557819 / ... より強く型付けされたソリューションです。
anthonyv

28
@MGOwenあなたはコードの性質を誤解しているようです。40行は、プロジェクトのどこかに配置した40行であるかどうか、またはそれらの行が外部ライブラリで(プリコンパイルされた、またはソースとして)来た場合でも同じです。それはされているだろうかなり驚くべき私は12月'11から存在していnuget上のライブラリに10月'08には、リンクされていた場合(なく、少なくともnugetはどちらかそれから存在していなかったので)、しかしである「何をしているか」の基本的な同じ。また、「実際のソリューション」というフレーズを使用して、すべてのコーディングの質問への明確に合意された単一のルートがあるように見えます。
マークグラベル

5
@MGOwenところで、外部libは2296行のコードです(AssemblyInfo.csは含みません)。ここの40行はかなり合理的に見えます
マークグラベル

231

複雑化することなく簡単すぎる:

  1. using System.Linq.Dynamic;上部に追加します。
  2. 使用する vehicles = vehicles.AsQueryable().OrderBy("Make ASC, Year DESC").ToList();

11
どこSystem.Linq.Dynamicから入手しましたか?
認知症、2013

1
MongoDBでlinqを使用する場合にも機能します。
soupy1976 2013

32
受け入れられた答えは2008年の正解だったかもしれませんが、現在これが現在最も簡単で最も正しい答えです。
EL MOJO 2014年

1
これは本当に
素晴らしく

5
「未来」の人々のために、ドットネットコアを使用している場合、これを使用してください:nuget.org/packages/System.Linq.Dynamic.Core
Rafael Merlin

78

答えを見つけました。私が使用することができ.AsQueryable<>()、それに対してによってダイナミック順を実行し、その後、のIQueryableに私のリストを変換するために、拡張メソッドを。


52
残りの例を挙げてください。
MGOwen 2013

54

この質問に遭遇しました。

上記のMarcのApplyOrder実装を使用して、次のようなSQLのような文字列を処理するExtensionメソッドを組み合わせました。

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

詳細はここにあります:http : //aonnull.blogspot.com/2010/08/dynamic-sql-like-linq-orderby-extension.html


1
次のように変更して、プロパティ名の大文字と小文字を区別しないようにします。PropertyInfo pi = type.GetProperty(prop、BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
Mrinal Kamboj

43

リフレクションを使用して、並べ替えたいプロパティを取得するのが効果的だと思います。

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

リフレクションの使用は、プロパティに直接アクセスするよりもかなり遅いため、パフォーマンスを調査する必要があることに注意してください。


これでも機能しますか?ORDERBYは、値ではなく、セレクタラムダ/デリゲート(機能<TSOURCE、TKEY> keySelector)..望んでいない
デイビーLandman

2
私はそれを投稿する前にこの例を試しました、そしてはい、それはうまくいきます。
Kjetil Watnedal、2008年

3
+1これはまさに私が探していたものです!これは、単純なページ並べ替えの問題に最適です。
アンドリューシーマー

これは私にはうまくいきませんでした。何か不足していますか?「SomeProperty」はどうあるべきか。プロパティ名とproperty.GetType()を指定してみました。IQueryable <>はありますがIEnumerable <>はありません
SOユーザー

2
@Alex Shkor:すべての要素を確認せずに要素をどのように並べ替えますか?ただし、他の答えにはより良い解決策があります。
Kjetil Watnedal、2012

19

他の人が言ったことに基づいて構築するだけです。私は以下がかなりうまくいくことを発見しました。

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

Linqの複数のorderby句を探してこの質問を見つけましたが、おそらくこれが作者が探していたものです

これを行う方法は次のとおりです。

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

5
説明が不足していたため、+ 1が反対票をキャンセルしました。私はまた、著者が複数の注文書に興味を持っていたのではないかと思います。ダイナミックキーワードであったとしても、反対票を投じる理由はありません。
Jason Kleban

11

私はこれを試みましたが、インラインlinq構文を使用していないため、Kjetil Watnedalのソリューションに問題がありました。私の特定の問題は、カスタムを使用して動的な並べ替えを行うことでした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() : "";
}

最後にもう1つ- OrderByカスタムを使用することを希望したと述べました-私IComparer自然分類をしたかったからです。

これを行うには、次のように変更OrderByします。

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

のコードについては、この投稿を参照してくださいNaturalSortComparer()


5

ダイナミックを使用 linq

追加するだけ using System.Linq.Dynamic;

次のように使用して、すべての列を並べ替えます。

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

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の答え

問題はなぜでしょうか?このような並べ替えは、コンパイル時ではなく実行時に例外をスローします(D2VIANTの回答のように)。

Linq to Sqlを処理していて、orderbyが式ツリーである場合、SQLに変換されて実行されます。


GetPropertyValue mehotodはすべての要素に対して実行されますが、これは悪い解決策です。
Alex Shkor

2
OrderBy以前の順序を維持しないでください!!
Amir Ismail

4

ここで私が興味深いと思ったものがあります。ソースがDataTableの場合、Dynamic 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に変換して実行するもう1つの方法を次に示します。

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

4

Maartenのおかげで(LINQでPropertyInfoオブジェクトを使用してコレクションをクエリします)、次の解決策を得ました。

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


3

別のソリューションでは、次のクラス/インターフェースを使用します。動的ではありませんが、機能します。

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

この回答は、@ 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バージョンを使用してバインドできます。たとえば、Asp.netのGridViewを使用すると、並べ替えのメリットがあります(IEnumerableバージョンを使用して並べ替えることはできません)。

私はDapperをORMとして使用し、IQueryableバージョンを構築し、asp.netのGridViewでの並べ替えをとても簡単に利用しました。


2

最初に動的ツールをインストール -> NuGetパッケージマネージャー->パッケージマネージャーコンソール

install-package System.Linq.Dynamic

名前空間を追加 using System.Linq.Dynamic;

今、あなたは使うことができます OrderBy("Name, Age DESC")


OrderBy( "Branch.BranchName"、 "Descending")のような内部プロパティの並べ替えでどのように使用できますか?
devC

これでうまくいきます。おそらく、質問が10年前のものであり、この簡単な方法が後になってからだったのでしょう。
kosherjellyfish

1

あなたはこれを使うことができます:

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

数年後、私はこれに遭遇しました。これは夢のように私のために働いた。私は1〜3個のプロパティを動的にソートしていますが、これは夢のように機能します。実装が簡単で手間がかかりません。
バザンガ

0

リストをIEnumerableまたはIquerableに変換し、System.LINQ.Dynamic名前空間を使用して追加すると、プロパティ名をカンマ区切りの文字列で、デフォルトでSystem.LINQ.Dynamicから来るOrderByメソッドに記述できます。


-3
var result1 = lst.OrderBy(a=>a.Name);// for ascending order. 
 var result1 = lst.OrderByDescending(a=>a.Name);// for desc order. 
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.