SqlCE 3.5 to 4.0 Converter

26 авг. 2010 г. | | |

Недавно (в июле этого года) вышла новая версия MS Sql CE, в которой был изменен алгоритм шифрования (стал использоваться SHA-2). Это означает, что для работы sql ce 4 с бд, созданной сервером версии 3.5, ее нужно предварительно переконвертировать. Для это в API предусмотрен специальный метод, поэтому особого труда конвертирование не вызовет.
Давайте напишем конвертер для файлов баз данных в формате slq ce 3.5 в формат slq ce 4.0.
Писать будем на C# в TDD-стиле, использовать VS 2008, тестировать в xUnit (последнюю версию можно скачать с codeplex). Когда вы используете TDD, не нарушая основных принципов ( а их всего три - RGR (красный-зеленый-синий): пишем тест, пишем код, рефакторим) и порядка их следования, то получаете автоматически приятные бонусы в коде.

Warning! Этот пост нужно рассматривать как пример по разработке в стиле TDD для начинающих.

Итак, начнем. Создаем новый Windows Forms Application - проект в VS (у меня он называется "SqlCE3.5_to_4.0"), сразу же добавляем в солюшн еще один проект Class Library, называем его Tests. И, в лучших традициях TDD, начинаем писать наш первый тест.

public class SqlCEVersionConverterTests
{
[Fact]
public void ConvertTest()
{
string file2convert = @"C:\sqlCE35.sdf";
var sqlCEConverter = new SqlCEVersionConverter(file2convert);
Assert.True(sqlCEConverter.Convert());
}
}

Так я представляю себе работу нашего конвертера. Тест, конечно же, не срабатывает - ведь класс SqlCEVersionConverter не реализован. Этим и займемся. Добавим в главный проект новый класс, SqlCEVersionConverter. Пока нам нужно сделать минимум телодвижений, чтобы заставить тест сработать - делаем фейковую реализацию:

public class SqlCEVersionConverter
{
public SqlCEVersionConverter(string file2convert)
{
}
public bool Convert()
{
var result = true;
return result;
}
}

Запускаем тест... Ура! Тест сработал. Теперь посмотрим, что нам нужно для того, чтобы метод Convert() заработал по-настоящему. Нам нужно подключиться к выбранной базе данных и заапгрейдить ее.

public bool Convert()
{
var result = false;
using (SqlCeEngine sqlCeEngine = new SqlCeEngine(connectionString))
{
try
{
sqlCeEngine.Upgrade();
result = true;
}
catch (Exception e)
{
//обработка исключения
}
}
return result;
}

Код не компилируется (F6) - не хватает строки соединения (connectionString). Добавляем новое поле в класс. Теперь код компилируется, но тест не срабатывает - мы используем пустую connectionString, из-за чего и не можем подключиться к БД, чтобы переконвертировать ее. Значит, нам нужен метод, который бы создавал строку подключения. Настало время писать следующий тест.

[Fact]
public void BuildConnectionStringTest()
{
string sqlCEdbFile = @"C:\sqlCE35.sdf";
var converter = new SqlCEVersionConverter(file2convert);
string connectionString = converter.BuildConnectionString(sqlCEdbFile);
Assert.Equal(@"Data Source=C:\sqlCE35.sdf;Mode=Exclusive", connectionString);
}

Код не компилируется, нужно реализовать метод BuildConnectionString():

public string BuildConnectionString(string sqlCEdbPath)
{
SqlCeConnectionStringBuilder builder = new SqlCeConnectionStringBuilder();
builder["Data Source"] = sqlCEdbPath;
builder["Mode"] = "Exclusive";
return builder.ConnectionString;
}

В реализации этого метода мы сразу опробовали новый API slq ce 4 - класс SqlCeConnectionStringBuilder. Ничего так, удобненько :)
Теперь изменим конструктор SqlCEVersionConverter:

public SqlCEVersionConverter(string file2convert)
{
connectionString = BuildConnectionString(file2convert);
}

