سؤال

لقد وجدت مثالا في أمثلة VS2008 لـ Dynamic LINQ الذي يسمح لك باستخدام سلسلة تشبه SQL (على سبيل المثال. OrderBy("Name, Age DESC")) للطلب.لسوء الحظ، الطريقة المضمنة تعمل فقط على IQueryable<T>؛.هل هناك أي طريقة للحصول على هذه الوظيفة IEnumerable<T>?

هل كانت مفيدة؟

المحلول

لقد تعثرت للتو في هذا العجوز ...

للقيام بذلك بدون مكتبة LINQ الديناميكية، تحتاج فقط إلى الكود كما هو موضح أدناه.يغطي هذا السيناريوهات الأكثر شيوعًا بما في ذلك الخصائص المتداخلة.

للحصول على العمل مع IEnumerable<T> يمكنك إضافة بعض طرق التغليف التي تمر بها AsQueryable - لكن الكود أدناه هو الجوهر Expression المنطق مطلوب.

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

يحرر:يصبح الأمر أكثر متعة إذا كنت تريد مزج ذلك معه dynamic - على الرغم من ملاحظة ذلك dynamic ينطبق فقط على LINQ-to-Objects (لا يمكن لأشجار التعبير الخاصة بـ ORMs وما إلى ذلك أن تمثل حقًا dynamic استفسارات - MemberExpression لا يدعمه).ولكن إليك طريقة للقيام بذلك باستخدام LINQ-to-Objects.لاحظ أن اختيار Hashtable يرجع إلى دلالات القفل المفضلة:

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

نصائح أخرى

سهلة للغاية دون أي تعقيد:

  1. يضيف using System.Linq.Dynamic; في القمة.
  2. يستخدم vehicles = vehicles.AsQueryable().OrderBy("Make ASC, Year DESC").ToList();

لقد وجدت الجواب.يمكنني استخدام .AsQueryable<>() الامتداد لتحويل قائمتي إلى IQueryable، ثم قم بتشغيل الترتيب الديناميكي مقابلها.

تعثرت للتو عبر هذا السؤال.

باستخدام تطبيق مارك ApplyOrder من الأعلى، قمت بتجميع طريقة ملحقة تتعامل مع سلاسل تشبه SQL مثل:

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

التفاصيل يمكن العثور عليها هنا: http://aonnull.blogspot.com/2010/08/dynamic-sql-like-linq-orderby-extension.html

أعتقد أنه سيكون من المفيد استخدام الانعكاس للحصول على أي خاصية تريد فرزها:

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

لاحظ أن استخدام الانعكاس أبطأ بكثير من الوصول إلى الخاصية مباشرة، لذلك يجب التحقق من الأداء.

مجرد البناء على ما قاله الآخرون.لقد وجدت أن ما يلي يعمل بشكل جيد.

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

لقد تعثرت في هذا السؤال بحثًا عن جمل Orderby متعددة LINQ وربما كان هذا ما كان يبحث عنه المؤلف

وإليك كيفية القيام بذلك:

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

كنت أحاول القيام بذلك ولكن أواجه مشاكل حل كيتيل واتنيدال لأنني لا أستخدم بناء جملة linq المضمن - أفضل بناء جملة أسلوب الطريقة.كانت مشكلتي المحددة هي محاولة إجراء فرز ديناميكي باستخدام خيار مخصص IComparer.

انتهى الحل الخاص بي على النحو التالي:

بالنظر إلى استعلام IQueryable مثل ذلك:

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

وبالنظر إلى وسيطة حقل الفرز في وقت التشغيل:

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

يبدو الأمر OrderBy الديناميكي كما يلي:

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

وذلك باستخدام طريقة مساعدة صغيرة تسمى GetReflectedPropertyValue():

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

شيء أخير - ذكرت أنني أريد OrderBy لاستخدام العرف IComparer - لأنني أردت أن أفعل الفرز الطبيعي.

للقيام بذلك، أنا فقط تغيير OrderBy ل:

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

يرى هذا المشنور للكود ل NaturalSortComparer().

يمكنك إضافتها:

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
}

ال GetPropertyValue الوظيفة من إجابة كيتيل واتنيدال

سيكون السؤال لماذا؟أي نوع من هذا القبيل من شأنه أن يطرح استثناءات في وقت التشغيل، بدلاً من وقت الترجمة (مثل إجابة D2VIANT).

إذا كنت تتعامل مع Linq to Sql وكان الأمر orderby عبارة عن شجرة تعبير، فسيتم تحويله إلى SQL للتنفيذ على أي حال.

إليك شيء آخر وجدته مثيرًا للاهتمام.إذا كان المصدر عبارة عن DataTable، فيمكنك استخدام الفرز الديناميكي دون استخدام 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;

مرجع: http://msdn.microsoft.com/en-us/library/bb669083.aspx (باستخدام DataSetExtensions)

إليك طريقة أخرى للقيام بذلك عن طريق تحويله إلى DataView:

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

شكرا لمارتن (الاستعلام عن مجموعة باستخدام كائن PropertyInfo في LINQ) حصلت على هذا الحل:

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

في حالتي كنت أعمل على "ColumnHeaderMouseClick" (WindowsForm) لذلك وجدت العمود المحدد مضغوطًا وPropertyInfo المقابل له:

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

أو

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

(تأكد من مطابقة أسماء الأعمدة لخصائص الكائن)

هتافات

بعد الكثير من البحث نجح هذا بالنسبة لي:

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

يمكنك تحويل IEnumerable إلى IQueryable.

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

يستخدم الحل البديل الفئة/الواجهة التالية.إنها ليست ديناميكية حقًا، ولكنها تعمل.

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

هذه الإجابة هي رد على التعليقات التي تحتاج إلى مثال للحل المقدم من @ جون شيهان - Runscope

يرجى تقديم مثال لبقيتنا.

في DAL (طبقة الوصول إلى البيانات)،

النسخة لا تعد ولا تحصى:

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

نسخة IQueryable

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

يمكنك الآن استخدام إصدار IQueryable للربط، على سبيل المثال GridView في Asp.net والاستفادة من الفرز (لا يمكنك الفرز باستخدام إصدار IEnumerable).

لقد استخدمت Dapper كـ ORM وقمت بإنشاء إصدار IQueryable واستخدمت الفرز في GridView في asp.net بسهولة شديدة.

أول تثبيت ديناميكيالأدوات -> مدير الحزم NuGet -> وحدة تحكم مدير الحزم

install-package System.Linq.Dynamic

يضيف مساحة الاسم using System.Linq.Dynamic;

الآن يمكنك استخدام OrderBy("Name, Age DESC")

قم بتحويل القائمة إلى IEnumerable أو Iquerable، وأضفها باستخدام مساحة الاسم System.LINQ.Dynamic، ثم يمكنك ذكر أسماء الخصائص في سلسلة مفصولة بفواصل إلى أسلوب OrderBy الذي يأتي افتراضيًا من System.LINQ.Dynamic.

استخدم الديناميكية linq

فقط اضف using System.Linq.Dynamic;

واستخدمه على هذا النحو لترتيب جميع أعمدتك:

string sortTypeStr = "ASC"; // or DESC
string SortColumnName = "Age"; // Your column name
query = query.OrderBy($"{SortColumnName} {sortTypeStr}");
var result1 = lst.OrderBy(a=>a.Name);// for ascending order. 
 var result1 = lst.OrderByDescending(a=>a.Name);// for desc order. 
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top