Linq OrderBy引数を動的に指定するにはどうすればよいですか?


94

orderbyパラメータとして受け取る値を使用して、渡された引数を指定するにはどうすればよいですか?

例:

List<Student> existingStudends = new List<Student>{ new Student {...}, new Student {...}}

現在の実装:

List<Student> orderbyAddress = existingStudends.OrderBy(c => c.Address).ToList();

の代わりにc.Address、それをパラメーターとしてどのように取ることができますか?

 string param = "City";
 List<Student> orderbyAddress = existingStudends.OrderByDescending(c => param).ToList();

4
あなたは、動的LINQのを探しているかもしれない:weblogs.asp.net/scottgu/archive/2008/01/07/...
BrokenGlass

@Nev_Rahd:質問を少し明確にしようとしました。また、OrderByはLinqの機能であり、オンになっておりIEnumerable、に固有の機能ではありませんList。編集を元に戻したり、さらに変更したりしてください:)
Merlyn Morgan-Graham

回答:


128

これが反射を使用した可能性です...

var param = "Address";    
var propertyInfo = typeof(Student).GetProperty(param);    
var orderByAddress = items.OrderBy(x => propertyInfo.GetValue(x, null));

3
しかし、Entity Framework(SQLサーバー、またはその他)などのプロバイダーによって解釈されるLinq式に関しては、それは本当ですか?
a.boussema 2013

2
@vijay- ThenByメソッドを使用します。
codeConcussion 2013

7
これを実行しようとすると、エラーが発生します。LINQto Entitiesがメソッド 'System.Object GetValue(System.Object、System.Object [])'メソッドを認識せず、このメソッドをストア式に変換できません。この回答はLinq To SQLにのみ適用されますか?
フィリード、2014年

4
.AsEnumerable()でエラーはありません:var orderByAddress = items.AsEnumerable()。OrderBy(x => propertyInfo.GetValue(x、null));
シーザー

1
ascまたはdescで注文する方法を動的に決定する方法
Hitesh Modha

122

次のように、リフレクションを少し使用して式ツリーを作成できます(これは拡張メソッドです)。

public static IQueryable<TEntity> OrderBy<TEntity>(this IQueryable<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[] { type, property.PropertyType },
                                   source.Expression, Expression.Quote(orderByExpression));
     return source.Provider.CreateQuery<TEntity>(resultExpression);
}

orderByPropertydesc並べ替えの基準となるプロパティ名です。のパラメーターとしてtrueを渡すと、降順で並べ替えられます。それ以外の場合は、昇順で並べ替えられます。

今、あなたが行うことができるはずですexistingStudents.OrderBy("City",true);existingStudents.OrderBy("City",false);


10
この答えは素晴らしく、反射の答えよりもはるかに優れています。これは実際には、エンティティフレームワークなどの他のプロバイダーと連携します。
サム

2
できればこれを10回賛成投票します!!! あなたはどこでこのような拡張メソッドを書くことを学びますか?? !!
Jach 2016年

3
これは、組み込みのOrderByと同様に、IOrderedQueryableを返す必要がありますか?そうすれば、.ThenByを呼び出すことができます。
Patrick Szalapski、2016

4
これはEFCore 3.0を使用すると機能しなくなったようで、クエリを変換できないというランタイムエラーが発生します。
ミルダン

3
はい、@ミルダン、これは私にとっても3.0と3.1で壊れます。エラー「〜cant translate」が発生します。必要に応じて、MySQlにPomeloを使用します。問題は式です。式を手動でコーディングすると、機能します。したがって、Lambda.Expression()の代わりに次のようなものを提供するだけです。LambdaExpression orderByExp1 =(Expression <Func <AgencySystemBiz、string >>)(x => x.Name);
Menace

10

@Icarusによる答えを拡張するには、拡張メソッドの戻り値の型をIQueryableではなくIOrderedQueryableにしたい場合は、次のように結果をキャストするだけです。

public static IOrderedQueryable<TEntity> OrderBy<TEntity>(this IQueryable<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[] { type, property.PropertyType },
        source.Expression, Expression.Quote(orderByExpression));
    return (IOrderedQueryable<TEntity>)source.Provider.CreateQuery<TEntity>(resultExpression);
}

2
他の回答はEntity Frameworkには適切ではないようです。Linq to EntitiesはGetProperty、GetValue
Bill

