web-dev-qa-db-ja.com

C#-プロパティ名を文字列として使用して、プロパティで並べ替えるコード

文字列としてプロパティ名を持っているときにC#のプロパティに対してコーディングする最も簡単な方法は何ですか?たとえば、ユーザーが(LINQを使用して)選択したプロパティで検索結果を並べることができるようにします。 UIの「order by」プロパティを、もちろん文字列値として選択します。条件付きロジック(if/else、switch)を使用して文字列をプロパティにマッピングすることなく、その文字列をlinqクエリのプロパティとして直接使用する方法はありますか。反射?

論理的に、これは私がやりたいことです:

query = query.OrderBy(x => x."ProductId");

更新:元々、Linq to Entitiesを使用することを指定しませんでした-リフレクション(少なくともGetProperty、GetValueアプローチ)はL2Eに変換されないようです。

69
Jeremy

私は他の人が投稿したものにこの代替を提供します。

System.Reflection.PropertyInfo prop = typeof(YourType).GetProperty("PropertyName");

query = query.OrderBy(x => prop.GetValue(x, null));

これにより、プロパティを取得するためにリフレクションAPIを繰り返し呼び出すことがなくなります。これで、繰り返される唯一の呼び出しは値の取得です。

ただし、

代わりにPropertyDescriptorを使用することを推奨します。これにより、カスタムTypeDescriptorsをタイプに割り当てて、プロパティと値を取得するための軽量な操作を可能にします。カスタム記述子がない場合は、とにかくリフレクションにフォールバックします。

PropertyDescriptor prop = TypeDescriptor.GetProperties(typeof(YourType)).Find("PropertyName");

query = query.OrderBy(x => prop.GetValue(x));

高速化については、CodeProjectのMarc Gravelの HyperDescriptor プロジェクトをご覧ください。私はこれを大成功で使用しました。これは、ビジネスオブジェクトでの高性能データバインディングと動的プロパティ操作の命の恩人です。

100
Adam Robinson

私はパーティーに少し遅れましたが、これが助けになることを願っています。

リフレクションを使用する場合の問題は、結果のExpression Treeが、内部.Netプロバイダー以外のLinqプロバイダーによってほぼ確実にサポートされないことです。これは内部コレクションでは問題ありませんが、ページネーションの前にソース(SQL、MongoDbなど)でソートが行われる場所では機能しません。

以下のコードサンプルは、OrderByおよびOrde​​rByDescendingのIQueryableエクステンションメソッドを提供し、次のように使用できます。

query = query.OrderBy("ProductId");

拡張方法:

public static class IQueryableExtensions 
{
    public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, string propertyName)
    {
        return source.OrderBy(ToLambda<T>(propertyName));
    }

    public static IOrderedQueryable<T> OrderByDescending<T>(this IQueryable<T> source, string propertyName)
    {
        return source.OrderByDescending(ToLambda<T>(propertyName));
    }

    private static Expression<Func<T, object>> ToLambda<T>(string propertyName)
    {
        var parameter = Expression.Parameter(typeof(T));
        var property = Expression.Property(parameter, propertyName);
        var propAsObject = Expression.Convert(property, typeof(object));

        return Expression.Lambda<Func<T, object>>(propAsObject, parameter);            
    }
}

よろしく、マーク。

51
Mark Powell

@ Mark Powell からの回答が好きでしたが、 @ ShuberF が言ったように、エラー_LINQ to Entities only supports casting EDM primitive or enumeration types_が出ます。

var propAsObject = Expression.Convert(property, typeof(object));を削除しても、intをオブジェクトに暗黙的にボックス化しないため、整数などの値型のプロパティでは機能しませんでした。

Kristofer Andersson および Marc Gravell のアイデアを使用するプロパティ名を使用してQueryable関数を構築し、Entity Frameworkで引き続き機能させる方法を見つけました。オプションのIComparerパラメーターも含めました。 注意:IComparerパラメーターはEntity Frameworkでは機能しないため、Linq to Sqlを使用する場合は省略してください。

以下は、Entity FrameworkおよびLinq to Sqlで動作します。

_query = query.OrderBy("ProductId");
_

