Выражения-подзапросы в фильтрах

Для фильтрации списков и деревьев на клиенте используются 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));

}

35_important

Данный метод работает только в LINQ-запросах, выполняемых на клиенте. В скриптах следует по-прежнему использовать DataContext.GetTable().

На сервере добавлен перехватчик LINQ-запросов, выполняемых с клиента. Перехватчик обрабатывает LINQ-выражения, заменяя одни поддеревья другими. В данном случае, встретив вызов метода QueryExtensions.GetTable(), перехватчик заменяет его вызовом extension-метода DataContext.GetTable(). Поскольку перехватчик обрабатывает только клиентские запросы, на сервере метод QueryExtensions.GetTable приведет к ошибке трансляции в SQL.