1
この方法は、3.0および3.1では失敗するようです(2.2では機能していました)。MySqlにはPomeloを使用しています。回避策がありますが、醜いです。上記の私のコメントを参照してください。
Menace

これは私にとってEF 3.0で機能しました。ただし、フロントエンドで大文字と小文字の区別を一致させる必要がないように、次の行を変更する必要があり ます。
アーサー王

これはまだCore 3.1用に最適化されていますか?
クリス・ゴー

8

1)System.Linq.Dynamicをインストールします

2)次のコードを追加します

public static class OrderUtils
{
    public static string ToStringForOrdering<T, TKey>(this Expression<Func<T, TKey>> expression, bool isDesc = false)
    {
        var str = expression.Body.ToString();
        var param = expression.Parameters.First().Name;
        str = str.Replace("Convert(", "(").Replace(param + ".", "");
        return str + (isDesc ? " descending" : "");
    }
}

3)Lambda関数を選択するためのスイッチを記述します

public static class SortHelper
{
    public static Expression<Func<UserApp, object>> UserApp(string orderProperty)
    {
        orderProperty = orderProperty?.ToLowerInvariant();
        switch (orderProperty)
        {
            case "firstname":
                return x => x.PersonalInfo.FirstName;
            case "lastname":
                return x => x.PersonalInfo.LastName;
            case "fullname":
                return x => x.PersonalInfo.FirstName + x.PersonalInfo.LastName;
            case "email":
                return x => x.Email;

        }
    }
}

4)ヘルパーを使用する

Dbset.OrderBy(SortHelper.UserApp("firstname").ToStringForOrdering())

5)ページング(PagedList)で使用できます。

public virtual  IPagedList<T> GetPage<TOrder>(Page page, Expression<Func<T, bool>> where, Expression<Func<T, TOrder>> order, bool isDesc = false,
      params Expression<Func<T, object>>[] includes)
    {
        var orderedQueryable = Dbset.OrderBy(order.ToStringForOrdering(isDesc));
        var query = orderedQueryable.Where(where).GetPage(page);
        query = AppendIncludes(query, includes);

        var results = query.ToList();
        var total =  Dbset.Count(where);

        return new StaticPagedList<T>(results, page.PageNumber, page.PageSize, total);
    }

説明

System.Linq.Dynamicを使用すると、OrderByメソッドで文字列値を設定できます。ただし、この拡張機能の内部では、文字列はLambdaに解析されます。そのため、Lambdaを解析して文字列に変換し、OrderByメソッドに渡せばうまくいくと思いました。そしてそれはうまくいきます!


6
   private Func<T, object> GetOrderByExpression<T>(string sortColumn)
    {
        Func<T, object> orderByExpr = null;
        if (!String.IsNullOrEmpty(sortColumn))
        {
            Type sponsorResultType = typeof(T);

            if (sponsorResultType.GetProperties().Any(prop => prop.Name == sortColumn))
            {
                System.Reflection.PropertyInfo pinfo = sponsorResultType.GetProperty(sortColumn);
                orderByExpr = (data => pinfo.GetValue(data, null));
            }
        }
        return orderByExpr;
    }

    public List<T> OrderByDir<T>(IEnumerable<T> source, string dir, Func<T, object> OrderByColumn)
    {
        return dir.ToUpper() == "ASC" ? source.OrderBy(OrderByColumn).ToList() : source.OrderByDescending(OrderByColumn).ToList();``
    }

 // Call the code like below
        var orderByExpression= GetOrderByExpression<SearchResultsType>(sort);

    var data = OrderByDir<SponsorSearchResults>(resultRecords, SortDirectionString, orderByExpression);    

鮮やかさ!まさに私が必要としたもの。
ブランドングリフィン

5

これは、条件付き降順を処理するために思いついたものです。これをkeySelectorfuncを動的に生成する他の方法と組み合わせることができます。

    public static IOrderedQueryable<TSource> OrderBy<TSource, TKey>(this IQueryable<TSource> source,
            System.Linq.Expressions.Expression<Func<TSource, TKey>> keySelector,
            System.ComponentModel.ListSortDirection sortOrder
            )
    {
        if (sortOrder == System.ComponentModel.ListSortDirection.Ascending)
            return source.OrderBy(keySelector);
        else
            return source.OrderByDescending(keySelector);
    }

使用法:

//imagine this is some parameter
var direction = System.ComponentModel.ListSortDirection.Ascending;
query = query.OrderBy(ec => ec.MyColumnName, direction);

