Đơn hàng LINQ động trên IEn Countable <T> / IQueryable <T>


668

Tôi đã tìm thấy một ví dụ trong Ví dụ VS2008 cho Dynamic LINQ cho phép bạn sử dụng chuỗi giống như sql (ví dụ OrderBy("Name, Age DESC"))để đặt hàng. Thật không may, phương thức này chỉ hoạt động trên IQueryable<T>. Có cách nào để có được chức năng này IEnumerable<T>không?


1
Câu trả lời tốt nhất cho đến ngày hôm nay, theo ý kiến ​​của tôi: thư viện System.Linq.Docate.Core .
Shahin Dohan

Câu trả lời:


903

Chỉ tình cờ gặp lại ...

Để làm điều này mà không cần thư viện LINQ động, bạn chỉ cần mã như dưới đây. Điều này bao gồm hầu hết các kịch bản phổ biến bao gồm các thuộc tính lồng nhau.

Để làm cho nó hoạt động với IEnumerable<T>bạn có thể thêm một số phương thức trình bao bọc đi qua AsQueryable- nhưng mã dưới đây là Expressionlogic cốt lõi cần thiết.

public static IOrderedQueryable<T> OrderBy<T>(
    this IQueryable<T> source, 
    string property)
{
    return ApplyOrder<T>(source, property, "OrderBy");
}

public static IOrderedQueryable<T> OrderByDescending<T>(
    this IQueryable<T> source, 
    string property)
{
    return ApplyOrder<T>(source, property, "OrderByDescending");
}

public static IOrderedQueryable<T> ThenBy<T>(
    this IOrderedQueryable<T> source, 
    string property)
{
    return ApplyOrder<T>(source, property, "ThenBy");
}

public static IOrderedQueryable<T> ThenByDescending<T>(
    this IOrderedQueryable<T> source, 
    string property)
{
    return ApplyOrder<T>(source, property, "ThenByDescending");
}

static IOrderedQueryable<T> ApplyOrder<T>(
    IQueryable<T> source, 
    string property, 
    string methodName) 
{
    string[] props = property.Split('.');
    Type type = typeof(T);
    ParameterExpression arg = Expression.Parameter(type, "x");
    Expression expr = arg;
    foreach(string prop in props) {
        // use reflection (not ComponentModel) to mirror LINQ
        PropertyInfo pi = type.GetProperty(prop);
        expr = Expression.Property(expr, pi);
        type = pi.PropertyType;
    }
    Type delegateType = typeof(Func<,>).MakeGenericType(typeof(T), type);
    LambdaExpression lambda = Expression.Lambda(delegateType, expr, arg);

    object result = typeof(Queryable).GetMethods().Single(
            method => method.Name == methodName
                    && method.IsGenericMethodDefinition
                    && method.GetGenericArguments().Length == 2
                    && method.GetParameters().Length == 2)
            .MakeGenericMethod(typeof(T), type)
            .Invoke(null, new object[] {source, lambda});
    return (IOrderedQueryable<T>)result;
}

Chỉnh sửa: sẽ thú vị hơn nếu bạn muốn kết hợp điều đó với dynamic- mặc dù lưu ý dynamicchỉ áp dụng cho LINQ-to-Object (cây biểu thức cho ORM, v.v. không thực sự đại diện cho dynamiccác truy vấn - MemberExpressionkhông hỗ trợ nó). Nhưng đây là một cách để làm điều đó với LINQ-to-Object. Lưu ý rằng sự lựa chọn Hashtablelà do ngữ nghĩa khóa thuận lợi:

using Microsoft.CSharp.RuntimeBinder;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Runtime.CompilerServices;
static class Program
{
    private static class AccessorCache
    {
        private static readonly Hashtable accessors = new Hashtable();

        private static readonly Hashtable callSites = new Hashtable();

