Опубликован: 08.07.2011 | Доступ: свободный | Студентов: 1772 / 93 | Оценка: 4.15 / 4.08 | Длительность: 15:28:00
Лекция 6:

Разработка Silverlight-приложений

Формирование запросов к службе данных

Сервисы WCF Data Services позволяют выполнять запросы к службе данных из клиентского приложения на основе библиотеки .NET Framework с использованием сформированных клиентских классов службы данных. Клиентская библиотека преобразует запрос, определенный на клиенте как экземпляр класса DataServiceQuery, в сообщение запроса HTTP GET. Библиотека получает ответное сообщение и преобразует его в экземпляры классов клиентской службы данных. Эти классы отслеживаются экземпляром DataServiceContext, которому принадлежит объект DataServiceQuery.

Универсальный класс DataServiceQuery представляет запрос, который возвращает коллекцию экземпляров заданного типа сущности. Запрос к службе данных всегда относится к контексту существующей службы данных. Этот контекст поддерживает URI службы и сведения о метаданных, необходимые для создания и выполнения запроса.

При выполнении запросов можно явно вызывать метод асинхронного вызова BeginExecute с объектом DataServiceQuery<T> или выполнить запрос LINQ к именованному объекту DataServiceQuery<T>, который получен из контекста DataServiceContext.

Использование метода асинхронного вызова BeginExecute

Для загрузки в приложение таблицы Employee базы данных Personal добавим в коде класса MainPage ссылки на библиотеку Data.Services.Client и созданный сервис данных Personal

using System.Data.Services.Client;
using SilverlightAppPersonal.Personal;

а также контекст сущностей context сервиса модели данных EmployeeEntities и экземпляр коллекции employees класса DataServiceCollection<T>

EmployeeEntities context;
DataServiceCollection<Employee> employees;

После внесенных изменений код класса MainPage будет следующим:

using System.Windows.Controls;
using System.Data.Services.Client;
using SilverlightAppPersonal.Personal;

namespace SilverlightAppPersonal
{
public partial class MainPage : UserControl
    {
        EmployeeEntities context;
        DataServiceCollection<Employee> employees;
        public MainPage()
        {
            InitializeComponent();
        }
    }
}

Класс DataServiceCollection<T> представляет коллекцию динамических сущностей, обеспечивающую выдачу уведомлений при добавлении и удалении из неё элементов или при обновлении списка. Службы данных WCF используют класс DataServiceCollection<T> с целью поддержки привязки извлекаемых из базы данных для элементов управления Silverlight.

При загрузке главной страницы необходимо выполнить следующие подготовительные действия:

  • создать экземпляр контекста сущностей сервиса модели данных Personal ;
  • создать экземпляр коллекции employees ;
  • определить событие загрузки коллекции employees.

Добавим в XAML-код описание события Loaded класса MainPage со ссылкой на обработчик MainPage_Loaded:

Loaded="MainPage_Loaded"

В коде класса MainPage добавим обработчик MainPage_Loaded:

private void MainPage_Loaded(object sender, RoutedEventArgs e)
        {
            context = new EmployeeEntities(new Uri(
"WfcDataServicePerson.svc", UriKind.Relative));
            employees = new DataServiceCollection<Employee>();
            employees.LoadCompleted += new 
               EventHandler<LoadCompletedEventArgs>(employees_LoadCompleted);
            ButtonOpen.IsEnabled = true;
        }

С учетом того, что при генерации страницы все кнопки создаются как недоступные, то после загрузки необходимо кнопку загрузки данных из базы сделать доступной:

ButtonOpen.IsEnabled = true;

Загрузка данных из базы данных в приложение будет проведена по щелчку кнопки "Загрузить". В XAML-описании добавим для кнопки ButtonOpen обработчик для события Click - Open_Click.

<Button Content="Загрузить" Height="25" Name="ButtonOpen" Margin="35,0,0,0" 
        Padding="10,5,10,5" Click="Open_Click" />

В код класса MainPage добавим метод Open_Click обработчика события Click кнопки ButtonOpen.

