强类型动态Linq排序

我正在尝试构建一些动态排序Linq IQueryable 的代码。

显而易见的方法是使用字符串作为字段名称对列表进行排序
http://dvanderboom.wordpress.com/2008/12/19/dynamically-composing-linq-orderby-clauses/

但是我想要一个更改 – 编译时检查字段名称,以及使用重构/查找所有引用来支持以后的维护的能力。 这意味着我想将字段定义为f => f.Name,而不是字符串。

对于我的特定用途,我想封装一些代码,这些代码将根据用户输入决定应该使用哪个命名的“OrderBy”表达式列表,而不必每次都编写不同的代码。

这是我写的内容的要点:

var list = from m Movies select m; // Get our list var sorter = list.GetSorter(...); // Pass in some global user settings object sorter.AddSort("NAME", m=>m.Name); sorter.AddSort("YEAR", m=>m.Year).ThenBy(m=>m.Year); list = sorter.GetSortedList(); ... public class Sorter ... public static Sorter GetSorter(this IQueryable source, ...) 

GetSortedList函数确定要使用哪个命名排序,这会产生List对象,其中每个FieldData包含AddSort中传递的字段的MethodInfo和Type值:

 public SorterItem AddSort(Func field) { MethodInfo ... = field.Method; Type ... = TypeOf(TKey); // Create item, add item to diction, add fields to item's List // The item has the ThenBy method, which just adds another field to the List } 

我不确定是否有办法以一种允许稍后返回的方式存储整个字段对象(由于它是generics类型,因此无法进行转换)

有没有办法我可以调整示例代码,或者提出全新的代码,以便在将强类型字段名称存储在某个容器中并进行检索(丢失任何generics类型转换) 后对其进行排序

最简单的方法是让你的AddSort()函数采用Expression >而不仅仅是一个Func。 这允许您的sort方法检查Expression以提取要排序的属性的名称。 然后,您可以在内部将此名称存储为字符串,因此存储非常简单,您可以使用链接到的排序算法,但您还可以获得类型安全性和编译时检查有效的属性名称。

 static void Main(string[] args) { var query = from m in Movies select m; var sorter = new Sorter(); sorter.AddSort("NAME", m => m.Name); } class Sorter { public void AddSort(string name, Expression> func) { string fieldName = (func.Body as MemberExpression).Member.Name; } } 

在这种情况下,我使用了对象作为func的返回类型,因为它很容易自动转换,但如果需要更多function,可以根据需要使用不同类型或generics实现。 在这种情况下,由于表达式就在那里进行检查,因此并不重要。

另一种可能的方法是仍然使用Func,并将其存储在字典本身中。 然后,当涉及到排序,并且您需要获得要排序的值时,您可以调用类似于:

 // assuming a dictionary of fields to sort for, called m_fields m_fields[fieldName](currentItem) 

坏消息! 我必须学习如何从头到尾阅读规范:-(

然而,既然我花了太多时间而不是工作,我会发布我的结果,希望这会激发人们阅读,思考,理解(重要)然后行动。 或者如何使用generics,lambdas和有趣的Linq东西过于聪明。

我在这个练习中发现的一个巧妙的技巧是那些源自Dictionary私有内部类。 它们的全部目的是移除所有这些尖括号以提高可读性。

哦,差点忘了代码:

更新:使代码通用,并使用IQueryable而不是IEnumerable

 using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using NUnit.Framework; using NUnit.Framework.SyntaxHelpers; namespace StackOverflow.StrongTypedLinqSort { [TestFixture] public class SpecifyUserDefinedSorting { private Sorter sorter; [SetUp] public void Setup() { var unsorted = from m in Movies select m; sorter = new Sorter(unsorted); sorter.Define("NAME", m1 => m1.Name); sorter.Define("YEAR", m2 => m2.Year); } [Test] public void SortByNameThenYear() { var sorted = sorter.SortBy("NAME", "YEAR"); var movies = sorted.ToArray(); Assert.That(movies[0].Name, Is.EqualTo("A")); Assert.That(movies[0].Year, Is.EqualTo(2000)); Assert.That(movies[1].Year, Is.EqualTo(2001)); Assert.That(movies[2].Name, Is.EqualTo("B")); } [Test] public void SortByYearThenName() { var sorted = sorter.SortBy("YEAR", "NAME"); var movies = sorted.ToArray(); Assert.That(movies[0].Name, Is.EqualTo("B")); Assert.That(movies[1].Year, Is.EqualTo(2000)); } [Test] public void SortByYearOnly() { var sorted = sorter.SortBy("YEAR"); var movies = sorted.ToArray(); Assert.That(movies[0].Name, Is.EqualTo("B")); } private static IQueryable Movies { get { return CreateMovies().AsQueryable(); } } private static IEnumerable CreateMovies() { yield return new Movie {Name = "B", Year = 1990}; yield return new Movie {Name = "A", Year = 2001}; yield return new Movie {Name = "A", Year = 2000}; } } internal class Sorter { public Sorter(IQueryable unsorted) { this.unsorted = unsorted; } public void Define

(string name, Expression> selector) { firstPasses.Add(name, s => s.OrderBy(selector)); nextPasses.Add(name, s => s.ThenBy(selector)); } public IOrderedQueryable SortBy(params string[] names) { IOrderedQueryable result = null; foreach (var name in names) { result = result == null ? SortFirst(name, unsorted) : SortNext(name, result); } return result; } private IOrderedQueryable SortFirst(string name, IQueryable source) { return firstPasses[name].Invoke(source); } private IOrderedQueryable SortNext(string name, IOrderedQueryable source) { return nextPasses[name].Invoke(source); } private readonly IQueryable unsorted; private readonly FirstPasses firstPasses = new FirstPasses(); private readonly NextPasses nextPasses = new NextPasses(); private class FirstPasses : Dictionary, IOrderedQueryable>> {} private class NextPasses : Dictionary, IOrderedQueryable>> {} } internal class Movie { public string Name { get; set; } public int Year { get; set; } } }

根据每个人的贡献,我提出了以下建议。

它提供双向排序以及解决内部问题。 这意味着我不需要为给定类型的每个未排序列表创建新的Sorter。 为什么不能将未排序的列表传递给分拣机。 这意味着我们可以为不同类型创建Sorter的signelton实例……

只是一个想法:

 [TestClass] public class SpecifyUserDefinedSorting { private Sorter sorter; private IQueryable unsorted; [TestInitialize] public void Setup() { unsorted = from m in Movies select m; sorter = new Sorter(); sorter.Register("Name", m1 => m1.Name); sorter.Register("Year", m2 => m2.Year); } [TestMethod] public void SortByNameThenYear() { var instructions = new List() { new SortInstrcution() {Name = "Name"}, new SortInstrcution() {Name = "Year"} }; var sorted = sorter.SortBy(unsorted, instructions); var movies = sorted.ToArray(); Assert.AreEqual(movies[0].Name, "A"); Assert.AreEqual(movies[0].Year, 2000); Assert.AreEqual(movies[1].Year, 2001); Assert.AreEqual(movies[2].Name, "B"); } [TestMethod] public void SortByNameThenYearDesc() { var instructions = new List() { new SortInstrcution() {Name = "Name", Direction = SortDirection.Descending}, new SortInstrcution() {Name = "Year", Direction = SortDirection.Descending} }; var sorted = sorter.SortBy(unsorted, instructions); var movies = sorted.ToArray(); Assert.AreEqual(movies[0].Name, "B"); Assert.AreEqual(movies[0].Year, 1990); Assert.AreEqual(movies[1].Name, "A"); Assert.AreEqual(movies[1].Year, 2001); Assert.AreEqual(movies[2].Name, "A"); Assert.AreEqual(movies[2].Year, 2000); } [TestMethod] public void SortByNameThenYearDescAlt() { var instructions = new List() { new SortInstrcution() {Name = "Name", Direction = SortDirection.Descending}, new SortInstrcution() {Name = "Year"} }; var sorted = sorter.SortBy(unsorted, instructions); var movies = sorted.ToArray(); Assert.AreEqual(movies[0].Name, "B"); Assert.AreEqual(movies[0].Year, 1990); Assert.AreEqual(movies[1].Name, "A"); Assert.AreEqual(movies[1].Year, 2000); Assert.AreEqual(movies[2].Name, "A"); Assert.AreEqual(movies[2].Year, 2001); } [TestMethod] public void SortByYearThenName() { var instructions = new List() { new SortInstrcution() {Name = "Year"}, new SortInstrcution() {Name = "Name"} }; var sorted = sorter.SortBy(unsorted, instructions); var movies = sorted.ToArray(); Assert.AreEqual(movies[0].Name, "B"); Assert.AreEqual(movies[1].Year, 2000); } [TestMethod] public void SortByYearOnly() { var instructions = new List() { new SortInstrcution() {Name = "Year"} }; var sorted = sorter.SortBy(unsorted, instructions); var movies = sorted.ToArray(); Assert.AreEqual(movies[0].Name, "B"); } private static IQueryable Movies { get { return CreateMovies().AsQueryable(); } } private static IEnumerable CreateMovies() { yield return new Movie { Name = "B", Year = 1990 }; yield return new Movie { Name = "A", Year = 2001 }; yield return new Movie { Name = "A", Year = 2000 }; } } public static class SorterExtension { public static IOrderedQueryable SortBy(this IQueryable source, Sorter sorter, IEnumerable instrcutions) { return sorter.SortBy(source, instrcutions); } } public class Sorter { private readonly FirstPasses _FirstPasses; private readonly FirstPasses _FirstDescendingPasses; private readonly NextPasses _NextPasses; private readonly NextPasses _NextDescendingPasses; public Sorter() { this._FirstPasses = new FirstPasses(); this._FirstDescendingPasses = new FirstPasses(); this._NextPasses = new NextPasses(); this._NextDescendingPasses = new NextPasses(); } public void Register(string name, Expression> selector) { this._FirstPasses.Add(name, s => s.OrderBy(selector)); this._FirstDescendingPasses.Add(name, s => s.OrderByDescending(selector)); this._NextPasses.Add(name, s => s.ThenBy(selector)); this._NextDescendingPasses.Add(name, s => s.ThenByDescending(selector)); } public IOrderedQueryable SortBy(IQueryable source, IEnumerable instrcutions) { IOrderedQueryable result = null; foreach (var instrcution in instrcutions) result = result == null ? this.SortFirst(instrcution, source) : this.SortNext(instrcution, result); return result; } private IOrderedQueryable SortFirst(SortInstrcution instrcution, IQueryable source) { if (instrcution.Direction == SortDirection.Ascending) return this._FirstPasses[instrcution.Name].Invoke(source); return this._FirstDescendingPasses[instrcution.Name].Invoke(source); } private IOrderedQueryable SortNext(SortInstrcution instrcution, IOrderedQueryable source) { if (instrcution.Direction == SortDirection.Ascending) return this._NextPasses[instrcution.Name].Invoke(source); return this._NextDescendingPasses[instrcution.Name].Invoke(source); } private class FirstPasses : Dictionary, IOrderedQueryable>> { } private class NextPasses : Dictionary, IOrderedQueryable>> { } } internal class Movie { public string Name { get; set; } public int Year { get; set; } } public class SortInstrcution { public string Name { get; set; } public SortDirection Direction { get; set; } } public enum SortDirection { //Note I have created this enum because the one that exists in the .net // framework is in the web namespace... Ascending, Descending } 

请注意,如果您不想对SortInstrcution具有依赖性,那么改变就不那么难了。

希望这有助于某人。

我喜欢上面的工作 – 非常感谢你! 我冒昧地添加了一些东西:

  1. 添加了排序方向。

  2. 注册并调用两个不同的问题。

用法:

 var censusSorter = new Sorter(); censusSorter.AddSortExpression("SubscriberId", e=>e.SubscriberId); censusSorter.AddSortExpression("LastName", e => e.SubscriberId); View.CensusEntryDataSource = censusSorter.Sort(q.AsQueryable(), new Tuple("SubscriberId", SorterSortDirection.Descending), new Tuple("LastName", SorterSortDirection.Ascending)) .ToList(); internal class Sorter { public Sorter() { } public void AddSortExpression

(string name, Expression> selector) { // Register all possible types of sorting for each parameter firstPasses.Add(name, s => s.OrderBy(selector)); nextPasses.Add(name, s => s.ThenBy(selector)); firstPassesDesc.Add(name, s => s.OrderByDescending(selector)); nextPassesDesc.Add(name, s => s.OrderByDescending(selector)); } public IOrderedQueryable Sort(IQueryable list, params Tuple[] names) { IOrderedQueryable result = null; foreach (var entry in names) { result = result == null ? SortFirst(entry.Item1, entry.Item2, list) : SortNext(entry.Item1, entry.Item2, result); } return result; } private IOrderedQueryable SortFirst(string name, SorterSortDirection direction, IQueryable source) { return direction == SorterSortDirection.Descending ? firstPassesDesc[name].Invoke(source) : firstPasses[name].Invoke(source); } private IOrderedQueryable SortNext(string name, SorterSortDirection direction, IOrderedQueryable source) { return direction == SorterSortDirection.Descending ? nextPassesDesc[name].Invoke(source) : nextPasses[name].Invoke(source); } private readonly FirstPasses firstPasses = new FirstPasses(); private readonly NextPasses nextPasses = new NextPasses(); private readonly FirstPasses firstPassesDesc = new FirstPasses(); private readonly NextPasses nextPassesDesc = new NextPasses(); private class FirstPasses : Dictionary, IOrderedQueryable>> { } private class NextPasses : Dictionary, IOrderedQueryable>> { } }