При загрузке данных из БД возникает исключение InvalidOperationException с сообщением: Элемент коллекции должен быть пустым перед использованием ItemsSource. Знаю, что для заполнения DataGrid можно использовать коллекции Items или ItemsSource, но одновременно их использовать нельзя: если задано значение для свойства ItemsSource и в коде C# добавляется элемент в Items, возникает исключение. |
Разработка 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.
Запросы службы данных 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.