Для фильтрации списков и деревьев на клиенте используются LINQ-выражения. В большинстве ситуаций для фильтрации достаточно простых предикатов, включающих константы или списки значений. К примеру, чтобы вывести товары из заданного списка категорий, достаточно вот такого фильтра:
private void GridPanel_ApplyCustomFilter(object sender, CustomFilterEventArgs args) { // filter by article groups not including subfolders var groups = ArticleGroupsTreePanel.SelectedRoots; args.AddFilter<Article>(ar => groups.Contains(ar.GroupID)); } |
Проблема появляется, когда выражению-фильтру требуется обращение к еще одной таблице. Попытка использовать DataContext.GetTable() к ожидаемым результатам не приводит, поскольку LINQ-выражения на клиенте имеют ряд ограничений, связанных с сериализацией и передачей их на сервер. Например, вот этот запрос приведет к ошибке сериализации:
private void GridPanel_ApplyCustomFilter(object sender, CustomFilterEventArgs args) { var groups = ArticleGroupsTreePanel.SelectedRoots;
// filter by article groups including subfolders args.AddFilter<Article>(ar => groups.Contains(ar.GroupID) || DataContext.GetTable<ArticleGroupTreeLink>() .Where(t => groups.Contains(t.AncestorID)) .Select(t => t.DescendantID) .Contains(ar.GroupID)); } |
Это происходит из-за того, что DataContext в выражении является свойством формы. На самом деле это this.DataContext, где this – форма, на которой лежит элемент управления с фильтром. Выражение захватывает ссылку this как Expression.Constant(typeof(ТипФормы), this), и попытка передачи этого выражения на сервер терпит неудачу, так как ТипФормы на сервере недоступен.
Чтобы включить в фильтр ссылку на таблицу, достаточно заменить DataContext.GetTable статическим методом QueryExtensions.GetTable (необходимо подключить using Ultima.Data). Использовать этот метод можно как в фильтрах, так и в обычных LINQ-запросах, за исключением самой первой таблицы в запросе. Фильтр, показанный в предыдущем примере, примет вид:
private void GridPanel_ApplyCustomFilter(object sender, CustomFilterEventArgs args) { var groups = ArticleGroupsTreePanel.SelectedRoots;
// filter by article groups including subfolders args.AddFilter<Article>(ar => groups.Contains(ar.GroupID) || QueryExtensions.GetTable<ArticleGroupTreeLink>() .Where(t => groups.Contains(t.AncestorID)) .Select(t => t.DescendantID) .Contains(ar.GroupID)); } |
Такие фильтры можно использовать не только для иерархических таблиц. Можно, например, фильтровать список товаров по списку онлайн-категорий. Онлайн-категории связаны с обычными категориями через таблицу ArticleGroupsToOnline:
private void GridPanel_ApplyCustomFilter(object sender, CustomFilterEventArgs args) { // filter by online groups var onlineGroups = OnlineGroupsTreePanel.SelectedList;
args.AddFilter<Article>(ar => QueryExtensions.GetTable<ArticleGroupsToOnline>() .Where(link => onlineGroups.Contains(link.OnlineGroupID)) .Select(link => link.ArticleGroupID) .Contains(ar.GroupID)); } |
Данный метод работает только в LINQ-запросах, выполняемых на клиенте. В скриптах следует по-прежнему использовать DataContext.GetTable(). На сервере добавлен перехватчик LINQ-запросов, выполняемых с клиента. Перехватчик обрабатывает LINQ-выражения, заменяя одни поддеревья другими. В данном случае, встретив вызов метода QueryExtensions.GetTable(), перехватчик заменяет его вызовом extension-метода DataContext.GetTable(). Поскольку перехватчик обрабатывает только клиентские запросы, на сервере метод QueryExtensions.GetTable приведет к ошибке трансляции в SQL. |