일반 확장 메서드 내에서 문자열 열 이름을 사용하여 IQueryable에 OrderBy를 적용하려면 어떻게해야합니까?


85
public static IQueryable<TResult> ApplySortFilter<T, TResult>(this IQueryable<T> query, string columnName)
  where T : EntityObject
{
  var param = Expression.Parameter(typeof(T), "o");
  var body = Expression.PropertyOrField(param,columnName);

  var sortExpression = Expression.Lambda(body, param);
  return query.OrderBy(sortExpression);
}

OrderBy의 유형은 sortExpression에서 유추되지 않기 때문에 런타임에 다음과 같이 지정해야합니다.

var sortExpression = Expression.Lambda<T, TSortColumn>(body, param);

또는

return query.OrderBy<T, TSortColumn>(sortExpression);

나는 이것이 가능하다고 생각하지 않지만 TSortColumn은 런타임 중에 만 결정할 수 있기 때문에 가능합니다.

이 문제를 해결할 방법이 있습니까?


확실하지 경우 당신이 찾고 있지만, 살펴하는지. 건배
joaopintocruz

@JTew 어떻게 내가 날짜별로 다음 clause..say ORDERBY ID로 2 차를 구현할 수 있습니다
SRJ

답변:


113

LINQ to SQL 프로젝트에서 비슷한 작업을 수행했습니다 (100 % 동일하지는 않지만 유사 함). 코드는 다음과 같습니다.

public static IQueryable<T> OrderBy<T>(this IQueryable<T> source, string ordering, params object[] values) {
    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), "OrderBy", new Type[] { type, property.PropertyType }, source.Expression, Expression.Quote(orderByExp));
    return source.Provider.CreateQuery<T>(resultExp);
}

우리는 실제로 제네릭을 사용하지 않았고 알려진 클래스가 있었지만 제네릭에서 작동해야합니다 (제네릭 자리 표시자를 있어야하는 곳에 넣었습니다).

편집 : 내림차순 OrderByDescending의 경우 "OrderBy"대신 전달하십시오 .

MethodCallExpression resultExp = Expression.Call(typeof(Queryable), "OrderByDescending", new Type[] { type, property.PropertyType }, source.Expression, Expression.Quote(orderByExp));

어차피 나 자신에게 답을 할당 할 수 없어요 :)
JTew

1
내림차순를 들어, "있는 OrderBy"MethodCallExpression resultExp = Expression.Call (대해서 typeof (Queryable에서), "OrderByDescending"대신 "OrderByDescending"전달, ...
게리 영어

3
이것은 단지 잘 작동하지만, 다음은 정말 좋은 깨끗한 코드 예제했다 : stackoverflow.com/questions/41244/dynamic-linq-orderby
BenSwayne

@Aaron 파월 어떻게 내가 날짜별로 다음 clause..say ORDERBY ID로 2 차를 구현할 수 있습니다
SRJ

3
매개 변수는 무엇입니까 values?
Frank Fajardo 2015

31

Dynamic Linq를 사용할 수도 있습니다.

여기 정보 http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx

여기에서 C # 다운로드 http://msdn.microsoft.com/en-us/vcsharp/bb894665.aspx

그런 다음 using Linq.Dynamic을 추가합니다. 이렇게 사용할 수있는 2 개의 추가 확장 메서드가 자동으로 생성됩니다.

return query.OrderBy("StringColumnName");

감사합니다. Phil Haack 사이트의 샘플에서 Linq.Dynamic을 보았지만 확실하지 않았습니다. 주말에 이걸 가지고 놀게 요.
JTew

다음 Systems.Linq.Dynamic.dll 여기에서 다운로드 할 수있는 대안으로 github.com/kahanu/System.Linq.Dynamic
베이 그

12

하위 속성에 대한 지원을 추가하기 위해 기능을 확장했습니다.

private static LambdaExpression GenerateSelector<TEntity>(String propertyName, out Type resultType) where TEntity : class
{
    // Create a parameter to pass into the Lambda expression (Entity => Entity.OrderByField).
    var parameter = Expression.Parameter(typeof(TEntity), "Entity");
    //  create the selector part, but support child properties
    PropertyInfo property;
    Expression propertyAccess;
    if (propertyName.Contains('.'))
    {
            // support to be sorted on child fields.
            String[] childProperties = propertyName.Split('.');
            property = typeof(TEntity).GetProperty(childProperties[0]);
            propertyAccess = Expression.MakeMemberAccess(parameter, property);
            for (int i = 1; i < childProperties.Length; i++)
            {
                    property = property.PropertyType.GetProperty(childProperties[i]);
                    propertyAccess = Expression.MakeMemberAccess(propertyAccess, property);
            }
    }
    else
    {
            property = typeof(TEntity).GetProperty(propertyName);
            propertyAccess = Expression.MakeMemberAccess(parameter, property);
    }
    resultType = property.PropertyType;                     
    // Create the order by expression.
    return Expression.Lambda(propertyAccess, parameter);
}

private static MethodCallExpression GenerateMethodCall<TEntity>(IQueryable<TEntity> source, string methodName, String fieldName) where TEntity : class
{
    Type type = typeof(TEntity);
    Type selectorResultType;
    LambdaExpression selector = GenerateSelector<TEntity>(fieldName, out selectorResultType);
    MethodCallExpression resultExp = Expression.Call(typeof(Queryable), methodName,
                                    new Type[] { type, selectorResultType },
                                    source.Expression, Expression.Quote(selector));
    return resultExp;
}

다음과 같은 기능을 사용할 수 있습니다.

GenerateMethodCall<TEntity>(source, "OrderByDescending", fieldName);

1
너는 나의 영웅이야 !!
Sebastián Guerrero

1
꼭 똑똑한 사람들을 사랑
로드 존슨에게

나는이 코드를 시도해 보았고 하나의 Child와 함께 작동하지만 둘 이상에서는 작동하지 않습니다.
Robbert Raats 2019

8

OrderBy의 확장 방법에 대한 아이디어를 사용했습니다. 그러나 "다 대다"의 경우 오류가 발생합니다. 예를 들어 Site, Customer 및 Customer_site 테이블이 있습니다. 주어진 사이트에 대해 고객 이름과 OrderBy 확장 (고객이 탐색 속성 인 "site.customer"를 전달할 때)별로 정렬하려고합니다. 줄에 오류가 발생합니다. propertyAccess = Expression.MakeMemberAccess (propertyAccess, property);

이것은 내가 사용하는 것입니다 (일부 개선 사항 :-)).

