Обновление БД с nHibernate/FluentMigrator и проблемы с индексами/дефолтными констрейнтами

Вопрос миграций — обновления БД со старых на новые версии для синхронизации с текущим состоянием — неминуемо встает перед разработчиками, как только их приложения уходят в «продакшн». До первого релиза во многих случаях вся команда разработчиков работает с одной базой, так что этот вопрос может не стоять так остро. Но как только приложение появляются первые продакшн/staging/testing сервера, ситуация, когда разработчики понаписали код и пора бы обновить релизную БД неминуемо возникает :)

Если релизы происходят нечасто, то для ручного решения таких проблем хорошим решением может стать SqlCompare от RedGate, которая эту задачу предельно упрощает. Но если у вас коробочный продукт, или вы хотите обновлять staging сервер по билду CI-сервера, то, конечно, рано или поздно захочется этот процесс автоматизировать.
Continue reading

Юнит-тестирование БД-зависимых классов

Написание автоматизированных тестов для приложений работающих с БД часто становится источником многочисленных споров. Основная причина этих споров — подходы к тестированию. Можно выделить 3 основных подхода, которые при этом применяются:

  1. Абстрагирование от БД. Подход предполагает, что взаимодействия с какими бы то ни было БД в тестах не происходит. Для этого все обращения к БД оборачивают в вызовы, например, IRepository и в тестах эти обращения замещаются с помощью mock-фреймворков (Rhino.Mocks, Moq, etc.). Из плюсов подхода можно выделить популярность паттерна репозиторий, что ускоряет знакомство с методикой. Из минусов — приходится замещать все БД-обращения (иногда их бывает довольно много), а также тесты жестко связываются с интерфесом репозиториев (в случае изменения интерфесов тесты придется также изменять)
  2. Работа поверх тестовой БД. В тестах используется база, «похожая» на продакшн-базу, но с некоторыми тестовыми данными. Тесты, таким образом, работают в окружении, максимально близком к реальной среде, что, без сомнения, является большим плюсом. Из минусов можно выделить более низкую скорость выполнения тестов и проблемы поддержания тестовой БД в эталонном состоянии.
  3. Работа поверх временных БД. В тестах используются временные базы, чаще всего, способные хранить структуру в памяти (SQLite) или на локальном диске (SQL CE). Из плюсов — высокая скорость работы и «приближенность» к реальному окружению (хоть и менее близкая, чем во втором пункте).

Стоит отметить и общую часть всех трех подходов в «простых» случаях: участки кода, которые не требуют «активного» доступа к БД, а лишь модифицируют сущности доменной модели, тестируются во всех подходах одинаково и взаимодействия с БД просто не требуют.

В своей работе я не использую репозитории, потому что в большинстве случаев они являются ненужной прослойкой, которая лишь усложняет доступ к данным.
Из других подходов, я стараюсь использовать последний, поскольку он наиболее лёгок и гибок в применении. Он особенно удобен при использовании ORM, поскольку в этом случае сам ORM обычно способен создать схему базы в соответствии с определенными маппингами и соглашениями.
Ниже я приведу пример базового тестового класса, при наследовании от которого я получаю временную sqlite базу со структурой, соответствующей текущему проекту, и 3 NHibernate-сессии открытые поверх этой БД. Несколько сессий я использую для удобства проверки коммитов: обычно настройку начальных данных я провожу в sessionArrange, в тестируемый код передается sessionAct и проверки осуществляются через sessionAssert, таким образом все «несохраненные» в тестируемом коде данные из sessionAssert будут просто не видны.

    public class InMemoryDb : IDisposable
    {
        private static Configuration Configuration;
        private static ISessionFactory SessionFactory;
        protected ISession _sessionAssert;
        protected ISession _sessionArrange;
        protected ISession _sessionAct;


        [SetUp]
        public void _Setup()
        {
            Init();
        }

        public void Init()
        {
            if (Configuration == null)
            {
                var conf = Fluently.Configure()
                    .Database(SQLiteConfiguration.Standard.InMemory)
                    .Mappings(m => m.AutoMappings.Add(AutomappingGenerator.CreateAutomappings)); 
                     //AutomappingGenerator.CreateAutomappings - статичная функция, возвращающая AutoPersistenceModel - конфигурацию NHibernate в текущем проекте.

                SessionFactory = conf.BuildSessionFactory();
                Configuration = conf.BuildConfiguration();
            }

            _sessionAct = SessionFactory.OpenSession();
            _sessionArrange = SessionFactory.OpenSession(_sessionAct.Connection);
            _sessionAssert = SessionFactory.OpenSession(_sessionAct.Connection);

            _sessionAct.BeginTransaction();
            _sessionArrange.BeginTransaction();
            _sessionAssert.BeginTransaction();

            var nullWriter = new StringWriter();
            new SchemaExport(Configuration).Execute(false, true, false, _sessionAct.Connection, nullWriter);
        }


        public void Dispose()
        {
            _sessionAct.Dispose();
            _sessionArrange.Dispose();
            _sessionAssert.Dispose();
        }
    }

Второй подход я также использую в случаях, когда воспользоваться третьим не получается :) В этом случае проблему «очистки» БД я решаю путем выполнения всех операций внутри теста в рамках одной транзакции и отката этой транзакции по окончании теста. Базовый класс в этом случае выглядит совсем просто, но более специфично, поэтому, пожалуй, от выкладывания его я воздержусь :)

Session/context management в настольных приложениях

Когда в сети говорят об ORM в .net — очень часто в качестве сферы применения «по умолчанию» рассматривается ASP.Net (и MVC в частности).
Происходит это, как мне кажется, потому, что именно веб — среда наиболее динамичная, открытая и быстро подхватывающая новые веяния, вследствие полной свободы в технических решениях. В случае, к примеру, Win Forms приложений переход со второго фреймворка на 4-ый — сложнейший шаг (апдейт должен затронуть всех пользователей, а у некоторых, может быть, еще win2000 :)), а для ASP.Net разработчиков проапгрейдить сервер, обычно, не проблема.
Еще одна причина может быть в том, что «принципы работы» в случае веба типичны, и рабочий цикл мал — пришел запрос, сгенерили ответ, отдали клиенту, вернулись в начальное состояние. Следовательно и подходы, применяемые одной компанией, легко адаптируются и в другой.
Desktop-приложения же часто очень велики, с длинным циклом разработки и, обычно, с устоявшимися внутри компании методиками работы, которые не всегда легко переносимы.

В этом посте я хотел бы рассказать об одной из специфических трудностей, с которыми можно столкнуться при работе с ORM в настольных приложениях, а именно: управление контекстом/сессией.
Continue reading

Слой доступа к данным в .net приложениях

При разработке приложений, работающих с базой данных, неизменно встает вопрос проектирования слоя доступа к этим самым данным. Использование «чистого» ADO.Net в рамках больших и сложных приложений рассматривается всё реже, и использование ORM становится уже стандартом де-факто.

Однако и после выбора ORM вопросы проектирования не заканчиваются — встает проблема собственно организации слоя доступа к данным. Здесь есть несколько ставших уже классическими подходов:
Continue reading