속성 이름을 문자열로 사용하여 속성별로 정렬하는 코드


92

속성 이름을 문자열로 사용할 때 C #의 속성에 대해 코딩하는 가장 간단한 방법은 무엇입니까? 예를 들어 사용자가 LINQ를 사용하여 선택한 속성별로 일부 검색 결과를 정렬 할 수 있도록하고 싶습니다. UI의 "order by"속성을 문자열 값으로 선택합니다. 조건부 논리 (if / else, switch)를 사용하여 문자열을 속성에 매핑하지 않고도 해당 문자열을 linq 쿼리의 속성으로 직접 사용할 수있는 방법이 있습니까? 반사?

논리적으로 이것은 내가하고 싶은 것입니다.

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

업데이트 : 원래 Linq to Entities를 사용하고 있음을 지정하지 않았습니다. 반영 (적어도 GetProperty, GetValue 접근 방식)이 L2E로 변환되지 않는 것 같습니다.


리플렉션을 사용해야한다고 생각하고 람다 식에서 리플렉션을 사용할 수 있을지 모르겠습니다 ... 음, Linq to SQL에서는 거의 확실하지 않지만 Linq를 목록 등에 사용할 때 사용할 수 있습니다.
— CodeRedick 2009

@Telos : 람다에서 리플렉션 (또는 다른 API)을 사용할 수없는 이유가 없습니다. 코드가 식으로 평가되고 다른 것으로 변환되는 경우 작동할지 여부 (예 : LINQ-to-SQL)는 완전히 또 다른 질문입니다.
— Adam Robinson

이것이 제가 답변 대신 댓글을 게시 한 이유입니다. ;) Linq2SQL에 주로 사용됩니다 ...
— CodeRedick 2009

1
같은 문제를 극복해야했습니다. 아래 내 대답을 참조하십시오. stackoverflow.com/a/21936366/775114
— Mark Powell

답변:


129

나는 다른 사람들이 게시 한 것에이 대안을 제공 할 것입니다.

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

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

이렇게하면 속성을 얻기 위해 리플렉션 API에 대한 반복적 인 호출이 방지됩니다. 이제 유일한 반복 호출은 값을 얻는 것입니다.

하나

PropertyDescriptor대신에 a 를 사용하는 것이 좋습니다. 이렇게하면 사용자 TypeDescriptor지정을 유형에 할당 할 수 있으므로 속성과 값을 검색하기위한 간단한 작업을 수행 할 수 있습니다. 사용자 지정 설명자가 없으면 어쨌든 리플렉션으로 돌아갑니다.

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

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

속도를 높이려면 HyperDescriptorCodeProject에 대한 Marc Gravel의 프로젝트를 확인하십시오 . 저는 이것을 아주 성공적으로 사용했습니다. 비즈니스 객체에 대한 고성능 데이터 바인딩 및 동적 속성 작업을위한 생명의 은인입니다.


반영된 호출 (즉, GetValue)은 반영에서 가장 비용이 많이 드는 부분입니다. 메타 데이터 검색 (예 : GetProperty)은 실제로 비용이 적게 듭니다 (대략적으로). 따라서 해당 부분을 캐싱하면 실제로 그다지 절약되지 않습니다. 이것은 어느 쪽이든 비용이 거의 같을 것이고 그 비용은 무겁습니다. 참고할 사항입니다.
— jrista 2009

1
@jrista : 확실히 호출은 가장 비용이 많이 듭니다. 그러나 "비용이 적게 든다"는 것은 "무료"또는 그에 가까운 것을 의미하지 않습니다. 메타 데이터 검색에는 사소한 시간이 걸리지 않으므로 캐싱하는 데 이점이 있으며 단점이 없습니다 (여기에서 누락 된 부분이없는 한). 사실 이것은 PropertyDescriptor어쨌든 ( 값 검색을 경량 작업으로 만들 수 있는 사용자 정의 유형 설명자를 설명하기 위해) 실제로 사용해야합니다 .
— Adam Robinson

프로그래밍 방식으로 ASP.NET GridView 정렬을 처리하기 위해 몇 시간 동안 검색했습니다. PropertyDescriptor prop = TypeDescriptor.GetProperties (typeof (ScholarshipRequest)). Find (e.SortExpression, true);
— Baxter

1
stackoverflow.com/questions/61635636/… 리플렉션에 문제가 있었는데 EfCore 3.1.3에서 해결되지 않았습니다. 경고를 위해 활성화해야하는 EfCore 2에서 오류가 발생하는 것 같습니다. 아래
— @Mark

1
다음 메시지가 나타납니다. InvalidOperationException : The LINQ 식 'DbSet <MyObject> .Where (t => t.IsMasterData) .OrderBy (t => t.GetType (). GetProperty ( "Address"). GetValue (obj : t, index : null) .GetType ()) '을 번역 할 수 없습니다. 번역 할 수있는 형식으로 쿼리를 다시 작성하거나 AsEnumerable (), AsAsyncEnumerable (), ToList () 또는 ToListAsync ()에 대한 호출을 삽입하여 명시 적으로 클라이언트 평가로 전환합니다.
— bbrinck

67

파티에 조금 늦었지만 도움이 되었으면합니다.