Запускаем тест - тест проходит. Look nice. Но что-то не то. Дааа, ведь база данных может быть запаролена! Это значит, что нам нужен для конвертирования бд знать еще и пароль! Т.е. нам нужен еще конструктор, принимающий два аргумента (путь к файлу бд и пароль) и метод BuildConnectionString, тоже на два аргумента. Пишем соответствующие тесты (позволю себе пропустить их) и добавляем реализацию в класс SqlCEVersionConverter. Запускаем - тесты срабатывают! Но все равно мне не нравятся эти два метода BuildConnectionString, засоряют они своим присутствием ауру конвертера :) Во-первых, эти методы должны быть явно приватными. Но тогда их не потестишь. Во-вторых, конвертер должен конвертировать, а тут ему еще вписали в обязанности connection strings делать. Не порядок. Это явно противоречит Single Responsible Principle. Я сделаю небольшой рефакторинг - вынесу эти два метода в отдельный класс SqlCEConnectionStringBuilder. Но для этого сначала напишу соответствующие тесты. Ниже тест SqlCEConnectionStringBuilder.BuildConnectionString() на два аргумента:

[Fact]
public void BuildConnectionStringTest()
{
string sqlCEdbFile = @"C:\sqlCE35.sdf";
string password = "somepassword";
var builder = new SqlCEConnectionStringBuilder();
string connectionString = builder.BuildConnectionString(sqlCEdbFile, password);
Assert.Equal(@"Data Source=C:\sqlCE35.sdf;Mode=Exclusive;Password=somepassword", connectionString);
}

Просто копируем тела методов из SqlCEVersionConverter в SqlCEConnectionStringBuilder, запускаем тесты - срабатывают. Теперь изменяем конструкторы SqlCEVersionConverter, таким образом, чтобы использовался класс SqlCEConnectionStringBuilder:

public SqlCEVersionConverter(string file2convert, string password)
{
SqlCEConnectionStringBuilder builder = new SqlCEConnectionStringBuilder();
connectionString = builder.BuildConnectionString(file2convert, password);
}

И после этого удаляем из SqlCEVersionConverter методы BuildConnectionString(). Компилируем - все в порядке. Запускаем еще раз для уверенности тесты - все зеленое :)
Теперь осталось сделать форму и нацепить обработку событий. У меня форма получилась такой:
На форме есть чекбокс - Make backup file before convert. Из-за того, что в sql ce 4 используется SHA-2, sql ce 3.5 или более ранние версии не смогут работать с файлами данных sql ce 4. Поэтому было бы хорошо делать бэкап. А для этого нам нужен SimpleBackuper. Но сначала - тесты! :)

public class SimpleBackuperTests
{
[Fact]
public void BackupBeforConvertTest()
{
string file2backup = @"C:\sqlCE35.sdf";
var backuper = new SimpleBackuper();
backuper.MakeBackup(file2backup);
string backupFilename = @"C:\sqlCE35_backup.sdf";
Assert.True(File.Exists(backupFilename));
}
}

Сильно заморачиваться над методом создания бэкапа я не буду - использую простое переименование и копирование.

public class SimpleBackuper
{
public void MakeBackup(string file2backup)
{
var backupFileName = file2backup.Insert(file2backup.LastIndexOf('.'), "_backup");
File.Copy(file2backup, backupFileName, true);
return;
}
}

Вот теперь осталось совсем немного - написать обработку клика по кнопке Convert. Думаю, это описывать не надо :)
Заметьте, до этого момента мы еще ни разу не запустили программку (F5) - только компилировали (F6)! Время запустить и законвертировать наконец-таки какую-нибудь sql ce 3.5 бд в 4.0! И вот он, сюрприз! База из 3040 Кб - файла превратилась в 676 Кб-файл! Вот это отличная новость четвертого компакта!

Итак, подведем итоги. Мы разработали приложение в стиле TDD. Получили чистый код, который работает :) Как бонус, у нас получилось отличное отделение логики решаемой задачи от графического пользовательского интерфейса. И мы можем совершенно без страха изменить что-либо или добавить новый функционал в программу.

0 коммент.:

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