721

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

4

22 に答える 22

946

この古き良きものに偶然出くわしました...

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

編集:それを組み合わせたい場合はもっと楽しくなりますdynamic-ただしdynamic、LINQ-to-Objectsにのみ適用されることに注意してください(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);
        }
    }
}
于 2008-10-24T13:21:09.627 に答える
269

簡単すぎて複雑なことはありません:

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

編集: 時間を節約するために、System.Linq.Dynamic.Core (System.Linq.Dynamic は非推奨) アセンブリはフレームワークの一部ではありませんが、nuget からインストールできます: System.Linq.Dynamic.Core

于 2011-12-28T19:24:01.227 に答える
56

この質問に出くわしました。

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

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

詳細はこちら: http://aonnull.blogspot.com/2010/08/dynamic-sql-like-linq-orderby-extension.html

于 2010-08-18T01:55:30.763 に答える
46

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

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

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

于 2008-09-03T07:00:05.270 に答える
21

他の人が言ったことに基づいているだけです。以下が非常にうまく機能することがわかりました。

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;
}
于 2009-04-01T07:04:53.667 に答える
12

私はこれをやろうとしていましたが、インライン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()

于 2009-10-29T11:01:16.020 に答える
10

Linq の複数の orderby 句を探してこの質問につまずきましたが、おそらくこれが著者が探していたものでした

その方法は次のとおりです。

var query = pets.OrderBy(pet => pet.Name).ThenByDescending(pet => pet.Age);    
于 2008-12-16T00:00:15.757 に答える
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 に変換されます。

于 2008-09-03T10:37:31.193 に答える
4

IEnumerable を IQueryable に変換できます。

items = items.AsQueryable().OrderBy("Name ASC");
于 2014-07-30T10:29:40.597 に答える
4

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

install-package System.Linq.Dynamic

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

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

于 2018-03-10T18:31:00.010 に答える
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();
于 2010-01-13T16:20:24.993 に答える
3

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

(列名がオブジェクトのプロパティと一致していることを確認してください)

乾杯

于 2012-10-16T17:24:34.840 に答える
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;
    }
}
于 2009-03-16T02:05:29.770 に答える
0

List を IEnumerable または Iquerable に変換し、System.LINQ.Dynamic 名前空間を使用して追加すると、デフォルトで System.LINQ.Dynamic から取得される OrderBy メソッドにプロパティ名をコンマ区切り文字列で指定できます。

于 2013-08-05T15:37:27.310 に答える
-3
var result1 = lst.OrderBy(a=>a.Name);// for ascending order. 
 var result1 = lst.OrderByDescending(a=>a.Name);// for desc order. 
于 2016-05-16T10:01:34.220 に答える