public static IQueryable<TEntity> OrderBy<TEntity>(this IQueryable<TEntity> source, string orderByValues) where TEntity : class
{
  IQueryable<TEntity> returnValue = null;

  string orderPair = orderByValues.Trim().Split(',')[0];
  string command = orderPair.ToUpper().Contains("DESC") ? "OrderByDescending" : "OrderBy";

  var type = typeof(TEntity);
  var parameter = Expression.Parameter(type, "p");

  string propertyName = (orderPair.Split(' ')[0]).Trim();

  System.Reflection.PropertyInfo property;
  MemberExpression propertyAccess;

  if (propertyName.Contains('.'))
  {
    // support to be sorted on child fields. 
    String[] childProperties = propertyName.Split('.');
    property = typeof(TEntity).GetProperty(childProperties[0]);
    propertyAccess = Expression.MakeMemberAccess(parameter, property);

    for (int i = 1; i < childProperties.Length; i++)
    {
      Type t = property.PropertyType;
      if (!t.IsGenericType)
      {
        property = t.GetProperty(childProperties[i]);
      }
      else
      {
        property = t.GetGenericArguments().First().GetProperty(childProperties[i]);
      }

      propertyAccess = Expression.MakeMemberAccess(propertyAccess, property);
    }
  }
  else
  {
    property = type.GetProperty(propertyName);
    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));

  returnValue = source.Provider.CreateQuery<TEntity>(resultExpression);

  if (orderByValues.Trim().Split(',').Count() > 1)
  {
    // remove first item
    string newSearchForWords = orderByValues.ToString().Remove(0, orderByValues.ToString().IndexOf(',') + 1);
    return source.OrderBy(newSearchForWords);
  }

  return returnValue;
}

문안 인사

슬로 보단


6

이것이 다음 을 확인하는 방법 인 것 같습니다 .

// ***** OrderBy(company => company) *****
// Create an expression tree that represents the expression
// 'whereCallExpression.OrderBy(company => company)'
MethodCallExpression orderByCallExpression = Expression.Call(
    typeof(Queryable),
    "OrderBy",
    new Type[] { queryableData.ElementType, queryableData.ElementType },
    whereCallExpression,
    Expression.Lambda<Func<string, string>>(pe, new ParameterExpression[] { pe }));
// ***** End OrderBy *****

1
젠장, 34 초 뒤에! : P
Aaron Powell

3

"System.Linq.Dynamic"패키지를 추가 할 수 있다면 복잡하지 않고 Too easy,

NuGet 패키지 관리자에서 fisrt insatll 패키지 "System.Linq.Dynamic"을 설치 한 다음 필요에 따라 다음과 같이 시도하십시오.

전의:

public IQueryable<TEntity> GetWithInclude(Expression<Func<TEntity, bool>> predicate,
                    List<string> sortBy, int pageNo, int pageSize = 12, params string[] include)
        {
            try
            {
                var numberOfRecordsToSkip = pageNo * pageSize;
                var dynamic = DbSet.AsQueryable();

                foreach (var s in include)
                {
                    dynamic.Include(s);
                }
                 return dynamic.OrderBy("CreatedDate").Skip(numberOfRecordsToSkip).Take(pageSize);


            }
            catch (Exception e)
            {
                throw new Exception(e.Message);
            }
        }

이것이 도움이되기를 바랍니다.


2

이 코드를 약간 수정했습니다 : https://stackoverflow.com/a/1670085/5852630

이 코드는 순차 정렬과 함께 작동합니다. 먼저 "OrderBy"를 실행 한 다음 "ThenBy"( "OrderBy"가 아님!)

public static IQueryable<TEntity> OrderBy<TEntity>(this IQueryable<TEntity> source, string orderByValues) where TEntity : class
{
    IQueryable<TEntity> returnValue = null;

    string[] orderPairs = orderByValues.Trim().Split(',');

    Expression resultExpression = source.Expression;

    string strAsc = "OrderBy";
    string strDesc = "OrderByDescending";

    foreach (string orderPair in orderPairs)
    {
        if (string.IsNullOrWhiteSpace(orderPair))
            continue;

        string[] orderPairArr = orderPair.Trim().Split(' ');

        string propertyName = orderPairArr[0].Trim();
        string orderNarrow = orderPairArr.Length > 1 ? orderPairArr[1].Trim() : string.Empty;

        string command = orderNarrow.ToUpper().Contains("DESC") ? strDesc : strAsc;

        Type type = typeof(TEntity);
        ParameterExpression parameter = Expression.Parameter(type, "p");

        System.Reflection.PropertyInfo property;
        Expression propertyAccess;

        if (propertyName.Contains('.'))
        {
            // support to be sorted on child fields. 
            String[] childProperties = propertyName.Split('.');
            property = typeof(TEntity).GetProperty(childProperties[0]);
            propertyAccess = Expression.MakeMemberAccess(parameter, property);

            for (int i = 1; i < childProperties.Length; i++)
            {
                Type t = property.PropertyType;
                if (!t.IsGenericType)
                {
                    property = t.GetProperty(childProperties[i]);
                }
                else
                {
                    property = t.GetGenericArguments().First().GetProperty(childProperties[i]);
                }

                propertyAccess = Expression.MakeMemberAccess(propertyAccess, property);
            }
        }
        else
        {
            property = type.GetProperty(propertyName);
            propertyAccess = Expression.MakeMemberAccess(parameter, property);
        }

        if (property.PropertyType == typeof(object))
        {
            propertyAccess = Expression.Call(propertyAccess, "ToString", null);
        }

        LambdaExpression orderByExpression = Expression.Lambda(propertyAccess, parameter);

        resultExpression = Expression.Call(typeof(Queryable), command, new Type[] { type, property.PropertyType == typeof(object) ? typeof(string) : property.PropertyType },
            resultExpression, Expression.Quote(orderByExpression));

        strAsc = "ThenBy";
        strDesc = "ThenByDescending";
    }

    returnValue = source.Provider.CreateQuery<TEntity>(resultExpression);

    return returnValue;
}

0

@Davy Landman 의 답변 (확장 방법을 원했습니다) 에서 내 적응이 있으며 약간 단순화했습니다.

public static IQueryable<T> SortBy<T>(this IQueryable<T> source, 
                                      String propertyName, 
                                      WebControls.SortDirection direction)
    {
        if (source == null) throw new ArgumentNullException("source");
        if (String.IsNullOrEmpty(propertyName)) return source;

        // Create a parameter to pass into the Lambda expression
        //(Entity => Entity.OrderByField).
        var parameter = Expression.Parameter(typeof(T), "Entity");

        //  create the selector part, but support child properties (it works without . too)
        String[] childProperties = propertyName.Split('.');
        MemberExpression property = Expression.Property(parameter, childProperties[0]);
        for (int i = 1; i < childProperties.Length; i++)
        {
            property = Expression.Property(property, childProperties[i]);
        }

        LambdaExpression selector = Expression.Lambda(property, parameter);

        string methodName = (direction > 0) ? "OrderByDescending" : "OrderBy";

        MethodCallExpression resultExp = Expression.Call(typeof(Queryable), methodName,
                                        new Type[] { source.ElementType, property.Type },
                                        source.Expression, Expression.Quote(selector));

        return source.Provider.CreateQuery<T>(resultExp);
    }

다음과 같이 사용할 수 있습니다.

gridview1.DataSource = DbContext.TB_CARS.SortBy("model", SortDirection.Descending);
//OR
gridview1.DataSource = DbContext.TB_CARS.SortBy("owner.first_name", 0);
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.