これにより.OrderBy、この拡張を新しいパラメーターで任意のIQueryable にチェーンできることに注意してください。

// perhaps passed in as a request of user to change sort order
// var direction = System.ComponentModel.ListSortDirection.Ascending;
query = context.Orders
        .Where(o => o.Status == OrderStatus.Paid)
        .OrderBy(ec => ec.OrderPaidUtc, direction);

3

これはstring、質問で要求したように、を渡すことはできませんが、それでも機能する可能性があります。

OrderByDescending方法はかかるFunc<TSource, TKey>ので、あなたの機能をこのように書き換えることができます。

List<Student> QueryStudents<TKey>(Func<Student, TKey> orderBy)
{
    return existingStudents.OrderByDescending(orderBy).ToList();
}

他のためのオーバーロードがありますOrderByDescending取るだけでなくExpression<Func<TSource, TKey>>、および/またはIComparer<TKey>。また、それらを調べて、それらがあなたに何か有用なものを提供しているかどうかを確認することもできます。


TKeyの型を定義していないため、これは機能しません。代わりに、<T>を<TKey>に変更する必要があります。
Patrick Desjardins 2014

これは私にとってうまくいきました!渡されたブール値に応じて、リストを昇順または降順で並べ替える関数が必要でした。あなたのコードは少し調整してうまくいきました!
Joe Gayetty、2016

LINQ in Action:IEnumerable <Book> CustomSort <TKey>(Func <Book、TKey> selector、Boolean ascending){IEnumerable <Book> books = SampleData.Books; 昇順で戻りますか?books.OrderBy(selector):books.OrderByDescending(selector); }
Leszek P

1

私のために働いた唯一のソリューションは、neoGenevaによってここhttps://gist.github.com/neoGeneva/1878868に投稿されました。

私は彼のコードを再投稿しますが、それはうまく機能し、インターウェブで失われることを望まないからです!

    public static IQueryable<T> OrderBy<T>(this IQueryable<T> source, string sortExpression)
    {
        if (source == null)
            throw new ArgumentNullException("source", "source is null.");

        if (string.IsNullOrEmpty(sortExpression))
            throw new ArgumentException("sortExpression is null or empty.", "sortExpression");

        var parts = sortExpression.Split(' ');
        var isDescending = false;
        var propertyName = "";
        var tType = typeof(T);

        if (parts.Length > 0 && parts[0] != "")
        {
            propertyName = parts[0];

            if (parts.Length > 1)
            {
                isDescending = parts[1].ToLower().Contains("esc");
            }

            PropertyInfo prop = tType.GetProperty(propertyName);

            if (prop == null)
            {
                throw new ArgumentException(string.Format("No property '{0}' on type '{1}'", propertyName, tType.Name));
            }

            var funcType = typeof(Func<,>)
                .MakeGenericType(tType, prop.PropertyType);

            var lambdaBuilder = typeof(Expression)
                .GetMethods()
                .First(x => x.Name == "Lambda" && x.ContainsGenericParameters && x.GetParameters().Length == 2)
                .MakeGenericMethod(funcType);

            var parameter = Expression.Parameter(tType);
            var propExpress = Expression.Property(parameter, prop);

            var sortLambda = lambdaBuilder
                .Invoke(null, new object[] { propExpress, new ParameterExpression[] { parameter } });

            var sorter = typeof(Queryable)
                .GetMethods()
                .FirstOrDefault(x => x.Name == (isDescending ? "OrderByDescending" : "OrderBy") && x.GetParameters().Length == 2)
                .MakeGenericMethod(new[] { tType, prop.PropertyType });

            return (IQueryable<T>)sorter
                .Invoke(null, new object[] { source, sortLambda });
        }

        return source;
    }

1
  • コードにナゲットパッケージDynamiteを追加する

  • 名前空間Dynamite.Extensions Eg を追加します:using Dynamite.Extensions;

  • SQLクエリと同様に、クエリで注文します。例:student.OrderBy( "City DESC、Address")。ToList();


1

@Icarusの応答を拡張するには:2つのフィールドでソートしたい場合は、次の機能を実行できます(1つのフィールドで、Icariusの応答は非常にうまく機能します)。

