[英]Applying IEnumerable<'T>.OrderBy on a IQueryable<'T>
[英]Dynamic LINQ OrderBy on IEnumerable<T> / IQueryable<T>
我在VS2008 Examples for 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 到对象(ORM 等的表达式树不能真正代表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);
}
}
}
太简单了,没有任何复杂性:
using System.Linq.Dynamic;
添加using System.Linq.Dynamic;
在顶部。vehicles = vehicles.AsQueryable().OrderBy("Make ASC, Year DESC").ToList();
编辑:为了节省一些时间, System.Linq.Dynamic.Core (不推荐使用 System.Linq.Dynamic)程序集不是框架的一部分,但可以从 nuget 安装: System.Linq.Dynamic.Core
我找到了答案。 我可以使用.AsQueryable<>()
扩展方法将列表转换为IQueryable,然后对它运行动态订单。
刚刚偶然发现了这个问题。
使用上面的 Marc 的 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;
}
我试图这样做,但是Kjetil Watnedal 的解决方案有问题,因为我不使用内联 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()
的代码,请参阅此帖子。
我在寻找 Linq 多个 orderby 子句时偶然发现了这个问题,也许这就是作者正在寻找的
以下是如何做到这一点:
var query = pets.OrderBy(pet => pet.Name).ThenByDescending(pet => pet.Age);
使用动态linq
只需添加using System.Linq.Dynamic;
并像这样使用它来订购所有列:
string sortTypeStr = "ASC"; // or DESC
string SortColumnName = "Age"; // Your column name
query = query.OrderBy($"{SortColumnName} {sortTypeStr}");
经过大量搜索,这对我有用:
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);
}
这是我发现有趣的其他事情。 如果您的源是数据表,则可以使用动态排序,而无需使用 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();
您可以将 IEnumerable 转换为 IQueryable。
items = items.AsQueryable().OrderBy("Name ASC");
你可以添加它:
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
函数来自Kjetil Watnedal 的回答
问题是为什么? 任何此类都会在运行时抛出异常,而不是编译时(如 D2VIANT 的回答)。
如果您正在处理 Linq to Sql 并且 orderby 是一个表达式树,它将被转换为 SQL 以供执行。
首先安装动态工具 --> NuGet 包管理器 --> 包管理器控制台
install-package System.Linq.Dynamic
using System.Linq.Dynamic;
添加命名空间using System.Linq.Dynamic;
现在您可以使用OrderBy("Name, Age DESC")
感谢 Maarten( 使用 LINQ 中的 PropertyInfo 对象查询集合)我得到了这个解决方案:
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();
(确保您的列名称与对象属性匹配)
干杯
此答案是对@John Sheehan - Runscope提供的解决方案需要示例的评论的回应
请为我们其他人提供一个例子。
在 DAL(数据访问层)中,
IEnumerable 版本:
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 版本来绑定,例如 Asp.net 中的 GridView 并有利于排序(你不能使用 IEnumerable 版本进行排序)
我使用 Dapper 作为 ORM 并构建 IQueryable 版本,并在 asp.net 的 GridView 中使用排序非常简单。
你可以使用这个:
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();
}
另一种解决方案使用以下类/接口。 它不是真正动态的,但它有效。
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;
}
}
将 List 转换为 IEnumerable 或 Iquerable,添加 using System.LINQ.Dynamic 命名空间,然后您可以将逗号分隔字符串中的属性名称提及到 OrderBy Method,该方法默认来自 System.LINQ.Dynamic。
您可以通过以下方式为多个订单执行此操作
IOrderedEnumerable<JToken> sort;
if (query.OrderBys[0].IsDESC)
{
sort = jarry.OrderByDescending(r => (string)r[query.OrderBys[0].Key]);
}
else
{
sort = jarry.OrderBy(r =>
(string) r[query.OrderBys[0].Key]);
}
foreach (var item in query.OrderBys.Skip(1))
{
if (item.IsDESC)
{
sort = sort.ThenByDescending(r => (string)r[item.Key]);
}
else
{
sort = sort.ThenBy(r => (string)r[item.Key]);
}
}
您可以像这样定义从字符串到 Func<> 的字典:
Dictionary<string, Func<Item, object>> SortParameters = new Dictionary<string, Func<Item, object>>()
{
{"Rank", x => x.Rank}
};
并像这样使用它:
yourList.OrderBy(SortParameters["Rank"]);
在这种情况下,您可以按字符串动态排序。
如果您使用规范(例如Ardalis Specification )
using Microsoft.EntityFrameworkCore;
namespace TestExtensions;
public static class IQueryableExtensions
{
public static void ApplyOrder<T>(ISpecificationBuilder<T> query, string propertyName, bool ascendingOrder)
{
if (ascendingOrder)
query.OrderBy(T => EF.Property<object>(T!, propertyName));
else
query.OrderByDescending(T => EF.Property<object>(T!, propertyName));
}
}
使用 Net6 和 EF
.AsQueryable().OrderBy((ColumnOrder.Column, ColumnOrder.Dir));
简单的问题应该有简单的答案——你只需要这两种扩展方法:
public static IOrderedEnumerable<TSource> OrderByDynamic<TSource, TKey>(this IEnumerable<TSource> source, bool isDescending, Func<TSource, TKey> keySelector) {
if (isDescending) {
return source.OrderByDescending<TSource, TKey>(keySelector);
} else {
return source.OrderBy<TSource, TKey>(keySelector);
}
}
public static IOrderedEnumerable<TSource> OrderByDynamic<TSource, TKey>(this IEnumerable<TSource> source, bool isDescending, Func<TSource, TKey> keySelector, IComparer<TKey> comparer) {
if (isDescending) {
return source.OrderByDescending<TSource, TKey>(keySelector,comparer);
} else {
return source.OrderBy<TSource, TKey>(keySelector,comparer);
}
}
用法:
var orderedList = UnorderedList.OrderByDynamic(isDescending, x => x.MySortValue).ToList();
我可以使用下面的代码来做到这一点。 无需编写冗长复杂的代码。
protected void sort_array(string field_name, string asc_desc)
{
objArrayList= Sort(objArrayList, field_name, asc_desc);
}
protected List<ArrayType> Sort(List<ArrayType> input, string property, string asc_desc)
{
if (asc_desc == "ASC")
{
return input.OrderBy(p => p.GetType()
.GetProperty(property)
.GetValue(p, null)).ToList();
}
else
{
return input.OrderByDescending(p => p.GetType()
.GetProperty(property)
.GetValue(p, null)).ToList();
}
}
var result1 = lst.OrderBy(a=>a.Name);// for ascending order.
var result1 = lst.OrderByDescending(a=>a.Name);// for desc order.
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.