IEnumerable <T>上的动态LINQ OrderBy
我在VS2008动态LINQ示例中找到了一个示例,该示例允许我们使用类似sql的字符串(例如," OrderBy(" Name,Age DESC"))进行排序。不幸的是,所包含的方法仅适用于IQueryable <T>
;。有没有办法在IEnumerable <T>
上获得此功能?
解决方案
回答
我想使用反射来获取要排序的任何属性都可以:
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( 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以供执行。
回答
我找到了答案。我可以使用.AsQueryable <>()
扩展方法将列表转换为IQueryable,然后对它运行动态订单。
回答
刚发现这个老歌...
要在没有动态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(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); } } }
回答
我偶然发现了这个问题,寻找Linq多个orderby子句
也许这就是作者想要的
这样做的方法如下:
var query = pets.OrderBy(pet => pet.Name).ThenByDescending(pet => pet.Age);
回答
替代解决方案使用以下类/接口。它不是真正的动态,但可以。
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; } }
回答
只是建立在别人所说的基础上。我发现以下方法效果很好。
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()
的代码。
回答
这是我发现有趣的事情。
如果源是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/zh-cn/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();
回答
刚刚偶然发现了这个问题。
从上面使用Marc的ApplyOrder实现,我拍了个Extension方法,该方法处理类似SQL的字符串,例如:
list.OrderBy("MyProperty DESC, MyOtherProperty ASC");
可以在这里找到详细信息:http://aonnull.blogspot.com/2010/08/dynamic-sql-like-linq-orderby-extension.html