Dynamic LINQ OrderBy on IEnumerable<T> / IQueryable<T>
我在VS2008的动态LINQ示例中发现了一个示例,它允许您使用类似SQL的字符串(例如
只是偶然发现了这个老东西…
要在没有动态Linq库的情况下完成这项工作,您只需要下面的代码。这涵盖了最常见的场景,包括嵌套属性。
要使它与
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | 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; } |
编辑:如果你想把它和
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 | 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); } } } |
太容易了,没有任何并发症:
我找到了答案。我可以使用
刚刚偶然发现这个问题。
使用上面Marc的applyOrder实现,我将处理类似SQL的字符串的扩展方法组合在一起,比如:
1 | list.OrderBy("MyProperty DESC, MyOtherProperty ASC"); |
有关详细信息,请访问:http://aonnull.blogspot.com/2010/08/dynamic-sql-like-linq-orderby-extension.html
我想使用反射来获得你想要排序的任何属性都是可行的:
1 2 3 4 5 6 7 8 9 10 11 | 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); } |
请注意,使用反射比直接访问属性慢得多,因此必须研究性能。
只是建立在别人所说的基础上。我发现下面的方法很有效。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | 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; } |
我在查找linq multiple orderby子句时遇到了这个问题。也许这就是作者想要的
以下是如何做到这一点:
1 | var query = pets.OrderBy(pet => pet.Name).ThenByDescending(pet => pet.Age); |
我试图这样做,但是KjetilWatnedal的解决方案有问题,因为我不使用内联LINQ语法-我更喜欢方法样式语法。我的具体问题是尝试使用自定义
我的解决方案是这样的:
给出这样一个IQueryable查询:
1 2 | List<DATA__Security__Team> teams = TeamManager.GetTeams(); var query = teams.Where(team => team.ID < 10).AsQueryable(); |
并给出一个运行时排序字段参数:
1 | string SortField; // Set at run-time to"Name" |
动态orderby如下所示:
1 | query = query.OrderBy(item => item.GetReflectedPropertyValue(SortField)); |
它使用了一个名为getReflectedPropertyValue()的小助手方法:
1 2 3 4 5 | public static string GetReflectedPropertyValue(this object subject, string field) { object reflectedValue = subject.GetType().GetProperty(field).GetValue(subject, null); return reflectedValue != null ? reflectedValue.ToString() :""; } |
最后一件事-我提到我希望
为此,我将
1 | query = query.OrderBy(item => item.GetReflectedPropertyValue(SortField), new NaturalSortComparer<string>()); |
有关
可以将IEnumerable转换为IQueryable。
1 | items = items.AsQueryable().OrderBy("Name ASC"); |
经过大量的搜索,这对我很有用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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); } |
由于Maarten(使用Linq中的PropertyInfo对象查询集合),我得到了这个解决方案:
1 | myList.OrderByDescending(x => myPropertyInfo.GetValue(x, null)).ToList(); |
在我的例子中,我正在处理一个"ColumnHeaderMouseClick"(WindowsForm),所以刚找到按下的特定列及其对应的属性信息:
1 2 3 4 5 | foreach (PropertyInfo column in (new Process()).GetType().GetProperties()) { if (column.Name == dgvProcessList.Columns[e.ColumnIndex].Name) {} } |
或
1 | PropertyInfo column = (new Process()).GetType().GetProperties().Where(x => x.Name == dgvProcessList.Columns[e.ColumnIndex].Name).First(); |
(请确保列名与对象属性匹配)
干杯
您可以添加它:
1 2 3 4 5 6 7 8 9 10 | 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 } |
问题是为什么?任何这样的类型都会在运行时抛出异常,而不是在编译时抛出异常(如d2viant的答案)。
如果您处理的是linq to sql,而orderby是一个表达式树,那么它将转换为sql以供执行。
这里还有一些我觉得有趣的东西。如果源是数据表,则可以使用动态排序而不使用动态LINQ
1 2 3 4 5 6 | 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(使用datasetensions)
还有一种方法可以通过将其转换为数据视图来实现:
1 2 3 4 5 | DataTable contacts = dataSet.Tables["Contact"]; DataView view = contacts.AsDataView(); view.Sort ="LastName desc, FirstName asc"; bindingSource1.DataSource = view; dataGridView1.AutoResizeColumns(); |
另一种解决方案使用以下类/接口。它不是真正的动态的,但它是有效的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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; } } |
此答案是对需要@john sheehan-runscope提供解决方案示例的注释的响应
Please provide an example for the rest of us.
在DAL(数据访问层)中,
IEnumerable版本:
1 2 3 4 5 6 | public IEnumerable<Order> GetOrders() { // i use Dapper to return IEnumerable<T> using Query<T> //.. do stuff return orders // IEnumerable<Order> } |
可查询版本
1 2 3 4 5 6 | public IQueryable<Order> GetOrdersAsQuerable() { IEnumerable<Order> qry= GetOrders(); //use the built-in extension method AsQueryable in System.Linq namespace return qry.AsQueryable(); } |
现在您可以使用iQuery版本进行绑定,例如ASP.NET中的GridView和排序优势(不能使用IEnumerable版本进行排序)
我使用dapper作为ORM,构建了iqueryable版本,并在ASP.NET中轻松地使用了GridView中的排序。
第一次安装动态工具——>Nuget Package Manager——>Package Manager控制台
1 | install-package System.Linq.Dynamic |
添加命名空间
现在您可以使用EDOCX1[1]
将list转换为ienumerable或iquerable,使用system.linq.dynamic命名空间添加,然后可以将属性名用逗号分隔的字符串提到orderby方法,该方法默认来自system.linq.dynamic。
使用动态
加上
然后像这样对所有列进行排序:
1 2 3 | string sortTypeStr ="ASC"; // or DESC string SortColumnName ="Age"; // Your column name query = query.OrderBy($"{SortColumnName} {sortTypeStr}"); |
1 2 | var result1 = lst.OrderBy(a=>a.Name);// for ascending order. var result1 = lst.OrderByDescending(a=>a.Name);// for desc order. |