|
При загрузке данных из БД возникает исключение 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.