当前位置:  开发笔记 > 编程语言 > 正文

强类型动态Linq排序

如何解决《强类型动态Linq排序》经验,为你挑选了3个好方法。

我正在尝试构建一些代码来动态排序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<>
}

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

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



1> David Wengie..:

最简单的方法是让你的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的返回类型,因为它很容易自动转换,但如果需要更多功能,可以根据需要使用不同类型或泛型实现.在这种情况下,由于表达式就在那里进行检查,因此并不重要.

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

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



2> Thomas Eyde..:

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

然而,既然我花了太多时间而不是工作,我会发布我的结果,希望这会激发人们阅读,思考,理解(重要)然后行动.或者如何使用泛型,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; } } }



3> anthonyv..:

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

它提供双向排序以及解决内部问题.这意味着我不需要为每个给定类型的未排序列表创建新的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具有依赖性,则不会很难更改.

希望这有助于某人.


您需要将SortInstrcution更改为SortInstruction
推荐阅读
无名有名我无名_593
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有