리플렉션을 사용할 때의 문제는 결과 식 트리가 내부 .Net 공급자가 아닌 다른 Linq 공급자에서 거의 확실히 지원되지 않는다는 것입니다. 내부 컬렉션에는 괜찮지 만 페이지 매김 전에 소스 (SQL, MongoDb 등)에서 정렬이 수행되는 경우에는 작동하지 않습니다.

아래 코드 샘플은 OrderBy 및 OrderByDescending에 대한 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);            
    }
}

감사합니다, 마크.


탁월한 솔루션-정확히 찾고있었습니다. 저는 Expression tree를 파헤쳐 야합니다. 아직 신참. @Mark, 중첩 식을 수행하는 솔루션이 있습니까? "Value"속성이있는 TSub 유형의 "Sub"속성이있는 T 유형이 있다고 가정 해 보겠습니다. 이제 "Sub.Value"문자열에 대해 Expression <Func <T, object >> 표현식을 가져오고 싶습니다.
— 사이먼 SCHEURER

4
Expression.Convert변환 property하려면 왜 필요 object합니까? 내가지고있어 Unable to cast the type 'System.String' to type 'System.Object'. LINQ to Entities only supports casting EDM primitive or enumeration types.오류를, 그리고 그것을 작동하는 것 같다 제거.
— ShuberFu

내가 올바르게 기억한다면 @Demodave. var propAsObject = Expression.Convert(property, typeof(object));그냥 사용하는 property대신에propAsObject
— ShuberFu

금. .Net Core 2.0.5에 맞게 조정되었습니다.
— Chris Amelinckx

2
있어 오류LINQ to Entities only supports casting EDM primitive or enumeration types
— 마테우스 Puwałowski

35

나는에서 답 좋아 @ 마크 파월 ,하지만 같은 @ShuberFu가 말했다, 그것은 오류를 제공합니다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");

그리고 @ 사이먼 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)
            )
        );
}
}

이런, 당신은 마이크로 소프트입니까? :) 그 Aggregate조각은 굉장합니다! Join"T.Property"와 같은 속성을 사용하기 때문에 EF Core 모델에서 생성 된 가상 뷰를 . 그렇지 않으면 후 주문 Join중 하나를 생산하는 것은 불가능하다 InvalidOperationException나 NullReferenceException. 그리고 Join대부분의 쿼리가 일정하기 때문에 AFTER를 주문해야합니다 . 뷰의 순서는 그렇지 않습니다.
— Harry

@괴롭히다. 고마워요.하지만 Aggregate조각에 대해 너무 많은 공을 들일 수는 없습니다 . 나는 그것이 Marc Gravell의 코드 와 지능적인 권고 의 조합이라고 믿습니다 . :)
— David Specht

@DavidSpecht 저는 Expression Trees를 배우는 중이 니, 이제 그들에 관한 모든 것이 나에게는 아직 흑 마법입니다. 하지만 빨리 배우고 VS의 C # 대화 형 창은 많은 도움이됩니다.
— 해리

이것을 사용하는 방법?
— 댓 응 우옌

@Dat 구엔 대신 products.OrderBy(x => x.ProductId), 당신은 사용할 수 있습니다products.OrderBy("ProductId")
— 데이비드 SPECHT

12

네, 리플렉션 외에 다른 방법은 없다고 생각합니다.

예:

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

나는 오류를 수신 "LINQ to Entities does not recognize the method 'System.Object GetValue(System.Object)' method, and this method cannot be translated into a store expression.", 어떤 생각이나 조언을 주 시겠어요?
— Florin Vîrdol

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

내 머리 꼭대기에서 정확한 구문을 기억하려고 노력하지만 그것이 옳다고 생각합니다.


2

반성이 답입니다!

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

반영된 PropertyInfo를 캐시하고, 잘못된 문자열을 확인하고, 쿼리 비교 함수를 작성하기 위해 할 수있는 일이 많이 있지만, 그 핵심은 바로이 작업입니다.


2

동적 Linq를 사용할 수 있습니다 . 이 블로그를 확인 하세요.

이 StackOverFlow 게시물 도 확인하십시오 .


이것은 나를 위해 최선의 답변입니다
— Demodave

2

동적 주문 항목에 대한 반영 확장보다 더 생산적 :

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

또한 컴파일 된 람바 (예 : Dictionary <>)를 캐시해야 할 수도 있습니다.


1

또한 Dynamic Expression 은이 문제를 해결할 수 있습니다. 런타임에 동적으로 생성 될 수있는 LINQ 식을 통해 문자열 기반 쿼리를 사용할 수 있습니다.

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

0

강력한 도구 이름 Expression을 사용할 수 있다고 생각합니다.이 경우 다음과 같이 확장 메서드로 사용합니다.

public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, string ordering, bool descending)
{
    var type = typeof(T);
    var property = type.GetProperty(ordering);
    var parameter = Expression.Parameter(type, "p");
    var propertyAccess = Expression.MakeMemberAccess(parameter, property);
    var orderByExp = Expression.Lambda(propertyAccess, parameter);
    MethodCallExpression resultExp = 
        Expression.Call(typeof(Queryable), (descending ? "OrderByDescending" : "OrderBy"), 
            new Type[] { type, property.PropertyType }, source.Expression, Expression.Quote(orderByExp));
    return (IOrderedQueryable<T>)source.Provider.CreateQuery<T>(resultExp);
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.