Question

J'ai trouvé un exemple dans le Exemples VS2008 pour Dynamic LINQ qui vous permet d'utiliser une chaîne de type SQL (par ex. OrderBy("Name, Age DESC")) pour la commande.Malheureusement, la méthode incluse ne fonctionne que sur IQueryable<T>;.Existe-t-il un moyen d'obtenir cette fonctionnalité IEnumerable<T>?

Était-ce utile?

La solution

Je viens de tomber sur ce vieux...

Pour ce faire sans la bibliothèque dynamique LINQ, vous avez juste besoin du code ci-dessous.Cela couvre les scénarios les plus courants, y compris les propriétés imbriquées.

Pour le faire fonctionner avec IEnumerable<T> vous pouvez ajouter des méthodes wrapper qui passent par AsQueryable - mais le code ci-dessous est le noyau Expression logique nécessaire.

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

Modifier:ça devient plus amusant si tu veux mélanger ça avec dynamic - mais notez que dynamic ne s'applique qu'à LINQ-to-Objects (les arbres d'expression pour les ORM, etc. ne peuvent pas vraiment représenter dynamic requêtes - MemberExpression ne le supporte pas).Mais voici une façon de le faire avec LINQ-to-Objects.A noter que le choix de Hashtable est dû à une sémantique de verrouillage favorable :

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

Autres conseils

Trop simple et sans aucune complication :

  1. Ajouter using System.Linq.Dynamic; au sommet.
  2. Utiliser vehicles = vehicles.AsQueryable().OrderBy("Make ASC, Year DESC").ToList();

J'ai trouvé la réponse.je peux utiliser le .AsQueryable<>() méthode d'extension pour convertir ma liste en IQueryable, puis exécutez la commande dynamique par rapport à elle.

Je viens de tomber sur cette question.

En utilisant l'implémentation ApplyOrder de Marc ci-dessus, j'ai créé une méthode Extension qui gère les chaînes de type SQL telles que :

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

Les détails peuvent être trouvés ici: http://aonnull.blogspot.com/2010/08/dynamic-sql-like-linq-orderby-extension.html

Je suppose que cela fonctionnerait d'utiliser la réflexion pour obtenir la propriété sur laquelle vous souhaitez trier :

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

Notez que l'utilisation de la réflexion est considérablement plus lente que l'accès direct à la propriété, les performances devraient donc être étudiées.

Je m'appuie simplement sur ce que d'autres ont dit.J'ai trouvé que ce qui suit fonctionne plutôt bien.

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

J'ai trébuché cette question à la recherche de clauses Linq à plusieurs ordres et peut-être que c'était ce que l'auteur cherchait

Voici comment procéder :

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

J'essayais de le faire mais j'ai des problèmes avec La solution de Kjetil Watnedal parce que je n'utilise pas la syntaxe Linq en ligne - je préfère la syntaxe de style méthode.Mon problème spécifique consistait à essayer d'effectuer un tri dynamique à l'aide d'un fichier personnalisé. IComparer.

Ma solution a fini comme ceci :

Étant donné une requête IQueryable comme celle-ci :

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

Et étant donné un argument de champ de tri à l'exécution :

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

Le OrderBy dynamique ressemble à ceci :

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

Et cela utilise une petite méthode d'assistance appelée GetReflectedPropertyValue() :

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

Une dernière chose - j'ai mentionné que je voulais le OrderBy utiliser la coutume IComparer - parce que je voulais faire Tri naturel.

Pour ce faire, je modifie simplement le OrderBy à:

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

Voir ce post pour le code pour NaturalSortComparer().

Vous pourriez l'ajouter :

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
}

Le GetPropertyValue la fonction vient de La réponse de Kjetil Watnedal

La question serait pourquoi ?Un tel tri générerait des exceptions au moment de l'exécution, plutôt qu'au moment de la compilation (comme la réponse de D2VIANT).

Si vous utilisez Linq to Sql et que le orderby est un arbre d'expression, il sera de toute façon converti en SQL pour l'exécution.

Voici autre chose que j'ai trouvé intéressant.Si votre source est un DataTable, vous pouvez utiliser le tri dynamique sans utiliser 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;

référence: http://msdn.microsoft.com/en-us/library/bb669083.aspx (Utilisation de DataSetExtensions)

Voici une autre façon de le faire en le convertissant en DataView :

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

Merci à Martin (Interroger une collection à l'aide de l'objet PropertyInfo dans LINQ) J'ai eu cette solution :

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

Dans mon cas, je travaillais sur un "ColumnHeaderMouseClick" (WindowsForm), je viens donc de trouver la colonne spécifique enfoncée et son PropertyInfo correspondant :

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

OU

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

(assurez-vous que les noms de vos colonnes correspondent aux propriétés de l'objet)

Acclamations

Après de nombreuses recherches, cela a fonctionné pour moi :

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

Vous pouvez convertir le IEnumerable en IQueryable.

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

Une solution alternative utilise la classe/interface suivante.Ce n'est pas vraiment dynamique, mais ça marche.

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

Cette réponse est une réponse aux commentaires qui nécessitent un exemple pour la solution fournie par @John Sheehan - Runscope

Veuillez fournir un exemple pour le reste d'entre nous.

en DAL (Data Access Layer),

La version IEnumerable :

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

La version IQueryable

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

Vous pouvez désormais utiliser la version IQueryable pour lier, par exemple GridView dans Asp.net et bénéficier du tri (vous ne pouvez pas trier en utilisant la version IEnumerable)

J'ai utilisé Dapper comme ORM et créé la version IQueryable et utilisé le tri dans GridView dans asp.net si facilement.

Première installation dynamiqueOutils -> Gestionnaire de packages NuGet -> Console du gestionnaire de packages

install-package System.Linq.Dynamic

Ajouter Espace de noms using System.Linq.Dynamic;

Maintenant vous pouvez utiliser OrderBy("Name, Age DESC")

Convertissez la liste en IEnumerable ou Iquerable, ajoutez en utilisant l'espace de noms System.LINQ.Dynamic, vous pouvez alors mentionner les noms de propriété dans une chaîne séparée par des virgules à la méthode OrderBy qui provient par défaut de System.LINQ.Dynamic.

Utiliser dynamique linq

Ajoutez simplement using System.Linq.Dynamic;

Et utilisez-le comme ceci pour trier toutes vos colonnes :

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. 
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top