Сильно типизированная динамическая сортировка 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, где каждая полевая информация содержит значения MethodInfo и Type полей, переданных в AddSort:

 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 } 

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

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

Самый простой способ сделать это – заставить функцию AddSort () использовать выражение > вместо Func. Это позволяет вашему методу сортировки проверять выражение для извлечения имени свойства, которое вы хотите сортировать. Затем вы можете сохранить это имя внутри строки в виде строки, поэтому сохранение очень просто, и вы можете использовать алгоритм сортировки, с которым вы связаны, но вы также получаете проверку типа и проверку времени компиляции для действительных имен свойств.

 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) 

Вот досада! Я должен научиться читать спецификации из конца в конец 🙁

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

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

О, почти забыл код:

UPDATE: IQueryable общий код и использовал 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; } } }

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

Он обеспечивает двунаправленную сортировку, а также решение проблемы наизнанку. Значит, для меня не имело большого значения, что новый сортировщик должен быть создан для каждого несортированного списка определенного типа. Почему этот сортируемый список не может быть передан в сортировщик. Это значит, что мы могли бы создать экземпляр подписчика Сортировщика для наших разных типов …

Просто идея:

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

  • Вычислить отличие от предыдущего элемента с LINQ
  • Linq-to-entities - метод Include () не загружается
  • Linq Query Group и выбор первых элементов
  • Заполнение DataSet или DataTable из набора результатов запроса LINQ
  • Entity Framework linq query Включает () несколько дочерних объектов
  • XDocument, содержащий пространства имен
  • Метод count count vs Count ()?
  • LINQ Single vs First
  • Выражение рекурсии в LINQ
  • Entity Framework 6 Первые пользовательские функции кода
  • Результат запроса нельзя перечислить более одного раза
  • Давайте будем гением компьютера.