private void Open_Click(object sender, RoutedEventArgs e)
  {
    ResetBindingData();
    DataServiceQuery<Employee> queryEmployee = 
                    context.Employees.AddQueryOption("$orderby", "EmployeeSurname");
    try
    {
        queryEmployee.BeginExecute(OnEmployeeQueryComplete, queryEmployee);
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}

Метод ResetBindingData() предназначен для очистка коллекции данных employees, списков сотрудников listBoxEmployees и должностей comboBoxTitle:

private void ResetBindingData()
{
   employees.Clear();
   listBoxEmployees.ItemsSource = null;
   comboBoxTitle.ItemsSource = null;
   LayoutRoot.UpdateLayout();
}

Далее формируется запрос queryEmployee на получение данных, при этом задается режим сортировки данных по фамилии сотрудника ( EmployeeSurname ):

DataServiceQuery<Employee>  queryEmployee = 
            context.Employees.AddQueryOption("$orderby", "EmployeeSurname");

С помощью метода AddQueryOption в запрос добавляются параметры. Службы данных WCF поддерживают следующие параметры запросов, приведенные в табл. 7.1.

Таблица 7.1. Параметры запросов протокола Open Data
Параметр запроса Описание
$orderby Определяет порядок сортировки по умолчанию для сущностей в возвращенном канале
$top Указывает количество сущностей, которые необходимо включить в возвращаемый канал
$skip Указывает количество сущностей, которые необходимо пропустить перед возвратом сущностей в канал
$filter Определяет выражение, фильтрующее сущности, которые возвращаются в канал на основе определенного критерия. Этот параметр запроса поддерживает набор операторов логического сравнения, арифметических операторов и заранее заданных функций запроса, которые используются для оценки критерия фильтра
$expand Указываются связанные сущности, возвращаемые запросом. Связанные сущности включаются либо в качестве канала, либо в качестве записи, встроенной в сущность, возвращаемую запросом
$format Указывает формат возвращаемого канала. По умолчанию для каналов задается формат Atom
$select Указывает проекцию, определяющую свойства сущности, возвращаемые в проекции. По умолчанию в канале возвращаются все свойства сущности
$inlinecount Запрашивает включение в поток количества сущностей, возвращаемых в канале

Запросы службы данных WCF для Silverlight выполняются асинхронно. Это реализуется методом BeginExecute(), который использует в качестве параметров делегат OnEmployeeQueryComplete и запрос queryEmployee.

queryEmployee.BeginExecute(OnEmployeeQueryComplete, queryEmployee);

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

private void OnEmployeeQueryComplete(IAsyncResult result)
{
    Dispatcher.BeginInvoke(() =>
    {
        DataServiceQuery<Employee> queryEmployee = 
                             result.AsyncState as DataServiceQuery<Employee>;
        try
        {
            employees.LoadAsync(queryEmployee);
        }
        catch (DataServiceQueryException ex)
        {
            MessageBox.Show(string.Format("Ошибка запроса в БД: {0} - {1}",
            ex.Response.StatusCode.ToString(), ex.Response.Error.Message));
        }
    });
}

На данном этапе нам осталось добавить в код класса MainPage делегат employees_LoadCompleted для события LoadCompleted коллекции employees, в котором после завершения формирования коллекции employees задается источник данных для списка listBoxEmployees и производится выделение первого элемента списка.

private void employees_LoadCompleted(object sender, LoadCompletedEventArgs e)
{
    if (e.Error == null)
    {
        if (employees.Continuation != null)
        {
            employees.LoadNextPartialSetAsync();
        }
        else
        {
            listBoxEmployees.ItemsSource = employees;
            listBoxEmployees.UpdateLayout();
            if (listBoxEmployees.Items.Count > 0)
                listBoxEmployees.SelectedIndex = 0;
            ButtonOpen.IsEnabled = false;
            
        }
    }
    else
    {
        MessageBox.Show(string.Format("Ошибка формирования коллекции: {0}",
                                                         e.Error.Message));
        ButtonOpen.IsEnabled = true;
    }
}

Использование запроса LINQ

Класс DataServiceQuery реализует интерфейс IQueryable, определяемый языком LINQ. Клиентская библиотека службы WCF Data Services может преобразовывать запросы LINQ к данным набора сущностей в URI, который представляет выражение запроса, вычисляемое для ресурса службы данных.

Сформируем запрос LINQ для получения приложением данных сущности Employee.

var allEmpoyees = from emp in context.Employees
                  orderby emp.EmployeeSurname
                  select emp;

Формирование коллекции employees класса DataServiceCollection<Employee> для приложения Silverlight может осуществляться только асинхронно, что определяет необходимость использования метода LoadAsync, который требует приведения параметра метода к типу DataServiceQuery<T>, в нашем случае к типу DataServiceQuery<Employee>.

employees.LoadAsync(allEmpoyees as DataServiceQuery<Employee>);

При асинхронной загрузке коллекции employees необходимо сформировать и обработать события загрузки коллекции типа employees.LoadCompleted для сущности Employee и события titles.LoadCompleted для сущности JobTitle.

Для случая использования запроса LINQ метод Open_Click() будет представлен следующим кодом.

private void Open_Click(object sender, RoutedEventArgs e)
{
    ResetBindingData();
    listBoxEmployees.ItemsSource = null;
    comboBoxTitle.ItemsSource = null;
    LayoutRoot.UpdateLayout();
    try
    {
        var allEmpoyees = from emp in context.Employees
                          orderby emp.EmployeeSurname
                          select emp;
        employees.LoadAsync(allEmpoyees as DataServiceQuery<Employee>);
        var allTitle = from titl in context.JobTitles
                       select titl;
        titles.LoadAsync(allTitle as DataServiceQuery<JobTitle>);
    }
    catch (DataServiceClientException ex)
    {
        MessageBox.Show(ex.Message);
    }
}

Следует отметить, что по сравнению с загрузкой данных с использованием метода асинхронного вызова BeginExecute, при применение LINQ-запросов отпадает необходимость использования делегатов OnEmployeeQueryComplete и OnTitleQueryComplete.

Александр Петров
Александр Петров

При загрузке данных из БД возникает исключение InvalidOperationException с сообщением: Элемент коллекции должен быть пустым перед использованием ItemsSource. Знаю, что для заполнения DataGrid можно использовать коллекции Items или ItemsSource, но одновременно их использовать нельзя: если задано значение для свойства ItemsSource и в коде C# добавляется элемент в Items, возникает исключение. 
Вопрос, как отследить и отключить добавление элемента в Items?

Максим Спиридонов
Максим Спиридонов

В пятой лекции на второй странице в компиляторе выскакивает ошибка в строчке :

ObjectQuery<Employee> employees = DataEntitiesEmployee.Employees;

Ошибка CS0029

Не удается неявно преобразовать тип "System.Data.Entity.DbSet<WpfApplProject.Employee>" в "System.Data.Entity.Core.Objects.ObjectQuery<WpfApplProject.Employee>".

в using прописал все как положено, здесь похоже именно с преобразованием типов проблемы