public static IQueryable<T> OrderByDynamic<T>(this IQueryable<T> q, string SortField1, string SortField2, bool Ascending)
        {
            var param = Expression.Parameter(typeof(T), "p");
            var body = GetBodyExp(SortField1, SortField2, param);
            var exp = Expression.Lambda(body, param);

            string method = Ascending ? "OrderBy" : "OrderByDescending";
            Type[] types = new Type[] { q.ElementType, exp.Body.Type };
            var mce = Expression.Call(typeof(Queryable), method, types, q.Expression, exp);
            return q.Provider.CreateQuery<T>(mce);
        }

これは本体がラムダ式に対して返す関数で、stringとintで機能しますが、各プログラマーのニーズに応じて機能させるには、型を追加するだけで十分です

public static NewExpression GetBodyExp(string field1, string field2, ParameterExpression Parametro)
        {    
            // SE OBTIENE LOS NOMBRES DE LOS TIPOS DE VARIABLE 
            string TypeName1 = Expression.Property(Parametro, field1).Type.Name;
            string TypeName2 = Expression.Property(Parametro, field2).Type.Name;

            // SE DECLARA EL TIPO ANONIMO SEGUN LOS TIPOS DE VARIABLES
            Type TypeAnonymous = null;
            if (TypeName1 == "String")
            {
                string var1 = "0";
                if (TypeName2 == "Int32")
                {
                    int var2 = 0;
                    var example = new { var1, var2 };
                    TypeAnonymous = example.GetType();
                }

                if (TypeName2 == "String")
                {
                    string var2 = "0";
                    var example = new { var1, var2 };
                    TypeAnonymous = example.GetType();
                }    
            }    

            if (TypeName1 == "Int32")
            {
                int var1 = 0;
                if (TypeName2 == "Int32")
                {
                    string var2 = "0";
                    var example = new { var1, var2 };
                    TypeAnonymous = example.GetType();
                }

                if (TypeName2 == "String")
                {
                    string var2 = "0";
                    var example = new { var1, var2 };
                    TypeAnonymous = example.GetType();
                }    
            }

            //se declaran los TIPOS NECESARIOS PARA GENERAR EL BODY DE LA EXPRESION LAMBDA
            MemberExpression[] args = new[] { Expression.PropertyOrField(Parametro, field1), Expression.PropertyOrField(Parametro, field2) };
            ConstructorInfo CInfo = TypeAnonymous.GetConstructors()[0];
            IEnumerable<MemberInfo> a = TypeAnonymous.GetMembers().Where(m => m.MemberType == MemberTypes.Property);

            //BODY 
            NewExpression body = Expression.New(CInfo, args, TypeAnonymous.GetMembers().Where(m => m.MemberType == MemberTypes.Property));

            return body;
        }

それを使用するには、次のようにします

IQueryable<MyClass> IqMyClass= context.MyClass.AsQueryable();
List<MyClass> ListMyClass= IqMyClass.OrderByDynamic("UserName", "IdMyClass", true).ToList();

これを行うためのより良い方法がある場合、彼らがそれを共有することは素晴らしいことです

私はおかげでそれを解決することができました:どのようにしてLinqでMultipleプロパティラムダ式を作成できます


-1

私はパーティーにかなり遅れましたが、これらの解決策はどれもうまくいきませんでした。私はSystem.Linq.Dynamicを試してみたいと思っていましたが、Nugetでそれを見つけることができませんでした。どちらにしても...

ここに私が思いついた解決策があります。OrderByOrderByDescendingOrderBy> ThenByの混合を動的に使用する必要がありました

私は単にリストオブジェクトの拡張メソッドを作成しましたが、少しハッキーです。私がこれを頻繁に実行している場合はお勧めしませんが、1回限りの使用には適しています。

List<Employee> Employees = GetAllEmployees();

foreach(Employee oEmployee in Employees.ApplyDynamicSort(eEmployeeSort))
{
    //do stuff
}

public static IOrderedEnumerable<Employee> ApplyDynamicSort(this List<Employee> lEmployees, Enums.EmployeeSort eEmployeeSort)
{
    switch (eEmployeeSort)
    {
        case Enums.EmployeeSort.Name_ASC:
            return lEmployees.OrderBy(x => x.Name);
        case Enums.EmployeeSort.Name_DESC:
            return lEmployees.OrderByDescending(x => x.Name);
        case Enums.EmployeeSort.Department_ASC_Salary_DESC:
            return lEmployees.OrderBy(x => x.Department).ThenByDescending(y => y.Salary);
        default:
            return lEmployees.OrderBy(x => x.Name);
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.