【问题标题】:Strongly typed dynamic Linq sorting强类型动态 Linq 排序
【发布时间】:2010-10-08 03:41:25
【问题描述】:

我正在尝试构建一些代码来动态排序 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 函数确定要使用哪个命名排序,这会产生一个 List 对象,其中每个 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<>
}

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

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

【问题讨论】:

标签: c# .net linq


【解决方案1】:

最简单的方法是让您的 AddSort() 函数采用 Expression> 而不仅仅是一个 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)

【讨论】:

  • 嗨 Ch00k,这段代码看起来很棒!我有同样的需求,除了 GroupBy ......你介意帮助我吗?谢谢!
【解决方案2】:

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

但是,既然我花了太多时间在闲逛而不是工作,我还是会发布我的结果,希望这能激发人们阅读、思考、理解(重要)然后采取行动。或者如何在泛型、lambda 和有趣的 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; }
    }
}

【讨论】:

  • 这个解决方案的代码比我所拥有的要少得多,只是我认为它违反了一个不成文的要求——我需要 IQueryable 后期绑定作为排序列表,然后通过 Skip/Take 来分页大型 SQL数据集。但是您确实提供了帮助 - 我不喜欢为此目的使用“AddSort”,“Define”要好得多。
  • 是的,我看到你使用了 IQueryable 接口,但是我太兴奋了,以至于忘记了这一切。我看看能不能进去……
【解决方案3】:

根据每个人的贡献,我想出了以下内容。

它提供双向排序以及从内到外解决问题。这意味着需要为给定类型的每个未排序列表创建一个新的排序器对我来说没有多大意义。为什么这个未排序的列表不能传递给排序器。这意味着我们可以为我们的不同类型创建 Sorter 的 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
}

请注意,如果您不想依赖 SortInstrcution,那么更改它不会那么难。

希望这对某人有所帮助。

【讨论】:

  • 你需要将 SortInstrcution 改为 SortInstruction
【解决方案4】:

我喜欢上面的作品 - 非常感谢!我冒昧地添加了几件事:

  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>>> { }
}

【讨论】:

  • 这真是太好了。我猜你解耦 AddSortExpression 和 Sort 的原因是你可以禁用数据集中某些“列”的排序?
猜你喜欢
  • 2013-10-14
  • 1970-01-01
  • 2012-02-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多