понедельник, 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. Столько удовольствия приносят эти "развлечения", когда на день-два приходится отвлечься от "экстенсивного" кодинга (генерации контента) к "интенсивному" почти-программированию (исследованию технологий).

Комментариев нет:

Отправить комментарий