        private static CallSite<Func<CallSite, object, object>> GetCallSiteLocked(
            string name) 
        {
            var callSite = (CallSite<Func<CallSite, object, object>>)callSites[name];
            if(callSite == null)
            {
                callSites[name] = callSite = CallSite<Func<CallSite, object, object>>
                    .Create(Binder.GetMember(
                                CSharpBinderFlags.None, 
                                name, 
                                typeof(AccessorCache),
                                new CSharpArgumentInfo[] { 
                                    CSharpArgumentInfo.Create(
                                        CSharpArgumentInfoFlags.None, 
                                        null) 
                                }));
            }
            return callSite;
        }

        internal static Func<dynamic,object> GetAccessor(string name)
        {
            Func<dynamic, object> accessor = (Func<dynamic, object>)accessors[name];
            if (accessor == null)
            {
                lock (accessors )
                {
                    accessor = (Func<dynamic, object>)accessors[name];
                    if (accessor == null)
                    {
                        if(name.IndexOf('.') >= 0) {
                            string[] props = name.Split('.');
                            CallSite<Func<CallSite, object, object>>[] arr 
                                = Array.ConvertAll(props, GetCallSiteLocked);
                            accessor = target =>
                            {
                                object val = (object)target;
                                for (int i = 0; i < arr.Length; i++)
                                {
                                    var cs = arr[i];
                                    val = cs.Target(cs, val);
                                }
                                return val;
                            };
                        } else {
                            var callSite = GetCallSiteLocked(name);
                            accessor = target =>
                            {
                                return callSite.Target(callSite, (object)target);
                            };
                        }
                        accessors[name] = accessor;
                    }
                }
            }
            return accessor;
        }
    }

    public static IOrderedEnumerable<dynamic> OrderBy(
        this IEnumerable<dynamic> source, 
        string property)
    {
        return Enumerable.OrderBy<dynamic, object>(
            source, 
            AccessorCache.GetAccessor(property), 
            Comparer<object>.Default);
    }

    public static IOrderedEnumerable<dynamic> OrderByDescending(
        this IEnumerable<dynamic> source, 
        string property)
    {
        return Enumerable.OrderByDescending<dynamic, object>(
            source, 
            AccessorCache.GetAccessor(property), 
            Comparer<object>.Default);
    }

    public static IOrderedEnumerable<dynamic> ThenBy(
        this IOrderedEnumerable<dynamic> source, 
        string property)
    {
        return Enumerable.ThenBy<dynamic, object>(
            source, 
            AccessorCache.GetAccessor(property), 
            Comparer<object>.Default);
    }

    public static IOrderedEnumerable<dynamic> ThenByDescending(
        this IOrderedEnumerable<dynamic> source, 
        string property)
    {
        return Enumerable.ThenByDescending<dynamic, object>(
            source, 
            AccessorCache.GetAccessor(property), 
            Comparer<object>.Default);
    }

    static void Main()
    {
        dynamic a = new ExpandoObject(), 
                b = new ExpandoObject(), 
                c = new ExpandoObject();
        a.X = "abc";
        b.X = "ghi";
        c.X = "def";
        dynamic[] data = new[] { 
            new { Y = a },
            new { Y = b }, 
            new { Y = c } 
        };

        var ordered = data.OrderByDescending("Y.X").ToArray();
        foreach (var obj in ordered)
        {
            Console.WriteLine(obj.Y.X);
        }
    }
}

109
Đoạn mã chết tiệt nhất tôi từng thấy :) Chỉ cần giải quyết một triệu vấn đề trong dự án của tôi :)
sajidnizami

4
@Dave - bạn cần bắt đầu với IQueryable<T>, vì vậy nếu bạn có một cái gì đó như List<T>(đó là IEnumerable<T>) bạn có thể cần sử dụng AsQueryable()- ví dụvar sorted = someList.AsQueryable().OrderBy("Foo.Bar");
Marc Gravell

7
Bạn đã thấy điều này ... nó có thể giúp một số người ... stackoverflow.com/questions/557819/NH đó là một giải pháp được đánh máy mạnh mẽ hơn.
anthonyv

