Общие сведения
В первой части мы ознакомились, что за зверь такой WCF RIA Services, и создали приложение, которое можно коротко назвать, как «Hello world». Все было очень просто, красиво, а так же «drag and drop». Однако для создания настоящих, больших и функциональных приложений нужно еще много чего выучить, например какие возможности доступны в клиентской части, и как их использовать. В этом уроке мы копнем немножко глубже в области получения данных на клиентской стороне. Так же Вы узнаете о конвенциях, которые используются для методов, получающих данные, определенные в серверной части, как их настраивать, используя атрибуты. Расскажу о некоторых программных реализациях, которые можно использовать для получения данных на стороне клиента и как использовать данные, которые используют отличные от Entity Framework источники.
Начальной точкой для изучения всего вышесказанного является проект, который был создан в предыдущей, первой части.
Шаг 1: Добавление в доменную службу параметризованного запроса
После того, как мы создали службу домена в предыдущем уроке, она имела такой вид:
public IQueryable<Task> GetTasks()
{
return this.ObjectContext.Tasks;
}
Этот метод возвращает все записи из таблицы Tasks как перечислимый тип IQueryable и не принимает аргументов. Если же возникает необходимость получить на стороне клиента более специфический запрос, то необходимо добавить метод в службу домена, который в свою очередь будет автоматически доступен клиенту. Так же есть возможность возвращать данные типа IEnumerable, но если основной поставщик данных (Entity Framework или LINQ to SQL) возвращает IQueryable, то это нужно будет реализовать отдельно.
Сейчас модернизируем наше приложение таким образом, что б добавить возможность получать события в заданном диапазоне дат. Для этого в службу домена добавьте следующий код:
public IQueryable<Task> GetTasksByStartDate(
DateTime lowerDateTimeInclusive,
DateTime upperDateTimeInclusive)
{
return this.ObjectContext.Tasks.Where(
t => t.StartDate >= lowerDateTimeInclusive && t.StartDate <= upperDateTimeInclusive);
}
Шаг 2: Изменения UI для получения конкретизированных данных.
Для этого необходимо добавить несколько элементов на нашу страницу. Откройте MainPage.xaml. Сместите Grid таким образом, что б над ним было свободное место для добавления элементов. С помощью «Drag and drop» добавьте из Панели инструментов два TextBox и один Button. Дайте имена созданным элементам, соответственно: lowerDate, upperDate, searchButton.
Измените Content кнопки на «Search By Date».
Шаг 3: Получение результата запроса на клиенте, используя DomainContext
Как Вы помните, DomainContext – это автогенерируемый код, который представляет отражение возможностей серверной части приложения. То есть, если сервис домена называется TasksDomainService, то клиентская часть приложения называется TasksDomainContext. Он предоставляет возможность обращаться к серверной части в асинхронном режиме, а так же сохранять изменения, которые были внесены на клиентской стороне.
Так, каждый раз, когда происходит получение сущностей от сервера путем выполнения запросов через контекст домена, в нем так же сохраняются ссылки на эти объекты и некоторая другая дополнительная информация, помогающая отслеживать изменения. И в случае изменения этих объектов, контекст домена будет отправлять на сервер только те сущности, которые нуждаются в обновлении. Про это в следующем уроке. А сейчас давайте сфокусируемся на получении данных.
Добавьте на только что созданную кнопку событие «Click». А в обработчик этого события добавьте следующий код:
private void searchButton_Click(object sender, RoutedEventArgs e)
{
DateTime lowerDateVal;
DateTime upperDateVal;
GetDates(out lowerDateVal, out upperDateVal);
TasksDomainContext context = new TasksDomainContext();
taskDataGrid.ItemsSource = context.Tasks;
EntityQuery<Task> query = context.GetTasksByStartDateQuery(lowerDateVal, upperDateVal);
LoadOperation<Task> loadOp = context.Load(query);
}
Обратить внимание следует на последние 4 строчки кода, так как первые 3 – просто получение введенной даты из соответствующих полей, и выглядит следующим образом:
private void GetDates(out DateTime lowerDateVal, out DateTime upperDateVal)
{
lowerDateVal = DateTime.MinValue;
upperDateVal = DateTime.MaxValue;
if (!string.IsNullOrWhiteSpace(lowerDate.Text))
{
lowerDateVal = DateTime.Parse(lowerDate.Text);
}
if (!string.IsNullOrWhiteSpace(upperDate.Text))
{
upperDateVal = DateTime.Parse(upperDate.Text);
}
}
Для того, что б можно было вызвать серверную часть необходимо создать экземпляр контекста домена. Скорее всего Вы захотите создать этот экземпляр таким образом, что б он прожил столько, сколько нужно, потому, что именно благодаря ему сохраняется вся необходимая информация для отслеживания изменений. Например, при использовании паттерна MVVM хорошей идеей будет создать и переместить экземпляр контекста домена в свойство модели вида. Более подробно поговорим на эту тему в четвертом уроке.
Контекст домена предоставляет коллекции сущностей, которые в свою очередь включают в себя наборы коллекций, возвращаемых доменом сервиса после отработки запросов. На данном этапе домен сервиса предоставляет только коллекцию данных из таблицы Tasks.
Обратите внимание, что данный код заменяет ItemsSource в DataGrid коллекцией Tasks до того, как производится какой либо запрос на получение данных. Так нужно в силу того, что запросы, отправляемые контекстом домена являются асинхронными, и заменят содержимое коллекции лишь после прихода ответа от сервера. Данная коллекция реализует интерфейс INotifyCollectionChanged и способна вызывать события, направленные на DataGrid (или любой связанный элемент) для обновления его содержимого.
Затем идет получение EntityQuery от контекста с аргументами, которые передаются в соответствующий метод на сервере. То есть тут мы указываем, что бы мы хотели получить, но вызов еще не происходит. И наконец-то происходит получение LoadOperation от контекста, путем вызова метода Load. Вот тут уже происходит непосредственно обращение к серверу в теневом потоке, а когда запрос выполниться на сервере и придет ответ с необходимыми данными — в потоке UI автоматически обновится вся информация на актуальную.
В принципе, это тоже самое, что происходит под покровом DomainDataSource из первого урока.
Шаг 4: Добавление метода, возвращающего единственный объект
Чтоб метод не возвращал коллекцию, его нужного так и объявить. Далее, после компиляции он будет так же доступен и на стороне клиента:
public Task GetTask(int taskId)
{
return this.ObjectContext.Tasks.FirstOrDefault(t => t.TaskId == taskId);
}
Конвенция о настройках
Есть такая тенденция в. NET программировании, когда уменьшается количество кода, необходимого явно прописывать для обеспечения нужных настроек, однако приходиться следовать некоторым правилам именования. Возможно Вы этого еще не заметили, бегло просматривая код службы домена, однако там уже это применяется. Это не очень очевидно на методах, которые извлекают данные, так как метод с любым названием будет отрабатывать одинаково хорошо. Однако для операций обновления, удаления и создания нужно придерживаться определенных правил, основываясь на которых WCF RIA Services ищет и исполняет нужные методы, например, такие как UpdateTask, InsertTask, DeleteTask. Возможно указание нескольких вариантов для каждой операции.
И не забывайте про возможности настройки используя атрибуты. Например для стороны осуществляющей запросы, декорировать методы можно атрибутом [Query], что сделает Ваш код более читабельным и легким для понимания. Нужно помнить, что и тип возвращаемого значения тоже многое говорит о назначении метода. Например, метод возвращающий IQueryable, IEnumberable — однозначно отвечает за извлечение данных. Однако преимущество использования атрибута Query еще в том, что он поддерживает еще некоторые дополнительные параметры, такие как максимальное количество возвращаемых результатов, которые ограничит их количество, даже если из БД вернется после запроса больше необходимого.
Пользовательские доменные службы
А что делать, если Вы не хотите использовать Entity Framework? Как вариант можно воспользоваться LINQ to SQL. Я бы советовал использоваться Entity Framework с новыми проектами. И по возможности со старыми.
LINQ to SQL поддерживается в WCF RIA Services через Toolkit.
Однако многие используют другие стратегии доступа к данным, например nHibernate и другие источники данных, такие как Oracle, MySQL и многие другие. Все это можно так же использовать в WCF RIA Services, но нужно определить объекты данных как простые сущности и использовать их далее в коде как сочтете нужным. Такой подход называется POCO (Plain Old CLR Objects) доменных служб.
Для создания такой доменной службы, класс домена службы нужно наследовать напрямую от DomainService, а не от LinqToEntitiesDomainService. В помощнике создания, кода создаете домен сервиса выберите «Пустой класс службы домена» в выпадающем меню «Доступные классы контекста». Затем определяете сущности, возвращаете коллекции IEnumerable этих сущностей, если Ваш источник данных не поддерживает IQueryable и делайте все, что нужно, в методах на основе источника данных.
Далее Вы должны спроектировать Ваш сервис согласно исходным данным и объектам, с которыми Вы работаете. Эти объекты должны обладать свойствами других объектов или наборов встроенных в .NET поддерживаемых типов.
Ключ, которым Вы обрамляете свои сущности, определяет, что они должны иметь ключевые свойства, которые однозначно их идентифицируют. Обычно «int» или «Guid». Указываете это свойство как атрибут [Key].
Ниже приведен код, который показывает, как объявлять POCO домен службы и его сущности:
public class Foo
{
[Key]
public int FooId { get; set; }
public string Name { get; set; }
}
[EnableClientAccess()]
public class MyPOCODomainService : DomainService
{
public IEnumerable<Foo> GetFoos()
{
return new List<Foo> { new Foo { FooId = 42, Name = "Fred" } };
}
}
В дополнении к атрибуту [Key], если у Вас имеются свойства, которые сами являются типами сущностей(связанные сущности или объединения), то необходимо добавить свойства к этой сущности, например, ID, которое устанавливает соответствие ключевых полей. Кроме того, непосредственно у свойства сущности необходимо указать атрибут [Association], которое показывает ID свойства, которое устанавливает связи(отношения) и сообщает, что это и есть внешний ключ. У свойства сущности еще нужно указать атрибут [Include], что б дочерняя сущность так же всегда извлекалась, когда извлекается родительская по средству RIA Services.
После запуска проекта получите такое окошко, где можно проводить выборку по дате:
Видео для этого урока
Исходники
На Github
Автор: struggleendlessly