Я столкнулся с проблемой при попытке реализовать логику сортировки:
Представьте себе модель Person, состоящую из нескольких одинаковых полей:
public class Person
{
public string firstName { get; set; }
public string lastName { get; set; }
public DateTime DOT { get; set; }
// ...
}
и еще пара человек:
ObservableCollection<Person> Persons = new();
var jane = new Person()
{
firstName = "Jane",
lastName = "Doe",
DOT = DateTime.Parse("01/01/2002")
};
var john = new Person()
{
firstName = "John",
lastName = "Doe",
DOT = DateTime.Parse("31/12/2002")
};
var george = new Person()
{
firstName = "George",
lastName = "Orwell",
DOT = DateTime.Parse("25/06/1903")
};
// Add persons to a collection:
Persons.add(jane);
Persons.add(john);
Persons.add(george);
Теперь несколько человек хранятся в коллекции и должны быть отсортированы. Однако, пользователь должен иметь возможность выбирать, для какого поля должна применяться сортировка по возрастанию или убыванию, в каком порядке и если вообще должна применяться.
Несколько примеров сортировки людей:
Порядок упорядочивает приоритет, поэтому первая выбранная сортировка имеет более высокий приоритет, чем последующие сортировки.
Теперь, насколько я понимаю, с помощью LINQ можно сделать что-то вроде следующего:
Persons = new(
Persons.OrderBy(x => x.firstName)
.ToList()
);
Persons = new(
Persons.OrderBy(x => x.firstName)
.ThenByDescending(x => x.lastName)
.ToList()
);
Persons = new(
Persons.OrderByDescending(x => x.firstName)
.ThenBy(x => x.DOT)
.ToList()
);
Persons = new(
Persons.OrderByDescending(x => x.firstName)
.ThenBy(x => x.lastName)
.ThenBy(x => x.DOT)
.ToList()
);
Persons = new(
Persons.OrderBy(x => x.DOT)
.ThenByDescending(x => x.firstName)
.ToList()
);
Persons = new(
Persons.OrderByDescending(x => x.lastName)
.ThenByDescending(x => x.firstName)
.ThenBy(x => x.DOT)
.ToList()
);
Мой вопрос сейчас: Как объединить эти заказы в одну чистую функцию? Как я могу динамически выбирать любой порядок по возрастанию. или дес. например, просматривая список сортировок?
ОБНОВЛЯТЬ: Для наглядности попробую привести реальный пример: Представьте, что сортировка управляется кнопками. При нажатии кнопки изменяется соответствующее логическое состояние, которое представляет соответствующее состояние по возрастанию/убыванию поля Person, например.
public class ButtonObject
{
public string? ButtonText { get; set; }
public IRelayCommand? ButtonCommand { get; set; }
public bool? ButtonIsVisible { get; set; } = true;
public bool? IsOrderedAscending { get; set; } = true;
public string? ButtonGlyph { get; set; }
}
Таким образом, в соответствии с именем, фамилией и DOT будут созданы следующие три кнопки, которые также используют определенные поля поддержки:
// Up- and Down-Button Glyphs from Google Material Icons:
private const string Keyboard_arrow_down = "\ue313";
public const string Keyboard_arrow_up = "\ue316";
private const string
[ObservableProperty]
private ObservableCollection<ButtonObject> _buttons = new();
// in the Constructor of the ViewModel:
{
_firstNameButton = new ButtonObject
{
ButtonText = "FirstName",
ButtonCommand = OrderByFirstNameCommand,
ButtonGlyph = Keyboard_arrow_down,
};
_lastNameButton = new ButtonObject
{
ButtonText = "LastName",
ButtonCommand = OrderByLastNameCommand,
ButtonGlyph = Keyboard_arrow_down,
};
_DOTButton = new ButtonObject
{
ButtonText = "DOT",
ButtonCommand = OrderByDOTCommand,
ButtonGlyph = Keyboard_arrow_down,
};
// add all buttons to the collection:
Buttons.Add(_firstNameButton);
Buttons.Add(_lastNameButton);
Buttons.Add(_DOTButton);
}
Теперь представьте, что кнопки можно динамически добавлять в соответствующий CollectionView в представлении, поэтому порядок может быть таким:
<------------------> <------------------> <------------------>
| FirstName Button | | LastName Button | | DOT Button |
<------------------> <------------------> <------------------>
или это может быть:
<------------------> <------------------> <------------------>
| LastNameButton | | FirstNameButton | | DOT Button |
<------------------> <------------------> <------------------>
или даже:
<------------------> <------------------>
| DOT Button | | LastName Button |
<------------------> <------------------>
При нажатии на кнопки изменяется логическое свойство по возрастанию, а также соответствующий глиф, чтобы соответствовать порядку возрастания или убывания:
[RelayCommand]
private void OnSortByFirstName()
{
var _sortByFirstNameButton = Buttons[0];
if (_sortByFirstNameButton.IsSortedAscending)
{
_sortByFirstNameButton.IsSortedAscending = false;
_sortByFirstNameButton.ButtonGlyph = Keyboard_arrow_up;
}
else
{
_sortByFirstNameButton.IsSortedAscending = true;
_sortByFirstNameButton.ButtonGlyph = Keyboard_arrow_down;
}
UpdateSorting();
}
[RelayCommand]
private void OnSortByLastName()
{
var _sortByLastNameButton = Buttons[1];
if (_sortByLastNameButton.IsSortedAscending)
{
_sortByLastNameButton.IsSortedAscending = false;
_sortByLastNameButton.ButtonGlyph = Keyboard_arrow_up;
}
else
{
_sortByLastNameButton.IsSortedAscending = true;
_sortByLastNameButton.ButtonGlyph = Keyboard_arrow_down;
}
UpdateSorting();
}
[RelayCommand]
private void OnSortByDOT()
{
var _sortByDOTButton = Buttons[2];
if (_sortByDOTButton.IsSortedAscending)
{
_sortByDOTButton.IsSortedAscending = false;
_sortByDOTButton.ButtonGlyph = Keyboard_arrow_up;
}
else
{
_sortByDOTButton.IsSortedAscending = true;
_sortByDOTButton.ButtonGlyph = Keyboard_arrow_down;
}
UpdateSorting();
}
Теперь меня беспокоит функция UpdateSorting()
. Ему необходимо учитывать порядок и состояние всех кнопок и, следовательно, переставлять людей.
Если бы была только одна кнопка, это могло бы быть что-то вроде этого:
private void UpdateSorting()
{
if (Buttons[0].IsSortedAscending)
{
Persons = new(
Persons.OrderBy(x => x.firstName).ToList()
);
}
else
{
Persons = new(
Persons.OrderByDescending(x => x.firstName).ToList()
);
}
}
Однако для двух кнопок код раздувается уже до следующего:
private void UpdateSorting()
{
if (Buttons[0].IsSortedAscending)
{
if (Buttons[1].IsSortedAscending)
{
Persons = new(
Persons
.OrderBy(x => x.firstName)
.ThenBy(x => x.lastName)
.ToList()
);
}
else
{
Persons = new(
Persons
.OrderBy(x => x.firstName)
.ThenByDescending(x => x.lastName)
.ToList()
);
}
}
else
{
if (Buttons[1].IsSortedAscending)
{
Persons = new(
Persons
.OrderByDescending(x => x.firstName)
.ThenBy(x => x.lastName)
.ToList()
);
}
else
{
Persons = new(
Persons
.OrderByDescending(x => x.firstName)
.ThenByDescending(x => x.lastName)
.ToList()
);
}
}
}
С каждой дополнительной кнопкой количество необходимых операторов if/else удваивается: это не может быть идиоматическим или лучшим решением.
Непонятно, что именно вы хотите сделать. Можете ли вы показать несколько примеров кода, который вы хотите написать, используя «одну чистую функцию», которую вы пытаетесь создать?
Каждая операция LINQ возвращает новый запрос. Вы можете добавить Where
, OrderBy
или ThenBy
по мере необходимости, в зависимости от параметров. Не обязательно указывать всех операторов сразу. Вы можете написать, например if (somParam) { query=query.Where();} if (someOrder){ query=query.OrderBy(...);}
Другая оптимизация — сохранить Expression<Func<TSource,TKey>>
, необходимый для OrderBy
, как свойство ButtonObject
и просто использовать его для построения запроса. Код сортировки может просто перебирать кнопки и применять правильный оператор и функцию, не зная, какое поле используется для сортировки.
Вы можете попробовать этот код:
public static IOrderedEnumerable<T> DynamicSort<T>(this IEnumerable<T> input, params (Func<T,object> selector, SortOrder sortOrder)[] parameters)
{
IOrderedEnumerable<T> result = null;
foreach(var p in parameters)
{
if (result == null)
{
result = p.sortOrder == SortOrder.Asc ? input.OrderBy(p.selector) : input.OrderByDescending(p.selector);
}
else
{
result = p.sortOrder == SortOrder.Asc ? result.ThenBy(p.selector) : result.ThenByDescending(p.selector);
}
}
return result;
}
Вы вызываете этот метод, например. к
persons.DynamicSort(
(new Func<Person,object>(x => x.firstName), SortOrder.Asc),
(new Func<Person,object>(x => x.lastName), SortOrder.Desc),
(new Func<Person,object>(x => x.DOT), SortOrder.Asc));
Это будет преобразовано в запрос LINQ
persons.OrderBy(x => x.firstName).ThenByDescending(x => x.lastName).ThenBy(x => x.DIT);
Этот метод к настоящему времени полностью разработан. Вам следует добавить несколько проверок, имеет ли ввод значение NULL или в массиве нет элементов. Более того, поскольку методы селектора имеют объект в качестве возвращаемого типа, типы значений будут заключены в рамку. Это означает, что при сборке мусора требуется больше работы, и это снижает производительность. Но, возможно, это хорошая отправная точка для вас.
Онлайн-демо: https://dotnetfiddle.net/6neHxr
Мне кажется, все в порядке; аргумент params
должен быть заполнен из графического интерфейса.
Это работает только с IEnumerable, а не с IQueryable, и даже там это приведет к упаковке типов значений. Это не удастся с EF или любым другим поставщиком, кроме LINQ-to-Objects. Исправить это легко, используйте IQueryable вместо IEnumerable, НЕ используйте object
где угодно и используйте Expression<Func<TSource,TKey>>
@PanagiotisKanavos Сначала я подумал о Func<TSource,TKey>
. Но если вы хотите упорядочить строку, а затем целое число, это не сработает. Вот почему я выбрал объект в качестве возвращаемого типа, несмотря на его недостаток. Кроме того, из примера ОП было ясно, что он хочет отсортировать ObservableCollection
, который хранится локально в памяти и не использует EF.
Возможно, вы можете использовать System.Linq.Dynamic