Pergunta

Encontrei um exemplo no Exemplos VS2008 para Dynamic LINQ que permite usar uma string semelhante a sql (por exemplo OrderBy("Name, Age DESC")) para encomendar.Infelizmente, o método incluído só funciona em IQueryable<T>;.Existe alguma maneira de ativar essa funcionalidade IEnumerable<T>?

Foi útil?

Solução

Acabei de me deparar com esse velho...

Para fazer isso sem a biblioteca LINQ dinâmica, você só precisa do código abaixo.Isso abrange os cenários mais comuns, incluindo propriedades aninhadas.

Para fazê-lo funcionar IEnumerable<T> você pode adicionar alguns métodos wrapper que passam por AsQueryable - mas o código abaixo é o núcleo Expression lógica necessária.

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

Editar:fica mais divertido se você quiser misturar isso com dynamic - embora note que dynamic aplica-se apenas a LINQ-to-Objects (árvores de expressão para ORMs etc. não podem realmente representar dynamic consultas - MemberExpression não suporta).Mas aqui está uma maneira de fazer isso com LINQ-to-Objects.Observe que a escolha de Hashtable é devido à semântica de bloqueio favorável:

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

Outras dicas

Muito fácil sem nenhuma complicação:

  1. Adicionar using System.Linq.Dynamic; no topo.
  2. Usar vehicles = vehicles.AsQueryable().OrderBy("Make ASC, Year DESC").ToList();

Eu encontrei a resposta.Eu posso usar o .AsQueryable<>() método de extensão para converter minha lista em IQueryable e, em seguida, execute a ordem dinâmica em relação a ela.

Acabei de me deparar com esta questão.

Usando a implementação ApplyOrder de Marc acima, criei um método de extensão que lida com strings semelhantes a SQL como:

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

Detalhes podem ser encontrados aqui: http://aonnull.blogspot.com/2010/08/dynamic-sql-like-linq-orderby-extension.html

Acho que funcionaria usar reflexão para obter qualquer propriedade que você deseja classificar:

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

Observe que usar a reflexão é consideravelmente mais lento do que acessar a propriedade diretamente, portanto o desempenho teria que ser investigado.

Apenas com base no que outros disseram.Descobri que o seguinte funciona muito bem.

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

Eu tropeço essa pergunta procurando por cláusulas Linq Multiple Orderby e talvez fosse isso que o autor estava procurando

Veja como fazer isso:

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

Eu estava tentando fazer isso, mas tendo problemas com A solução de Kjetil Watnedal porque não uso a sintaxe linq inline - prefiro a sintaxe no estilo de método.Meu problema específico foi tentar fazer uma classificação dinâmica usando um personalizado IComparer.

Minha solução acabou assim:

Dada uma consulta IQueryable assim:

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

E dado um argumento de campo de classificação em tempo de execução:

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

O OrderBy dinâmico se parece com isto:

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

E isso está usando um pequeno método auxiliar chamado GetReflectedPropertyValue():

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

Uma última coisa - mencionei que queria o OrderBy usar personalizado IComparer - porque eu queria fazer Classificação natural.

Para fazer isso, apenas altero o OrderBy para:

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

Ver esta postagem para o código de NaturalSortComparer().

Você poderia adicioná-lo:

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
}

O GetPropertyValue a função é de A resposta de Kjetil Watnedal

A questão seria por quê?Qualquer tipo lançaria exceções em tempo de execução, em vez de tempo de compilação (como a resposta do D2VIANT).

Se você estiver lidando com Linq to Sql e o orderby for uma árvore de expressão, ele será convertido em SQL para execução de qualquer maneira.

Aqui está outra coisa que achei interessante.Se sua fonte for um DataTable, você pode usar classificação dinâmica sem usar 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;

referência: http://msdn.microsoft.com/en-us/library/bb669083.aspx (Usando DataSetExtensions)

Aqui está mais uma maneira de fazer isso, convertendo-o em um DataView:

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

Obrigado a Maarten (Consultar uma coleção usando o objeto PropertyInfo em LINQ) Eu tenho esta solução:

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

No meu caso eu estava trabalhando em um "ColumnHeaderMouseClick" (WindowsForm), então encontrei a coluna específica pressionada e seu PropertyInfo correspondente:

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

(certifique-se de que os nomes das colunas correspondam às propriedades do objeto)

Saúde

Depois de muita pesquisa, isso funcionou para mim:

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

Você pode converter IEnumerable em IQueryable.

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

Uma solução alternativa usa a seguinte classe/interface.Não é verdadeiramente dinâmico, mas funciona.

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

Esta resposta é uma resposta aos comentários que precisam de um exemplo para a solução fornecida por @John Sheehan - Runscope

Por favor, forneça um exemplo para o resto de nós.

em DAL (camada de acesso a dados),

A versão IEnumerable:

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

A versão IQueryable

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

Agora você pode usar a versão IQueryable para vincular, por exemplo GridView no Asp.net e se beneficiar da classificação (você não pode classificar usando a versão IEnumerable)

Usei o Dapper como ORM e criei a versão IQueryable e utilizei a classificação no GridView no asp.net com muita facilidade.

Primeira instalação dinâmicaFerramentas -> Gerenciador de Pacotes NuGet -> Console do Gerenciador de Pacotes

install-package System.Linq.Dynamic

Adicionar Espaço para nome using System.Linq.Dynamic;

Agora você pode usar OrderBy("Name, Age DESC")

Converta List em IEnumerable ou Iquerable, adicione usando o namespace System.LINQ.Dynamic, então você pode mencionar os nomes das propriedades em uma string separada por vírgula para o método OrderBy que vem por padrão de System.LINQ.Dynamic.

Usar dinâmico linq

basta adicionar using System.Linq.Dynamic;

E use assim para ordenar todas as suas colunas:

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. 
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top