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


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 AnderssonMarc 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중 하나를 생산하는 것은 불가능하다 InvalidOperationExceptionNullReferenceException. 그리고 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.