Make me async
Почти месяц назад я написал статью «Эволюция сервисных методов». В ней я пришел к реализации библиотеки MakeMeAsync, которая позволяет декорировать вызовы методов, делая их асинхронными. При этом код остаётся максимально читабельным и чистым, на мой взгляд.
Вот уже месяц как библиотека используется в реальном боевом проекте и показывает себя с лучшей стороны. Сначала удалось сократить размер самих сервисов, так как большая часть кода была инфраструктурной, затем, когда код стал ясным, я легко увидел, что сервисы не в полной мере выполняют свои задачи, а скорее делегируют их на слой ниже. Это меня огорчило, так как реализация является неправильным подходом; но порадовало, так как я смог это увидеть, и мне было психологически легче перенести код в нужное место, так как сервис выглядел компактным и понятным.
Кроме появления более четкой организации логики кода, значительно облегчилась задача по отображению ошибок, которые могли появиться во время выполнения задач в асинхронном режиме. Мне не надо было для каждой задачи прописывать/указывать метод обработки ошибки, можно было задать такой обработчик один раз в начале работы программы и все.
Итак, я думаю, что для начала стоит обновить информацию о том, как используется библиотека MakeMeAsync.
Эволюция сервисных методов
Сложность по шкале Microsoft 200-300
Давно хотел изложить свои мысли по поводу эволюции сервисных методов, но до последнего момента правильная, на мой взгляд, идея имела плохую реализацию – слишком трудоемкую и развесистую. На данный момент я пришел к некоторому сокращению кода и готов поделиться мыслями по поводу эволюции сервисных методов. Сразу скажу, что речь пойдет о доменных сервисах по обработке данных, так как чаще всего именно они занимаются обработкой данных в течение продолжительного времени. Однако описанные подходы наверно можно применять и к другим классам с «долгоиграющими» методами.
В данном случае под эволюцией подразумевается то, как инфраструктурно выглядит метод, а не его функциональная эволюция (рефакторинг). Описывать, как всегда, я собираюсь свой личный опыт, то, на чем я набил шишки.
Простейшие
Начиная со школы или университета, и в продолжение достаточно долгого времени мы пишем только однопоточные приложения. Все действия в них выполняются последовательно, синхронно. В этом нет ничего плохого в целом, многие методы и библиотеки работают синхронно и это хорошо. Не стоит городить сложность там, где она не нужна. Итак, простейший сервис может выглядеть следующим образом:
public class Service {
public void Method() {
// do some complicated actions
}
}
Не будем разводить дискуссий по поводу вырожденности примера, мол, большую часть времени есть аргументы и возвращаемое значение – суть от этого не поменяется. Сколько бы раз я не написал вызов этого метода, он будет выполнен последовательно, один за другим.
var service = new Service();
service.Method();
service.Method();
service.Method();
...
Со временем начинает расти недовольство тем, что интерфейс программы, если он есть, замирает в результате выполнения цепочки последовательных методов и в этот момент ни отменить операции, ни что-то сделать еще. С одной стороны есть «плюс», не надо делать уведомление о том, что операция закончилась, так как экран «отмерзнет», но лучше это делать более цивилизованными способами.
Зато, кстати, не надо было задумываться о том, что этот код может выполняться сразу в нескольких потоках, никаких проверок, что метод уже запущен и работает с данными. Второй раз с интерфейса все равно не запустишь его, если есть такая связь.
Сообщения с таймером
При работе со многими программами мы не обращаем внимания на множество вещей в интерфейсе, считая это само собой разумеющимся, или же считаем милой забавной штучкой, на которую потратили от силы полчаса. Такие мелочи в интерфейсе, в конечном счете, складываются в ощущение целостности процесса работы, которые не отвлекают от важной информации, а подчеркивают ее, не заставляют делать лишние телодвижения. Всё кажется логичным, простым и доступным для понимания. Наверно не стоит говорить уже, что такие интерфейсы занимают в своей проработке и реализации уйму времени и сил. Об одной из таких приятных «мелочей» я бы хотел сегодня рассказать.
Задумка
Я думаю, что если подумать, то все вспомнят о статусной строке в приложении, где часто пишется состояние программы, уведомления о завершении каких-либо фоновых задач, другая интересная информация о работе программы. Еще можно вспомнить о программах, которые показывают информационное сообщение пользователю в каком-либо специальном месте на интерфейсе, а через какое-то время (секунд 5) надпись исчезает. Учитывая последние тенденции к тому, чтобы избавлять пользователя от popup-окон, в которых написано что-то в духе: Данная операция не может быть совершена, так как она в процессе выполнения, - и на всплывшем окне только одна кнопка OK. Раздражает такое поведение неимоверно, так как приводит к лишним действиям! В общем, сегодня я покажу, как можно реализовать набор классов для реализации такого поведения и использовать его в дальнейшем без существенных модификаций.
Неискушенный читатель наверно может воскликнуть: «Что за бредятина, какие еще наборы классов для того, чтобы сделать два set’a строки? Надо показать сообщение, так присвоил переменной сообщения нужный текст, когда не надо – присвоил пустую строку. Any problem?»
Если кратко, то проблем много! Сейчас попробую перечислить их, как они придут в голову:
- Много инфраструктурного кода получится, ведь надо будет вводить таймеры, ответы и наверняка что-то еще. И это каждый раз при попытке сменить текст.
- Надо запоминать предыдущий текст в сообщении, если он был.
- Легко запутаться что, где и зачем реализовано. Скорее всего, будет много копи-пасты, а следовательно больше мест для ошибок.
- Некрасиво!
Лично для меня хватило только первого пункта из-за моей профессиональной лени.
Тестирование событий с помощью Moq
Вообще название статьи опять очень длинное на самом деле, что-то в духе: Тестирование сложных сценарий с событиями на примере Moq, связыванием StructureMap, построением объектов с помощью NBuilder, и проверки условий с помощью FluentAssertions. Но согласитесь, что это как-то чересчур и надо что-то выбрать одно, а то перечислять все технологии в заголовке плохо. Особенно когда рассматриваешь пример не простого приложения типа Hello World.
При тестировании real-life приложения возникает очень много вопросов по реализации и использовании технологий, которые обычно не рассматриваются в обзорных статьях, которые копипастятся друг у друга блогерами и агрегаторами статей. Надеюсь, данная статья прольет свет на некоторые аспекты использования упомянутых технологий.
BLToolkit. Intro — II
Хранимые процедуры
Для многих разработчиков является нормой сделать всю работу с базой на хранимых процедурах, лет десять назад это было очень популярно. Впрочем, еще много ситуации когда без хранимых процедур будет тяжело обходиться, так что посмотрим, как же работать с ними с помощью BLToolkit.
Начнем с написания хранимой процедуры. Пусть нам требуется выборка людей старше определенного возраста.
create procedure Person_GetOlderThan(
@Age int
)
as
set nocount on;
select PersonId
,Name
,Birth
,Resident
,Gender
,Weight
,Height
from dbo.Person
where datediff(yy,Birth, getdate()) > @Age
BLToolkit. Intro — I
Уже достаточно давно меня интересует тема CQRS, но пока что все останавливалось на изучении документов, примеров и самому написать что-то используя подход CQRS не доводилось. В последние дни работа в этом направлении активизировалась с новой силой, и пошлел процесс по исследованию инструментов более всего пригодных для реализации этой концепции. Наверно так повлияла конференция Patterns’n’Practices, когда было объявлено что к концу года команда выпустит гайдлайны по реализации CQRS на практике.
Одной из ключевых вещей является непосредственное получение информации из базы данных без промежуточных программных слоев. Такое получение данных должно быть быстрое и простое. Простое и быстрое. По многочисленным источникам и по результатам сайта ORM Battle был выбран для экспериментов BLToolkit.
Насчет скорости работы есть диаграмма сравнения с другими фреймворками на 30 июля 2011 года:
Не могу не вспомнить фразу с презентации на NDC2011 по поводу Kill your ORM, где ведущий высказался в таком духе: «Linq2Sql вышел слишком простым и быстрым, поэтому придумали EF. Больше абстракций!».
Первой фазой я решил проверить легкость использования и различные нюансы построения классов для маппинга, так как обычно уже на этой фазе начинают вылезать разные неприятные моменты использования.
На официальном сайте есть что-то в духе вводного курса, но как обычно это и бывает, многие вещи писатели держали в уме и считали само-собой разумеющееся. Разобраться можно, но последовательность повествования надо поменять, что я и попробую сейчас сделать, надеюсь, что получится яснее.
Eloquera and WPF
Во время экспериментов с объектной базой Eloquera, я столкнулся с одним очень неприятным эффектом, который меня напрягал в течение месяца наверно или даже больше. За это время я успел замучать самих создателей базы, отписался на официальном форуме DevExpress, даже вовлек в эту котовасию Дона Смита после конференции Patterns’n’Practices и его коллег. В итоге, после многочисленных писем со стэк-трейсами и примерами кода, проблема была решена. Но обо всем по порядку.
Частой практикой при разработке сколько-нибудь сложного приложения является вынос всех визуальных компонентов в отдельную библиотеку. Т.е. существует исполняемый проект с основной формой и проект с пользовательскими экранами (UserControls), которые отображаются на основной форме. В простейшем случае все это выглядит примерно так в обозревателе проектов:
Используя простейший код для отображения пользовательских компонентов, можно убедиться что все хорошо, компоненты отображаются и приложение работает как ожидалось.
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
content.Children.Clear();
content.Children.Add(new ExternalDllControl());
}
}
Здесь за переменной content скрывается StackPanel, так что приводить код основной формы не стоит.
Далее начинаются волшебные вещи, которые можно было охарактеризовать фразой из старинного анекдота: «Если я тебя по голове ударю, у тебя шнурки развяжутся?», так вот шнурки развязывались. При создании экземпляра класса базы данных Eloquera, выкидывалось сообщение о невозможности найти визуальный компонент в библиотеке CustomControl. Такое поведение характерно только для работы базы в конфигурации Desktop.
The component 'CustomControls.ExternalDllControl' does not have a resource identified by the URI '/CustomControls;component/externaldllcontrol.xaml'.
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
var db = new DB("server=(local);options=none");
content.Children.Clear();
content.Children.Add(new ExternalDllControl());
}
}
Дальнейшая работа была невозможна. Ошибка происходила в независимости от того, в каком потоке создавалось соединение с базой данных.
Проблему можно было обойти перенеся все компоненты в исполяемую сборку, но это было частичное решение и работало только если не использовались визуальные компоненты третьих сторон, с которыми могли произойти те же самые сложности.
О причинах такого поведения
После долгих расспросов и копаний обнаружилось, что проблема была в механизме обнаружения хранимых процедур, которые появились в версии 4.1, но задел к которым был наверно уже с версии 3.4.1. Данный механизм автоматически сканирует библиотеки в директории по умолчанию и загружает их в пространство памяти сервера. Таким образом, сервер пытался отъесть все библиотеки, и если остальному коду было все равно, на то какой у них codebase, то с WPF такой фокус не прокатил.
Решение
Решение как всегда оказалось простое и эффективное. Если не планируется использовать хранимые процедуры для базы Eloquera, то надо указать директорию для поиска библиотек с хранимыми процедурами на какую-нибудь папку, где вашего приложения точно не окажется.
Изменить путь до хранимых процедур можно декларативно в коде, причем сделать это желательно как можно раньше, до загрузки визуальных компонентов. Я поместил код в App.
public partial class App : Application {
public App() {
var db = new DB("server=(local);options=none");
DB.Configuration.ServerSettings.StoredProceduresPath = "c:\temp";
}
}
После этого проблема исчезла и можно спокойно продолжать работу над проектом, используя в качестве базы данных Eloquera.
Hard'n'Heavy!
Eloquera V
краткое описание / немного истории / установка / режимы работы базы: service, inmemory, desktop / восстановление базы
конфигурация / программная конфигурация / простейшие операции CRUD / insert / update / refreshMode / delete
построение запросов / joins / параметры / массивы / шаблонные контейнеры / order by / доступные функции / almos
хранимые процедуры
родные и динамические объекты / работа с массивами / контейнеры / фильтрация типов, свойств и полей / backup and restore / эволюция типов / fulltext search / индексация / под капотом
«Родные» и динамические объекты
Под родными объектами в данном случае подразумеваются любые типы принадлежащие CLR или созданные вами. Eloquera может на лету (в runtime) конвертировать динамические объекты в «родные» и обратно.
Внимание!
Для использования этого функционала должно быть открыто соединение к базе.
Основные методы для конвертации:
- ToNative<ConcretType>()
- FromNative(variable)
Пример для уже открытой базы по созданию динамического объекта:
Book book = new Book { DbID = 12345, Title = "Title Mitle" }
Dynamic dynObj = new Dynamic(book);
db.Store(dynObj);
Book newBook = dynObj.ToNative();
При обновлении данных применяются следующие схемы работы:
- Обновление через родной тип
0 Динамический тип переводим в «родной» тип
0 Изменяем «родной» тип
0 Обновляем (синхронизируем) динамический тип с помощью FromNative
- Обновление через динамический тип
0 Существует переменная конкретного типа с данными динамического
0 Изменяем динамический тип
0 Обновляем (синхронизируем) «родной» тип с помощью ToNative<T>(variable)
Eloquera IV
краткое описание / немного истории / установка / режимы работы базы: service, inmemory, desktop / восстановление базы
конфигурация / программная конфигурация / простейшие операции CRUD / insert / update / refreshMode / delete
построение запросов / joins / параметры / массивы / шаблонные контейнеры / order by / доступные функции / almos
хранимые процедуры
Хранимые процедуры
Начиная с версии 4.0 Eloquera поддерживает хранимые процедуры. Я думаю, не стоит расписывать все прелести и возможности хранимых процедур. Те, кто работают с базами и так их знают. Хочу только заметить, что результат выполнения хранимой процедуры остается, связан с контекстом базы, а значит, полученные данные можно изменять и обновлять объекты в базе.
Хранимые процедуры могут рассматриваться как методы расширения к основной базе. Они пишутся с использованием C#, за что благодарность создателям, за то, что они не выдумали какой-либо свой интерпретируемый диалект для этого дела. Получается, что можно использовать все возможность .Net при написании процедур.
Итак, хватит слов, лучше показать, как создаются хранимые процедуры на деле.
Первым делом надо объявить интерфейс с желаемыми процедурами. Данный интерфейс должен находиться на стороне клиента и сервера.
public interface ICityStatisticSP{
CityCars GetCityCars(string cityName);
}
Затем создаем класс, который нужен будет только на стороне сервера. При этом библиотеку с реализацией процедуры надо поместить в папку Lib в директории базы, либо настроить путь до библиотек с хранимыми процедурами с помощью параметра StoredProceduresPath.
public class CityStatisticSP: StoredProcedure, ICityStatisticSP {
public CityCars GetCityCars(string cityName) {
Parameters parms = db.CreateParameters();
parms["city"] = cityName;
var cars = db.ExecuteQuery("SELECT Car FROM Car JOIN Seller ON Car.SellerID = Seller.ID WHERE Seller.City = @city", parms)
.OfType()
.ToList();
…
}
}
Добавление новых процедур не требует перезапуска сервиса базы данных.
Eloquera III
краткое описание / немного истории / установка / режимы работы базы: service, inmemory, desktop / восстановление базы
конфигурация / программная конфигурация / простейшие операции CRUD / insert / update / refreshMode / delete
построение запросов / joins / параметры / массивы / шаблонные контейнеры / order by / доступные функции / almos
Построение запросов
Eloquera позволяет писать запросы к базе данных на привычном многим SQL, причем с приятными дополнениями, которые я не встречал в MS SQL. Запросы можно писать в отношении полей и свойств класса, к дочерним объектам, коллекциям и массивам объектов. Все вместе это дает очень мощную платформу для анализа данных.
Как вы уже могли заметить, получение данных происходит с помощью двух функций:
- ExecuteQuery(string query)
- ExecuteScalar(string query)
Обе функции имеют перегруженные варианты для возможности указания глубины запрашиваемых объектов, параметров. Да-да, можно строить параметризированные запросы.
Общий вид и порядок служебных слов в запросе тот же, что и в «обычном» SQL . Если делается запрос к одной сущности, то слово from можно опустить. Например:
var result = eloquera.ExecuteScalar("Select ClassId where Text = 'XXX'");
Eloquera поддерживает операторы TOP, SKIP, Order by (подробно будет чуть позже).
var result = eloquera.ExecuteQuery("Select Top 5 ClassId");
var result = eloquera.ExecuteQuery("Select Skip 3 ClassId");
Запросы можно строить не только для собственных полей класса, но и обращаться к составным полям, массивам. Сложный объект может содержать другие объекты или массивы, например:
public class BasicUser {
public Location HomeLocation;
public Location CurrentLocation;
public string Name;
public DateTime BirthDate;
public string Interests;
public BasicUser[] Friends;
public WorkPlace UserWorkPlace;
public School UserSchool;
public string[] Emails;
}

