четверг, 28 апреля 2011 г.

Создание пустой базы данных по шаблону

Некоторое время назад (уже уж более полугода) я устроился на работу в отличную фирму в группу разработки мобильных решений на платформе MS Windows CE и библиотеки .NET Compact Framework (.NET CF) в среде Visual Studio 2008. Первое время я занимался поддержкой существующих приложений либо разрабатывал настольные приложения различного назначения.

Мобильные решения часто в составе имеют также базу данных SQL Server Compact (SQL Server CE) в виде единого файла *.sdf, который хранится либо во внутренней памяти мобильного устройства (и, соответственно, удаляется при горячей перезагрузке), либо на флеш-карте, если такая есть в устройстве (так как флеш-память энергонезависима, файл базы данных остается после любой перезагрузки). В связи с этим возникает задача создания пустой базы данных по шаблону, в которой приложение может складировать данные. Такая может быть заполнена по умолчанию какими-либо данными (например, можно хранить номер версии базы для поддержания ее в актульном состоянии). У меня была возможность сравнить два решения этой задачи, которыми я готов поделиться.


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

То, как было придумано до меня

В тех проектах, которые я поддерживаю, задача создания пустой базы решается следующим образом:

  1. из кода создается база данных в указанном месте (во внутренней память или на флешке):
    // useInsideStore - хранить ли базу во внутренней памяти
    string path = useInsideStore ? "\\temp\\MyDatabase.sdf" : "\\storage card\\MyDatabase.sdf";
    if (!File.Exists(path))
    {
        try
        {
            SqlCeEngine engine = new SqlCeEngine(connectionString);
            engine.CreateDatabase();
        }
        finally
        {
            engine.Dispose();
        }
    }
    
  2. из кода вызываются команды на создание таблиц:
    SqlCeCommand command = _connection.CreateCommand();
    command.CommandText = "CREATE TABLE [Users] (" +
                      "Id int PRIMARY KEY, " +
                      "UserName nvarchar(50) NOT NULL)";
    command.ExecuteNonQuery();
    
  3. в коде выполняются запросы на добавление некоторых данных по умолчанию:

Решение имеет право на существование и оно работает. Но, лично для меня, такое решение кажется неудобным в первую очередь из-за того, что такой "шаблон" базы тяжело поддерживать.

Представим, что появилась необходимость в какой-то таблице базы изменить/добавить столбец с данными. Для этого придется в коде запроса на создание соответствующей таблицы вносить изменения. Кому-то покажется, что это тривиальная задача и, в общем-то, ничего сложного в этом нет, и он будет прав. А я вот, лично, редко пишу запросы на создание и изменение структуры БД, поэтому, чтобы выполнить эту операцию, придется идти читать документацию, тратить время и т.д. Да и где гарантия, что я сделаю все правильно с первого раза?  А если нужно добавить не столбец, а новую таблицу? В общем, думаю, мысль понятна.

Таким образом, могу выделить минусы такого решения:

  • возможность допустить ошибку при создании/редактровании столбца/таблицы/прочее;
  • относительно большие затраты по времени;
  • и еще один минус, который не может пройти мимо, в процессе design-time фактически нет экземпляра БД.

То, что пришло мне в голову

Недавно передо мной встала задача разработки нового приложения. И тут у меня появилась возможность решить задачу создания пустой базы иным способом.

Во-первых, мне кажется логичным, чтобы база была создана в design-time, с нужным набором данных и с которой можно было работать средствами, например, Visual Studio, - иметь своего рода "болванку".

Во-вторых, хочется, чтобы файл базы данных входил в состав проекта Visual Studio и коммитился в svn вместе со проектом.

В принципе, можно так и сделать. В свойствах проекта через Add -> New Item создаем файл базы данных:

Далее появляется диалог для создания типизированного DataSet, но если нужды в нем нет, то от такой услуги можно спокойно отказаться.

Затем с помощью средств самой студии (в окне Server Explorer) можно создать необходимые таблицы и наполнить их необходимыми данными.

В принципе, на этом можно было бы остановиться, поставив в свойствах созданного файла базы данных в параметре Copy to Output Directory значение Copy always. Таким образом, рядом со сборками при построении проекта будет валяться "болванка" базы.

А далее при необходимости шаблонную базу можно копировать в любое место.

Бонус

Все было бы так просто, если бы не одно НО! В отличие от первого решения, у нас в результате появился дополнительный файл. Ничего плохого в этом нет, но и ничего хорошего тоже. Мало того, что эталонный файл могут случайно удалить пользователи, подумав, что база лежит в другом месте (во внутренней памяти или на флешке, куда они сами указали в настройках приложения), будет еще хуже, если они его начнут править.

В общем, такой вариант меня лично не устраивает. Поэтому делаем откат до того момента, как задали значение параметру Copy to Output Directory и оставляем его по умолчанию - Do not copy. Напротив, параметру Build Action устанавливаем значение Embedded Resource.

Что нам это дало? А то, что файл базы данных теперь будет входить в состав сборки. А копировать файл базы в указанное место можно, например, следующим образом:

// useInsideStore - хранить ли базу во внутренней памяти
string path = useInsideStore ? "\\temp\\MyDatabase.sdf" : "\\storage card\\MyDatabase.sdf";
if (!File.Exists(path))
{
    FileStream fileStream;
    byte[] bytes;
    using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("YourNamespace.MyDatabase.sdf"))
    {
        fileStream = File.OpenWrite(path);
        BinaryReader binaryReader = new BinaryReader(stream);
        bytes = binaryReader.ReadBytes((int) stream.Length);
    }
    fileStream.Write(bytes, 0, bytes.Length);
    fileStream.Close();
}

Вместо YourNamespace нужно подставить пространство имен, в котором сохранен файл базы данных.

Такое решение лишено недостатков первого варианта. Конечно, появился другой недостаток - размер сборки увеличивается на размер "болванки" базы. Но, по-моему, эта та цена, которую во многих случаях можно и заплатить.

Спасибо за внимание! Если у вас есть другие решения такой задачи, с удовольствием пообсуждаем их в комментариях.

Ссылки по теме:

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

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