@ Simon Scheurer これも機能します:

_query = query.OrderBy("ProductCategory.CategoryId");
_

Entity FrameworkまたはLinq to Sqlを使用していない場合、これは機能します。

_query = query.OrderBy("ProductCategory", comparer);
_

コードは次のとおりです。

_public static class IQueryableExtensions 
{    
public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> query, string propertyName, IComparer<object> comparer = null)
{
    return CallOrderedQueryable(query, "OrderBy", propertyName, comparer);
}

public static IOrderedQueryable<T> OrderByDescending<T>(this IQueryable<T> query, string propertyName, IComparer<object> comparer = null)
{
    return CallOrderedQueryable(query, "OrderByDescending", propertyName, comparer);
}

public static IOrderedQueryable<T> ThenBy<T>(this IOrderedQueryable<T> query, string propertyName, IComparer<object> comparer = null)
{
    return CallOrderedQueryable(query, "ThenBy", propertyName, comparer);
}

public static IOrderedQueryable<T> ThenByDescending<T>(this IOrderedQueryable<T> query, string propertyName, IComparer<object> comparer = null)
{
    return CallOrderedQueryable(query, "ThenByDescending", propertyName, comparer);
}

/// <summary>
/// Builds the Queryable functions using a TSource property name.
/// </summary>
public static IOrderedQueryable<T> CallOrderedQueryable<T>(this IQueryable<T> query, string methodName, string propertyName,
        IComparer<object> comparer = null)
{
    var param = Expression.Parameter(typeof(T), "x");

    var body = propertyName.Split('.').Aggregate<string, Expression>(param, Expression.PropertyOrField);

    return comparer != null
        ? (IOrderedQueryable<T>)query.Provider.CreateQuery(
            Expression.Call(
                typeof(Queryable),
                methodName,
                new[] { typeof(T), body.Type },
                query.Expression,
                Expression.Lambda(body, param),
                Expression.Constant(comparer)
            )
        )
        : (IOrderedQueryable<T>)query.Provider.CreateQuery(
            Expression.Call(
                typeof(Queryable),
                methodName,
                new[] { typeof(T), body.Type },
                query.Expression,
                Expression.Lambda(body, param)
            )
        );
}
}
_
23
David Specht

はい、リフレクション以外の方法はないと思います。

例:

query = query.OrderBy(x => x.GetType().GetProperty("ProductId").GetValue(x, null));
12
Alon Gubkin
query = query.OrderBy(x => x.GetType().GetProperty("ProductId").GetValue(x, null));

頭の上の正確な構文を思い出そうとしていますが、それは正しいと思います。

5
dkackman

動的なLinqを使用できます- this ブログをご覧ください。

また、 this StackOverFlow postをチェックしてください...

2

反射が答えです!

typeof(YourType).GetProperty("ProductId").GetValue(theInstance);

反映されたPropertyInfoのキャッシュ、不正な文字列のチェック、クエリ比較関数の作成などのためにできることはたくさんありますが、基本的にはこれがあなたのすることです。

2
Sebastian Good

動的注文アイテムのリフレクション拡張よりも生産性が高い:

public static class DynamicExtentions
{
    public static object GetPropertyDynamic<Tobj>(this Tobj self, string propertyName) where Tobj : class
    {
        var param = Expression.Parameter(typeof(Tobj), "value");
        var getter = Expression.Property(param, propertyName);
        var boxer = Expression.TypeAs(getter, typeof(object));
        var getPropValue = Expression.Lambda<Func<Tobj, object>>(boxer, param).Compile();            
        return getPropValue(self);
    }
}

例:

var ordered = items.OrderBy(x => x.GetPropertyDynamic("ProductId"));

また、準拠したlambasをキャッシュする必要がある場合があります(例:Dictionary <>)

2
devi

また、 動的式 はこの問題を解決できます。実行時に動的に構築できたLINQ式を介して文字列ベースのクエリを使用できます。

var query = query
          .Where("Category.CategoryName == @0 and Orders.Count >= @1", "Book", 10)
          .OrderBy("ProductId")
          .Select("new(ProductName as Name, Price)");
1
ali-myousefi