28
@MGOwen bạn dường như hiểu sai bản chất của mã. 40 dòng là như nhau cho dù đó là 40 dòng mà bạn đặt ở đâu đó trong dự án của bạn, hoặc nếu các dòng đó đến (được biên dịch trước, hoặc dưới dạng nguồn) trong một thư viện bên ngoài. Sẽ rất tuyệt vời nếu tôi đã liên kết, vào tháng 10 năm 2008 với một thư viện trên nuget tồn tại từ ngày 11 tháng 12 (không phải vì nuget không tồn tại sau đó), nhưng "cơ bản là nó đang làm gì" giống nhau. Ngoài ra, bạn sử dụng cụm từ "giải pháp thực tế" như thể có một số tuyến đường duy nhất được xác định rõ ràng cho mọi câu hỏi mã hóa: không có.
Marc Gravell

5
@MGOwen btw, lib bên ngoài là 2296 dòng mã (không bao gồm HộiInfo.cs); Điều gì làm cho 40 dòng ở đây trông khá hợp lý
Marc Gravell

231

Quá dễ dàng mà không có bất kỳ biến chứng:

  1. Thêm using System.Linq.Dynamic;ở trên cùng.
  2. Sử dụng vehicles = vehicles.AsQueryable().OrderBy("Make ASC, Year DESC").ToList();

11
và bạn đã lấy System.Linq.Dynamictừ đâu?
Mất trí nhớ

1
Hoạt động khi sử dụng linq với MongoDB là tốt.
soupy1976

32
Câu trả lời được chấp nhận có thể là câu trả lời đúng trong năm 2008 nhưng hiện tại đây là câu trả lời dễ nhất, đúng nhất hiện nay.
EL MOJO

1
Điều này thực sự tốt và xử lý đơn giản, rất nhiều phức tạp trong nội bộ, yêu thích nó
Mrinal Kamboj

5
Đối với những người trong "tương lai", nếu bạn đang sử dụng lõi dotnet, hãy sử dụng: nuget.org/packages/System.Linq.Docate.Core
Rafael Merlin

78

Tôi tìm thấy câu trả lời. Tôi có thể sử dụng .AsQueryable<>()phương thức tiện ích mở rộng để chuyển đổi danh sách của mình thành IQueryable, sau đó chạy thứ tự động bằng cách chống lại nó.


52
Vui lòng cung cấp một ví dụ cho phần còn lại của chúng tôi.
MGOwen

54

Chỉ vấp phải câu hỏi này.

Sử dụng triển khai ApplyOrder của Marc từ trên xuống, tôi đã tát một phương thức Tiện ích mở rộng xử lý các chuỗi giống như SQL như:

list.OrderBy("MyProperty DESC, MyOtherProperty ASC");

Chi tiết có thể được tìm thấy ở đây: http://aonnull.blogspot.com/2010/08/dynamic-sql-like-linq-orderby-extension.html


