2009-02-17 4 views
24

Я пытаюсь создать код для динамической сортировки Linq IQueryable <>.Сильно типизированная динамическая сортировка Linq

Очевидный способ здесь, которая сортирует список, используя строку для имени поля
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 содержит MethodInfo и значение Типа полей, передаваемых в AddSort:

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

Я не уверен, если есть способ, чтобы сохранить весь объект поля таким образом, что позволит ему быть возвращены позже (это было бы невозможно бросить, так как оно является общим типом)

Есть ли способ, которым я мог бы адаптировать образец кода или создать совершенно новый код для сортировки с использованием строго типизированных имен полей после они были сохранены в каком-то контейнере и получены (потеряние какого-либо родового типа)

+2

Посмотреть сообщение: (. Это может помочь другим, кто ищет "волшебную" заказ на) http://stackoverflow.com/questions/41244/dynamic-linq-orderby – Nordes

ответ

17

Самый простой способ сделать это - заставить функцию 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; 
    } 
} 

В этом случае, я использовал объект в качестве типа возвращаемого FUNC, потому что его легко автоматически конвертируемым, но вы могли бы реализовать это с различными типами или дженериков, в случае необходимости, если вам требуется больше функциональных возможностей , В этом случае, поскольку выражение только что должно быть проверено, это не имеет большого значения.

Другим возможным способом является получение Func и сохранение его в самом словаре. Затем, когда дело доходит до сортировки, и вам нужно получить значение для сортировки, вы можете позвонить примерно так:

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

Привет, Ch00k, этот код выглядит потрясающе! У меня есть такая же потребность, за исключением GroupBy ... Не могли бы вы мне помочь? Благодаря! – ibiza

+0

Но каков код для заказа для каждого 'fieldName'? –

8

Bummer! Я должен научиться читать спецификации из конца в конец :-(

Однако теперь, когда я потратил слишком много времени на обман, а не на работу, я буду публиковать результаты в любом случае, надеясь, что это вдохновит людей читать, думать , понимать (важно), а затем действовать. Или как быть слишком умным с дженериками, лямбдами и смешными материалами Линка.

Аккуратный трюк, который я обнаружил во время этого упражнения , те частные внутренние классов, которые черпают из Dictionary. Их цель состоит в том, чтобы удалить все этих угловых кронштейнов, чтобы улучшал удобочитаемость.

О, чуть не забыл код:

UPDATE: Сделан код родовой и использовать 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; } 
    } 
} 
+1

Это решение намного меньше кода, чем то, что у меня есть, только я считаю, что это нарушает неписаное требование - мне нужно привязать позднюю привязку IQueryable к сортированным спискам, а затем перейти через Skip/Take на большие массивы данных SQL. Однако вы помогли - мне не понравилось «AddSort» для этой цели, «Define» намного лучше. – David

+0

Да, я видел, что вы использовали интерфейс IQueryable, но я был так взволнован, что все это забыл. Я посмотрю, смогу ли я его получить ... –

8

Основываясь на том, что каждый внес свой вклад я придумал следующие.

Он обеспечивает двунаправленную сортировку, а также решение проблемы наизнанку. Значит, для меня не имело большого значения, что новый сортировщик должен быть создан для каждого несортированного списка определенного типа. Почему этот сортируемый список не может быть передан в сортировщик. Тогда это означает, что мы могли бы создать экземпляр 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 это Wouldn это трудно изменить.

Надеюсь, это поможет кому-то.

+4

вам нужно изменить SortInstrcution to SortInstruction – Timmerz

3

Мне понравилась работа выше - большое спасибо! Я взял на себя смелость добавить пару вещей:

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

Это действительно приятно. Я угадываю, почему вы отделили AddSortExpression и Sort, чтобы вы могли отключить сортировку по определенным «столбцам» в вашем наборе данных? –

Смежные вопросы