Когда уже умеешь программировать, то рассматриваешь всё только с позиции микроскопа. но иногда молоток тоже вполне себе решение
(с) Конфуций, V в. до н.э.
Всем привет! Сегодня расскажу про свою библиотеку для генерации кода, которая упрощает работу со схемами в Revit API, а так же позволяет их изменять. Да и вообще, упрощает работу со схемами почти до уровня обычного запроса к базе данных.
Введение
- Итак, допустим, ваш плагин должен хранить какую-то информацию в недоступном для пользователя виде. Очевидный вариант — внешнее хранилище ExtensibleStorage. Оно действительно позволяет хранить много информации, но имеет минусы:
- Ограниченный набор типов данных
- С ним неудобно работать
- Однажды созданную схему поменять нельзя
И если с первыми двумя неудобствами можно смириться, то вот в третьим может возникнуть проблема. Допустим, мы написали плагин со схемой и выдали его в работу. Теперь нам поступила задача расширить функционал. Решение задачи потребовало увеличение числа полей в схеме. Изменить схему нельзя, поэтому мы создали новую схему. И для новых пользователей с новыми файлами никаких проблем нет. Но вот старые файлы со старой схемой работать будут так себе. Да, там создастся новая схема, но в неё не перенесутся данные из уже существующих элементов. Если на эти данные завязана логика плагина, то эти элементы придётся создавать заново. Сегодня я расскажу, как решить эту проблему.
Работа с базами данных в Entity Framework
Отойдём чуть-чуть в сторону. Кратко расскажу, как работает Entity Framework — система для работы с базами данных для веб-приложений на C#, позволяющая использовать LINQ вместо прямых запросов SQL.
1. Мы определяем класс Модели — сущность, которая будет храниться в таблице базы данных. Одна модель — одна таблица, а каждое свойство модели — столбец таблицы.
2. Добавляем нашу модель в качестве DbSet<Model> в класс-наследник класса DbContext — основной класс для доступа к базе данных.
3. Пишем в терминале команду 'dotnet ef migration add ...'.
4. Entity Framework генерирует нам файл с кодом на C# (миграцию), который показывает, как наша база данных изменилась. Какие таблицы добавились, какие исчезли и как модифицировались столбцы
5. Пишем в терминале 'dotnet ef update database', и миграция применяется к базе данных. Все таблицы обновляются.
6. Если мы изменили или добавили модель, то повторяем шаги 3-5. Таблицы в базе данных меняются, существовавшая в них информация при этом никуда не исчезает.
Собственно, для чего это всё. Я создал библиотеку, которая делает всё тоже самое, но только для Revit Extensible Storage.
Библиотека Atomatiq.SchemaMigrations
Код библиотеки находится тут: https://github.com/atomatiq/SchemaMigrations
А тут — пошаговая инструкция по работе.
Итак, как теперь мы решаем проблему со схемами:
- Подключаем к проекту библиотеку Atomatiq.SchemaMigrations.Database
- Определяем класс модели. Каждое свойство модели — будущее поле в схеме, поэтому они должны быть только совместимого типа
3. Создаём класс наследник класса SchemaContext, определяем в нем набор из SchemaSet<Model>:
4. Устанавливаемый глобально тул для генерации миграций
5. Запускаем в терминале 'schema-migration-add InitialMigration'.
6. Генератор миграций создаёт нам файл миграции:
Да, это всё автоматически сгенерированный код. Вот так, кстати, выглядит код, который генерирует другой код:
6. Теперь нам надо воспользоваться тем, что мы нагенерировали. Все миграции применятся автоматически, как только мы обратимся к любому элементу. Если мы как-то меняли схему, а в проекте осталась старая версия, то все данные перенесутся в новосозданную схему, а из старой — удалятся. Чтобы воспользоваться схемой, используем класс DatabaseConnection<TModel>. В качестве параметра при создании он принимает элемент, в котором будет хранится Entity данной схемы.
Для работы со схемой нужны 2 метода:
- SaveObject сохраняет в схему данные, которые мы передали в виде экземпляра класса Модели (указанной в угловых скобках у DatabaseConnection<Model>).
- LoadObject считывает данные их схемы и возвращает их в виде экземпляра класса модели. Если в Entity не существует, то вернётся пустой объект. Существование объекта в схеме проверяет метод HasObject.
Собственно, на этом всё. Всё, что я писал в прошлой статье про схемы и запись данных в них, можно считать устаревшим. Зачем записывать данные в схему по одному полю, запоминать тип и имя поля, передавая имя в виде строки, если можно взаимодействовать напрямую с моделями, словно загружая их из базы данных?
Пример клиентского приложения, использующего данную библиотеку:
Обращение к читателям
Во-первых, я буду рад, если вы поддержите репозиторий проекта звездой на гитхабе.
Во-вторых, я буду невероятно рад, если вы воспользуетесь этой библиотекой на своём рабочем проекте. В качестве основной инструкции используйте readme.md.
В-третьих, в проекте могут быть баги, а в readme.md — непонятки. Если вы найдёте баг — смело создавайте Issue в репозитории. А также можете вложить и свой вклад в проект, исправив баг и сделав пулл-реквест, или предложив упрощение для readme.
Заключение
Конечно же, не забывайте подписываться на мой телеграм-канал о Revit API и на меня лично в GitHub. Это моя первая публичная библиотека, так что не судите строго и делитесь своими мыслями в комментариях. До новых встреч!