1
Công cụ tuyệt vời, chỉ cần thêm một sửa đổi như sau để làm cho trường hợp tên thuộc tính không nhạy cảm: PropertyInfo pi = type.GetProperty (prop, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
Mrinal Kamboj

43

Tôi đoán nó sẽ hoạt động để sử dụng sự phản chiếu để có được bất kỳ tài sản nào bạn muốn sắp xếp:

IEnumerable<T> myEnumerables
var query=from enumerable in myenumerables
          where some criteria
          orderby GetPropertyValue(enumerable,"SomeProperty")
          select enumerable

private static object GetPropertyValue(object obj, string property)
{
    System.Reflection.PropertyInfo propertyInfo=obj.GetType().GetProperty(property);
    return propertyInfo.GetValue(obj, null);
}

Lưu ý rằng việc sử dụng phản xạ chậm hơn đáng kể so với truy cập trực tiếp vào tài sản, do đó hiệu suất sẽ phải được điều tra.


Điều này thậm chí làm việc? orderby không muốn một giá trị nhưng một bộ chọn lamba / đại biểu (Func <TSource, TKey> keySelector) ..
Davy Landman

2
Tôi đã thử ví dụ này trước khi đăng nó, và vâng, nó hoạt động.
Kjetil Watnedal

3
+1 Đây chính xác là những gì tôi đang tìm kiếm! Điều này sẽ làm việc tuyệt vời cho các vấn đề sắp xếp trang đơn giản.
Andrew Siemer

Điều này đã không làm việc cho tôi. Tui bỏ lỡ điều gì vậy? "Một số chương trình" nên là gì. Tôi đã thử cung cấp tên thuộc tính cũng như property.GetType (). Tôi có IQueryable <> và không IEn Countable <>
Người dùng SO

2
@Alex Shkor: Làm thế nào bạn có nghĩa vụ sắp xếp các yếu tố mà không cần nhìn vào tất cả các yếu tố? Tuy nhiên, có những giải pháp tốt hơn trong các câu trả lời khác.
Kjetil Watnedal

19

Chỉ cần xây dựng trên những gì người khác đã nói. Tôi thấy rằng sau đây hoạt động khá tốt.

public static IEnumerable<T> OrderBy<T>(this IEnumerable<T> input, string queryString)
{
    if (string.IsNullOrEmpty(queryString))
        return input;

    int i = 0;
    foreach (string propname in queryString.Split(','))
    {
        var subContent = propname.Split('|');
        if (Convert.ToInt32(subContent[1].Trim()) == 0)
        {
            if (i == 0)
                input = input.OrderBy(x => GetPropertyValue(x, subContent[0].Trim()));
            else
                input = ((IOrderedEnumerable<T>)input).ThenBy(x => GetPropertyValue(x, subContent[0].Trim()));
        }
        else
        {
            if (i == 0)
                input = input.OrderByDescending(x => GetPropertyValue(x, subContent[0].Trim()));
            else
                input = ((IOrderedEnumerable<T>)input).ThenByDescending(x => GetPropertyValue(x, subContent[0].Trim()));
        }
        i++;
    }

    return input;
}

12

Tôi đã vấp phải câu hỏi này khi tìm kiếm nhiều mệnh đề Linq và có lẽ đây là điều mà tác giả đang tìm kiếm

Đây là cách để làm điều đó:

var query = pets.OrderBy(pet => pet.Name).ThenByDescending(pet => pet.Age);    

5
+1 đã hủy bỏ phiếu bầu do thiếu giải thích. Tôi cũng nghĩ rằng tác giả có thể đã quan tâm đến nhiều thứ tự. Ngay cả khi năng động từ khóa, không có lý do gì để bỏ phiếu.
Jason Kleban

11

Tôi đã cố gắng làm điều này nhưng gặp vấn đề với giải pháp của Kjetil Watnedal vì tôi không sử dụng cú pháp linq nội tuyến - tôi thích cú pháp kiểu phương thức. Vấn đề cụ thể của tôi là trong việc cố gắng thực hiện sắp xếp động bằng cách sử dụng một tùy chỉnh IComparer.

Giải pháp của tôi đã kết thúc như thế này:

Đưa ra một truy vấn IQueryable như vậy:

List<DATA__Security__Team> teams = TeamManager.GetTeams();
var query = teams.Where(team => team.ID < 10).AsQueryable();

Và đưa ra một đối số trường sắp xếp thời gian chạy:

string SortField; // Set at run-time to "Name"

OrderBy năng động trông giống như vậy:

query = query.OrderBy(item => item.GetReflectedPropertyValue(SortField));

Và đó là sử dụng một phương thức trợ giúp nhỏ có tên GetReflectedPropertyValue ():

public static string GetReflectedPropertyValue(this object subject, string field)
{
    object reflectedValue = subject.GetType().GetProperty(field).GetValue(subject, null);
    return reflectedValue != null ? reflectedValue.ToString() : "";
}

Một điều cuối cùng - tôi đã đề cập rằng tôi muốn OrderBysử dụng tùy chỉnh IComparer- bởi vì tôi muốn thực hiện sắp xếp tự nhiên .

Để làm điều đó, tôi chỉ cần thay đổi OrderBythành:

query = query.OrderBy(item => item.GetReflectedPropertyValue(SortField), new NaturalSortComparer<string>());

Xem bài đăng này cho mã cho NaturalSortComparer().


5

Sử dụng năng động linq

chỉ cần thêm using System.Linq.Dynamic;

Và sử dụng nó như thế này để đặt hàng tất cả các cột của bạn:

string sortTypeStr = "ASC"; // or DESC
string SortColumnName = "Age"; // Your column name
query = query.OrderBy($"{SortColumnName} {sortTypeStr}");

4

Bạn có thể thêm nó:

public static IEnumerable<T> OrderBy( this IEnumerable<T> input, string queryString) {
    //parse the string into property names
    //Use reflection to get and sort by properties
    //something like

    foreach( string propname in queryString.Split(','))
        input.OrderBy( x => GetPropertyValue( x, propname ) );

    // I used Kjetil Watnedal's reflection example
}

Các GetPropertyValuechức năng là từ câu trả lời Kjetil Watnedal của

Vấn đề sẽ là tại sao? Bất kỳ loại nào như vậy sẽ ném ngoại lệ vào thời gian chạy, thay vì thời gian biên dịch (như câu trả lời của D2VIANT).

Nếu bạn đang giao dịch với Linq sang Sql và orderby là một cây biểu thức, nó sẽ được chuyển đổi thành SQL để thực thi.


GetPropertyValue mehotod sẽ được thực thi cho tất cả các yếu tố, đó là giải pháp tồi.
Alex Shkor

2
OrderBykhông duy trì trật tự trước đó !!
Amir Ismail

4

Đây là một cái gì đó khác tôi thấy thú vị. Nếu nguồn của bạn là DataTable, bạn có thể sử dụng sắp xếp động mà không cần sử dụng Dynamic Linq

DataTable orders = dataSet.Tables["SalesOrderHeader"];
EnumerableRowCollection<DataRow> query = from order in orders.AsEnumerable()
                                         orderby order.Field<DateTime>("OrderDate")
                                         select order;
DataView view = query.AsDataView();
bindingSource1.DataSource = view;

tham khảo: http://msdn.microsoft.com/en-us/l Library / bb669083.aspx (Sử dụng DataSetExtensions)

Đây là một cách khác để làm điều đó bằng cách chuyển đổi nó thành DataView:

DataTable contacts = dataSet.Tables["Contact"];    
DataView view = contacts.AsDataView();    
view.Sort = "LastName desc, FirstName asc";    
bindingSource1.DataSource = view;
dataGridView1.AutoResizeColumns();

4

Nhờ Maarten ( Truy vấn bộ sưu tập bằng đối tượng PropertyInfo trong LINQ ) Tôi đã nhận được giải pháp này:

myList.OrderByDescending(x => myPropertyInfo.GetValue(x, null)).ToList();

Trong trường hợp của tôi, tôi đã làm việc trên "CộtHeadMouseClick" (WindowsForm) vì vậy chỉ cần tìm thấy Cột cụ thể được nhấn và thuộc tính tương ứng của nó làInInfo:

foreach (PropertyInfo column in (new Process()).GetType().GetProperties())
{
    if (column.Name == dgvProcessList.Columns[e.ColumnIndex].Name)
    {}
}

HOẶC LÀ

PropertyInfo column = (new Process()).GetType().GetProperties().Where(x => x.Name == dgvProcessList.Columns[e.ColumnIndex].Name).First();

(hãy chắc chắn có Tên cột của bạn khớp với Thuộc tính đối tượng)

Chúc mừng


4

Sau rất nhiều tìm kiếm, điều này làm việc cho tôi:

public static IEnumerable<TEntity> OrderBy<TEntity>(this IEnumerable<TEntity> source, 
                                                    string orderByProperty, bool desc)
{
    string command = desc ? "OrderByDescending" : "OrderBy";
    var type = typeof(TEntity);
    var property = type.GetProperty(orderByProperty);
    var parameter = Expression.Parameter(type, "p");
    var propertyAccess = Expression.MakeMemberAccess(parameter, property);
    var orderByExpression = Expression.Lambda(propertyAccess, parameter);
    var resultExpression = Expression.Call(typeof(Queryable), command, 
                                           new[] { type, property.PropertyType },
                                           source.AsQueryable().Expression, 
                                           Expression.Quote(orderByExpression));
    return source.AsQueryable().Provider.CreateQuery<TEntity>(resultExpression);
}

4

Bạn có thể chuyển đổi IEnumerable thành IQueryable.

items = items.AsQueryable().OrderBy("Name ASC");

3

Một giải pháp thay thế sử dụng lớp / giao diện sau. Nó không thực sự năng động, nhưng nó hoạt động.

public interface IID
{
    int ID
    {
        get; set;
    }
}

public static class Utils
{
    public static int GetID<T>(ObjectQuery<T> items) where T:EntityObject, IID
    {
        if (items.Count() == 0) return 1;
        return items.OrderByDescending(u => u.ID).FirstOrDefault().ID + 1;
    }
}

2

Câu trả lời này là phản hồi cho các bình luận cần một ví dụ cho giải pháp được cung cấp bởi @John Sheehan - Runscope

Vui lòng cung cấp một ví dụ cho phần còn lại của chúng tôi.

trong DAL (Lớp truy cập dữ liệu),

Phiên bản IEnumerable:

  public  IEnumerable<Order> GetOrders()
    {
      // i use Dapper to return IEnumerable<T> using Query<T>
      //.. do stuff
      return  orders  // IEnumerable<Order>
  }

Phiên bản IQueryable

  public IQueryable<Order> GetOrdersAsQuerable()
    {
        IEnumerable<Order> qry= GetOrders();
        //use the built-in extension method  AsQueryable in  System.Linq namespace
        return qry.AsQueryable();            
    }

Bây giờ bạn có thể sử dụng phiên bản IQueryable để liên kết, ví dụ GridView trong Asp.net và lợi ích cho việc sắp xếp (bạn không thể sắp xếp bằng phiên bản IEnumerable)

Tôi đã sử dụng Dapper dưới dạng ORM và xây dựng phiên bản IQueryable và sử dụng sắp xếp trong GridView trong asp.net rất dễ dàng.


2

Cài đặt công cụ động đầu tiên -> Trình quản lý gói NuGet -> Bảng điều khiển quản lý gói

install-package System.Linq.Dynamic

Thêm không gian tên using System.Linq.Dynamic;

Bây giờ bạn có thể sử dụng OrderBy("Name, Age DESC")


Làm cách nào tôi có thể sử dụng nó với sắp xếp thuộc tính bên trong - như OrderBy ("Branch.BranchName", "Giảm dần")
devC

Điều này làm việc cho tôi. Có lẽ bởi vì câu hỏi đã 10 tuổi và phương pháp dễ dàng hơn này chỉ xuất hiện sau đó.
cá kosherjellyfish

1

Bạn có thể sử dụng điều này:

        public List<Book> Books(string orderField, bool desc, int skip, int take)
{
    var propertyInfo = typeof(Book).GetProperty(orderField);

    return _context.Books
        .Where(...)
        .OrderBy(p => !desc ? propertyInfo.GetValue(p, null) : 0)
        .ThenByDescending(p => desc ? propertyInfo.GetValue(p, null) : 0)
        .Skip(skip)
        .Take(take)
        .ToList();
}

Một vài năm sau và tôi vấp phải điều này; Điều này làm việc cho tôi, giống như một giấc mơ. Tôi có sắp xếp động trên 1 đến 3 thuộc tính và điều này hoạt động như một giấc mơ. Dễ dàng thực hiện và rắc rối miễn phí.
Bazïnga

0

Chuyển đổi danh sách sang IEnumerable hoặc Iquerable, thêm bằng cách sử dụng không gian tên System.LINQ.Docate, sau đó bạn có thể đề cập đến các tên thuộc tính trong chuỗi được phân tách bằng dấu phẩy thành Phương thức OrderBy theo mặc định từ System.LINQ.Docate.


-3
var result1 = lst.OrderBy(a=>a.Name);// for ascending order. 
 var result1 = lst.OrderByDescending(a=>a.Name);// for desc order. 
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.