суббота, 13 октября 2012 г.

Strongly-typed Expand and async queries.

Очередной рабочий день разбирался с особенностями WCF Data Services.

Как я уже раньше писал, клиентская часть по умолчанию использует ленивую синхронную подгрузку. Чтобы этого избежать, пишутся простенькие методы, которые делают явную загрузку (eager loading). Благодаря избавлению от ObservableCollection<T>, это сделать легко.
Сегодня в дополнение к методу WhereAsync добавил методы FirstOrDefaultAsync и CountAsync, т.к. именно эти методы востребованы членами моей команды (судя по количеству не очень красивого кода, где они это сделали через WhereAsync). Код ниже.

Вторая важная особенность -- типизированный Expand. Наш предыдущий мега-проект работает на WCF RIA Services + SL, поэтому все уже привыкли писать инклуды. Тут, казалось бы, заменяем слово Include на Expand - и вуаля! Как бы не так.
.Expand(a => a.Author)
.Expand(a => a.Customers.Select(c => c.Person))
Первый работает, второй нет. Сегодня работая с проектом заметил, что коллеги решили не париться и писать строковые, не-типизированные Expand'ы. Кровь взыграла: как же так, нарушение идеологии!
Нашёл вот здесь код для примера, допилил чуток, и вот он - метод Include!
Если кому пригодится, кинул на pasteBin.

Третья важная особенность - это не EF. Поскольку все привыкли к риа сервисам, возникает куча проблем на ровном месте, как-то:
  • сущность уже загружена сервисом, как мне её выцепить по Id? Снова лезть на сервисы?
  • надо сделать отмену изменений свойств сущности (Modified -> Unchanged). Как, заново брать её от сервисов?
  • хз есть сущность или нет. Подгружаем на всякий случай -- падает Exception: The context is already tracking a different entity with the same resource Uri.
  • в модели много FK связей. Присвоение навигационного свойства не меняет скалярный Id и наоборот. SetLink иногда помогает, иногда нет.
  • как сделать SaveChanges только для одного графа, не затрагивая другой? Несколько контекстов и attach/detach? Вы это серьёзно?
  • как писать валидацию? Дублировать код с обеих сторон? Пока делаем client-side only, но есть идея делать всё в одном месте: серверный EF, автоматическая генерация некоторых проверок в SQL и прокидывание на клиента с возможностью расширения на клиенте.
Короче, проблем хватает, наверное интереснее будет реализовать подмножество функций EF для решения описанных проблем, благо T4 имеются.

понедельник, 8 октября 2012 г.

OData Client T4 template

Недавно начали писать приложение с использованием WCF Data Services. Сервисы получаются простыми, лёгкими, быстрыми - то что нужно для скоростного написания прототипа.

Недостатки, конечно, присутствуют. Вот что помешало работе в первый же день:
  • Синхронная ленивая загрузка данных.
  • Клиентские классы без возможности кастомизации.
  • Функционал контекста конечно далёк от EF и RIA Services.
  • Не прокидываются DataAnnotations, про которые знает EF.
Первый недостаток решается путём написания метода с названием типа WhereAsync, который к тому же легко сделать awaitable. Однако практически сразу приложение начало падать.
Дело в том, что в клиентских сущностях для коллекций используется класс DataServiceCollection<T> : ObservableCollection<T>. Решение мотивируется тем, что это позволяет напрямую биндить коллекции сущностей в UI. Однако класс ObservableCollection<T> не является thread-safe.
Что происходит: при материализации объектов, связанных с уже загруженными объектами, DataServiceContext вызывает метод InsertItem, который кидает InvalidOperationException.

Пришлось не побояться и попробовать T4 темплейты.
Столкнулся сразу с несколькими особенностями темплейтов:
  • Не подцепляется пространство имён. Логично, правда? Решается дописыванием строчки TransformContext.Namespace = "MyProject.Client.MyService";
  • Версия протокола определяется как V1, из-за чего компилятор тут же начинает ругаться на методы Any. Поскольку сервисы у нас свои и в том же солюшне, закомментарил строчку //version = dataServiceVersion.Major.ToString();
  • Не тащатся DefaultValue из edmx. Вот тут пришлось повозиться. Инструкция:
    1. В метод WriteTypeProperties для primitive types добавить:
      var structuralType = property as IEdmStructuralProperty;
      string defaultValue = string.Empty;
      if (structuralType != null && structuralType.DefaultValueString != null)
             defaultValue = structuralType.DefaultValueString.ToString();
    2. Этот самый defaultValue передавать в WriteTypeProperty и оттуда в WriteTypePropertyFields.
    3. В самом WriteTypePropertyFields убрать слово new и добавить его в другие WriteTypeProperty методы.
    4. Добавить обработчик строкового значения:
      if (!string.IsNullOrEmpty(defaultValue)
      && primitiveType.PrimitiveKind == EdmPrimitiveTypeKind.String)
      {
           defaultValue = "\"" + defaultValue + "\"";
      }
    5. В методе WriteTypeStaticCreateMethod не обрабатывать свойства, у которых есть defaultValue:
      var structuralType = property as IEdmStructuralProperty;
      if (structuralType == null || structuralType.DefaultValueString == null)
               nonNullableProps.Add(property); 
        
Благодаря темплейтам поменял тип коллекций на List<T>, и асинхронная загрузка стала работать как ожидается. Я уверен, что до выкладывания в продакшн возникнут и другие проблемы, но если есть полный контроль над процессом (T4, исходники на codeplex) - большинство проблем можно решить.
P.S. Столько удовольствия приносят эти "развлечения", когда на день-два приходится отвлечься от "экстенсивного" кодинга (генерации контента) к "интенсивному" почти-программированию (исследованию технологий).

суббота, 2 июня 2012 г.

T4Toolbox для VS 2012 RC

Надо с чего-то начинать писать блоги.
Наша команда использует T4 в повседневной работе для генерации метаданных к сущностям EF. Когда вышел VS 11 beta, мы обрадовались, что проекты завелись без проблем, но полноценному переходу мешало отсутствие готового T4Toolbox. Исходники не помогли делу, т.к. в SDK отсутствовали компоненты TextTemplating.

С выходом RC появилось желание проверить, как оно там. Потратив некоторое время на поднятие свежей виртуалки, мне удалось решить задачу. Готовый рецепт:
1. Установить оба SDK и WIX.
2. Убить ссылку на StyleCop (или обновить до 4.7).
После этого проекты загрузятся.
3. Поменять все зависимости
4. Убрать CodeAnalysis при билде
5. Suppress warning 3008
Оно даже сбилдится (под администратором). Тут самое интересное.
6. Редактируем в инсталляторе VisualStudio10.wxs, меняя цифру 10 на 11.
7. Меняем путь для значений реестра на 'HKCU\Software\Microsoft\VisualStudio\11.0_Config'
Билдим, устанавливаем, проверяем на рабочем проекте - работает!
Теперь переходу на VS 11 ничего не мешает.

Итого за 4 часа получил установщик, который буду раздавать команде. Думаю, Олег вскоре выпустит обновление, тогда возьмём его бинарники.

Вот сам установщик, может кому пригодится.