Làm cách nào để áp dụng OrderBy trên IQueryable bằng cách sử dụng tên cột chuỗi trong phương thức mở rộng chung?


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

Vì kiểu cho OrderBy không được suy ra từ sortExpression, nên tôi cần chỉ định nó như thế này tại thời điểm chạy:

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

Hoặc là

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

Tuy nhiên, tôi không nghĩ rằng điều này là có thể vì TSortColumn chỉ có thể được xác định trong thời gian chạy.

Có cách nào để giái quyết vấn đề này không?


Không chắc liệu đây có phải là thứ bạn đang tìm kiếm hay không, nhưng hãy xem. Cheers
joaopintocruz

@JTew Làm cách nào để tôi có thể thực hiện đơn đặt hàng thứ hai theo mệnh đề..nói theo đơn đặt hàng sau đó theo ngày
SRJ

Câu trả lời:


113

Chúng tôi đã làm điều gì đó tương tự (không giống nhau 100%, nhưng tương tự) trong một dự án LINQ to SQL. Đây là mã:

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

Chúng tôi không thực sự sử dụng chung chung, chúng tôi có một lớp đã biết, nhưng nó sẽ hoạt động trên một chung (tôi đã đặt trình giữ chỗ chung ở vị trí cần thiết).

Chỉnh sửa: Đối với thứ tự giảm dần, hãy chuyển vào OrderByDescendingthay vì "OrderBy":

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

Heh no prob, tôi không thể chỉ định câu trả lời cho chính mình :)
JTew

1
cho thứ tự giảm dần, vượt qua trong "OrderByDescending" thay vì "OrderBy" MethodCallExpression resultExp = Expression.Call (typeof (queryable), "OrderByDescending", ...
Garry tiếng Anh

3
Đây chỉ làm việc tốt, nhưng sau đây là chỉ là một thực sự sạch đẹp mã ví dụ: stackoverflow.com/questions/41244/dynamic-linq-orderby
BenSwayne

@Aaron Powell Làm thế nào tôi có thể thực hiện một trật tự thứ hai bởi orderby id clause..say sau đó theo ngày
SRJ

3
Tham số valuesđể làm gì?
Frank Fajardo

31

Bạn cũng có thể sử dụng Dynamic Linq

Thông tin tại đây http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx

Tải xuống C # tại đây http://msdn.microsoft.com/en-us/vcsharp/bb894665.aspx

Sau đó, chỉ cần thêm bằng cách sử dụng Linq.Dynamic; và bạn tự động nhận được 2 phương thức mở rộng bổ sung có thể được sử dụng như thế này

return query.OrderBy("StringColumnName");

Cảm ơn, tôi đã xem Linq.Dynamic về một mẫu tại trang web của Phil Haack nhưng không chắc về nó. Tôi sẽ chơi với cái này vào cuối tuần.
JTew

Thay vào đó, có thể tải xuống Systems.Linq.Dynamic.dll từ đây: github.com/kahanu/System.Linq.Dynamic
Baig Ngày

12

Tôi đã mở rộng các chức năng của bạn để thêm hỗ trợ cho Thuộc tính con.

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;
}

Bạn có thể sử dụng các chức năng này như:

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

1
Bạn là người hùng của tôi !!
Sebastián Guerrero

1
phải yêu những người thông minh
Rod Johnson

Tôi đã thử mã này và nó hoạt động với một Con, nhưng không hoạt động với nhiều hơn một, ví dụ: nó hoạt động với sắp xếp trên x.String và x.Object.String, nhưng không hoạt động với sắp xếp trên x.Object.Object.String.
Robbert Raats

8

Tôi đã sử dụng ý tưởng của bạn cho phương pháp mở rộng cho OrderBy. Nhưng trong trường hợp "nhiều đến nhiều", tôi đang gặp lỗi. Ví dụ, bạn có bảng Site, Customer và Customer_site. Đối với Trang web nhất định, tôi muốn sắp xếp theo tên khách hàng và trong phần mở rộng OrderBy (khi tôi chuyển "site.customer" nơi khách hàng là thuộc tính điều hướng) Tôi gặp lỗi ở dòng: propertyAccess = Expression.MakeMemberAccess (propertyAccess, thuộc tính);

Đây là những gì tôi sử dụng (với một số cải tiến :-)):

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;
}

Trân trọng

Slobodan


6

Có vẻ như đây là cách để làm điều đó, bây giờ để xác minh rằng:

// ***** 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
chết tiệt, chậm 34 giây! : P
Aaron Powell,

3

Nếu bạn có thể thêm gói "System.Linq.Dynamic" thì, Quá dễ dàng mà không có bất kỳ phức tạp nào,

Gói fisrt insatll "System.Linq.Dynamic" từ trình quản lý gói NuGet sau đó thử như bên dưới theo nhu cầu của bạn,

Ví dụ:

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

Hy vọng điều này sẽ giúp


2

Tôi đã sửa mã này một chút: https://stackoverflow.com/a/1670085/5852630

Mã này hoạt động với sắp xếp tuần tự: đầu tiên thực hiện "OrderBy", sau đó "ThenBy" (Không phải "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

Đây là bản chuyển thể của tôi từ câu trả lời của @Davy Landman (tôi muốn có một phương pháp mở rộng) và tôi đã đơn giản hóa một chút.

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

Nó có thể được sử dụng như thế này:

gridview1.DataSource = DbContext.TB_CARS.SortBy("model", SortDirection.Descending);
//OR
gridview1.DataSource = DbContext.TB_CARS.SortBy("owner.first_name", 0);
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.