代码之家  ›  专栏  ›  技术社区  ›  David

强类型动态LINQ排序

  •  25
  • David  · 技术社区  · 16 年前

    我正在尝试构建一些代码来动态排序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<TSource>
    ...
    public static Sorter<TSource> GetSorter(this IQueryable<TSource> source, ...)
    

    GetSortedList函数确定要使用的命名排序中的哪一个将导致一个列表对象,其中每个FieldData都包含AddSort中传递的字段的MethodInfo和Type值:

    public SorterItem<TSource> AddSort(Func<T, TKey> 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<>
    }
    

    我不确定是否有一种方法可以以允许稍后返回的方式存储整个字段对象(因为它是泛型类型,所以不可能强制转换)

    有没有一种方法可以调整示例代码,或者想出全新的代码,以便使用强类型字段名进行排序? 之后 它们已存储在某个容器中并被检索(丢失任何泛型类型转换)

    4 回复  |  直到 11 年前
        1
  •  17
  •   David Wengier    16 年前

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

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

    在本例中,我使用了object作为func的返回类型,因为它很容易自动转换,但是如果需要更多的功能,您可以使用不同的类型或泛型来实现它。在这种情况下,由于表达式只存在于需要检查的地方,所以实际上并不重要。

    另一种可能的方法是仍然使用func,并将其存储在字典本身中。然后,当涉及到排序时,您需要获取要排序的值,您可以调用如下内容:

    // assuming a dictionary of fields to sort for, called m_fields
    m_fields[fieldName](currentItem)
    
        2
  •  8
  •   Thomas Eyde    16 年前

    真倒霉!我必须学习如何从头到尾地阅读规范:-(

    然而,现在我已经花了太多的时间在闲逛而不是工作上了,我无论如何都会把我的结果贴出来,希望这能激励人们阅读、思考、理解(重要的)然后行动起来。或是如何在仿制药、羔羊肉和有趣的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<Movie> sorter;
    
            [SetUp]
            public void Setup()
            {
                var unsorted = from m in Movies select m;
                sorter = new Sorter<Movie>(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<Movie> Movies
            {
                get { return CreateMovies().AsQueryable(); }
            }
    
            private static IEnumerable<Movie> 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<E>
        {
            public Sorter(IQueryable<E> unsorted)
            {
                this.unsorted = unsorted;
            }
    
            public void Define<P>(string name, Expression<Func<E, P>> selector)
            {
                firstPasses.Add(name, s => s.OrderBy(selector));
                nextPasses.Add(name, s => s.ThenBy(selector));
            }
    
            public IOrderedQueryable<E> SortBy(params string[] names)
            {
                IOrderedQueryable<E> result = null;
    
                foreach (var name in names)
                {
                    result = result == null
                                 ? SortFirst(name, unsorted)
                                 : SortNext(name, result);
                }
    
                return result;
            }
    
            private IOrderedQueryable<E> SortFirst(string name, IQueryable<E> source)
            {
                return firstPasses[name].Invoke(source);
            }
    
            private IOrderedQueryable<E> SortNext(string name, IOrderedQueryable<E> source)
            {
                return nextPasses[name].Invoke(source);
            }
    
            private readonly IQueryable<E> unsorted;
            private readonly FirstPasses firstPasses = new FirstPasses();
            private readonly NextPasses nextPasses = new NextPasses();
    
    
            private class FirstPasses : Dictionary<string, Func<IQueryable<E>, IOrderedQueryable<E>>> {}
    
    
            private class NextPasses : Dictionary<string, Func<IOrderedQueryable<E>, IOrderedQueryable<E>>> {}
        }
    
    
        internal class Movie
        {
            public string Name { get; set; }
            public int Year { get; set; }
        }
    }
    
        3
  •  8
  •   anthonyv    15 年前

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

    它提供双向排序以及从内到外解决问题。也就是说,对于我来说,需要为给定类型的每个未排序列表创建一个新的排序器没有多大意义。为什么不能把未排序的列表传给排序器呢?这意味着我们可以为不同类型的排序器创建一个signelton实例…

    只是一个想法:

    [TestClass]
    public class SpecifyUserDefinedSorting
    {
        private Sorter<Movie> sorter;
        private IQueryable<Movie> unsorted;
    
        [TestInitialize]
        public void Setup()
        {
            unsorted = from m in Movies select m;
            sorter = new Sorter<Movie>();
            sorter.Register("Name", m1 => m1.Name);
            sorter.Register("Year", m2 => m2.Year);
        }
    
        [TestMethod]
        public void SortByNameThenYear()
        {
            var instructions = new List<SortInstrcution>()
                                   {
                                       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<SortInstrcution>()
                                   {
                                       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<SortInstrcution>()
                                   {
                                       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<SortInstrcution>()
                                   {
                                       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<SortInstrcution>()
                                   {
                                       new SortInstrcution() {Name = "Year"} 
                                   };
            var sorted = sorter.SortBy(unsorted, instructions); 
            var movies = sorted.ToArray();
    
            Assert.AreEqual(movies[0].Name, "B");
        }
    
        private static IQueryable<Movie> Movies
        {
            get { return CreateMovies().AsQueryable(); }
        }
    
        private static IEnumerable<Movie> 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<T> SortBy<T>(this IQueryable<T> source, Sorter<T> sorter, IEnumerable<SortInstrcution> instrcutions)
        {
            return sorter.SortBy(source, instrcutions);
        }
    }
    
    public class Sorter<TSource>
    {
        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<TKey>(string name, Expression<Func<TSource, TKey>> 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<TSource> SortBy(IQueryable<TSource> source, IEnumerable<SortInstrcution> instrcutions)
        {
            IOrderedQueryable<TSource> result = null;
    
            foreach (var instrcution in instrcutions) 
                result = result == null ? this.SortFirst(instrcution, source) : this.SortNext(instrcution, result); 
    
            return result;
        }
    
        private IOrderedQueryable<TSource> SortFirst(SortInstrcution instrcution, IQueryable<TSource> source)
        {
            if (instrcution.Direction == SortDirection.Ascending)
                return this._FirstPasses[instrcution.Name].Invoke(source);
            return this._FirstDescendingPasses[instrcution.Name].Invoke(source);
        }
    
        private IOrderedQueryable<TSource> SortNext(SortInstrcution instrcution, IOrderedQueryable<TSource> source)
        {
            if (instrcution.Direction == SortDirection.Ascending)
                return this._NextPasses[instrcution.Name].Invoke(source);
            return this._NextDescendingPasses[instrcution.Name].Invoke(source);
        }
    
        private class FirstPasses : Dictionary<string, Func<IQueryable<TSource>, IOrderedQueryable<TSource>>> { }
    
        private class NextPasses : Dictionary<string, Func<IOrderedQueryable<TSource>, IOrderedQueryable<TSource>>> { } 
    }
    
    
    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
    }
    

    注意,如果你不想依赖于sortinstrucation,就不难改变了。

    希望这能帮助别人。

        4
  •  3
  •   nawfal Donny V.    11 年前

    我喜欢上面的工作-非常感谢!我冒昧地补充了几点:

    1. 添加了排序方向。

    2. 提出了注册和调用两个不同的问题。

    用途:

    var censusSorter = new Sorter<CensusEntryVM>();
    censusSorter.AddSortExpression("SubscriberId", e=>e.SubscriberId);
    censusSorter.AddSortExpression("LastName", e => e.SubscriberId);
    
    View.CensusEntryDataSource = censusSorter.Sort(q.AsQueryable(), 
        new Tuple<string, SorterSortDirection>("SubscriberId", SorterSortDirection.Descending),
        new Tuple<string, SorterSortDirection>("LastName", SorterSortDirection.Ascending))
        .ToList();
    
    
    
    internal class Sorter<E>
    {
        public Sorter()
        {
        }
        public void AddSortExpression<P>(string name, Expression<Func<E, P>> 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<E> Sort(IQueryable<E> list, 
                                         params Tuple<string, SorterSortDirection>[] names) 
        { 
            IOrderedQueryable<E> 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<E> SortFirst(string name, SorterSortDirection direction, 
                                               IQueryable<E> source) 
        { 
            return direction == SorterSortDirection.Descending
                 ? firstPassesDesc[name].Invoke(source) 
                 : firstPasses[name].Invoke(source);
        } 
    
        private IOrderedQueryable<E> SortNext(string name, SorterSortDirection direction, 
                                              IOrderedQueryable<E> 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<string, Func<IQueryable<E>, IOrderedQueryable<E>>> { }
        private class NextPasses : Dictionary<string, Func<IOrderedQueryable<E>, IOrderedQueryable<E>>> { }
    }