- PVSM.RU - https://www.pvsm.ru -
Имеем MSSQL 2008
Хотим MySQL версии 5.х
Для разработчиков на .NET променять MSSQL на MySQL это наверное все равно, что пересесть с мерседеса на что-то по-проще. Как говорится, к хорошему быстро привыкаешь.
Но есть как минимум две причины сделать это
Работа с базой MSSQL в нашем случае осуществляется через LINQ провайдер.
При переходе, не хотелось бы терять эту возможность, поэтому для работы с MySQL выбор пал на BLToolkit [1].
Самое простое — это переписать код. BLToolkit в отличие от MS-провайдера относится к классу легких ORM, поэтому там немного другие конструкции подключения к базе, но LINQ-выражения останутся теми же.
Думаете осталось перенести данные и все заработает?
Как бы не так.
Для миграции мы использовали MySQL Migration Toolkit.
Большинство типов полей MSSQL экспортировались без проблем, но два поля на отрез отказались:
В нашем случае мы в исходной базе изменили varchar на nvarchar, а datetime2 на datetime. С первым все ясно — поле просто стало хранить символы в unicode, а во втором у нас не было причин по которым разработчикам понадобилось хранить datetime с точностью до 100 наносекунд, кроме той, что если положить DateTime.Now в базу, а потом извлечь, то значения могут быть не равны из-за округления (какой-то функциональный тест написанный таким образом падал и разработчик решил проблему использованием datetime2).
Поле типа timestamp экспортируется в поле с аналогичным типом, однако его поведение немного отличается.
В классе .NET для MSSQL оно представлено каким-то недоделанным типом Binary, в результате чего приходится делать хаки для LINQ вроде этого [2]
В MySQL оно транслируется в обычный DateTime, но есть два подводных камня
DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, иначе поле не будет автоматически обновляться, как мы привыкли в MSSQL. (Прочитать про особенности обновления timestamp в MySQL [3])На этом этапе у вас скорее всего уже собирается проект и возможно что-то даже работает.
Если mapping не работает по причине того, что для каких-то полей класса не находятся соответствующие поля в таблице, то это возможно потому, что для MS LINQ нужные поля классов нужно помечать атрибутом [Column], а для BLToolkit наоборот лишние поля нужно помечать атрибутом [MapIgnore]
Следующим неприятным сюрпризом может стать тот факт, что BLToolkit не экранирует названия полей, поэтому не стоит использовать в ваших таблицах для имени поля всякие ключевые слова типа Key. При трансляции LINQ-выражения в SQL-запрос он упадет с синтаксической ошибкой.
Если ваша программ не покрыта тестами, то это очень плохо.
Только благодаря простому функциональному тесту, который проверял, что возвращаемое NULL поле в базе равно null-строке в .NET выяснилось, что BLToolkit по-умолчанию достает string.Empty.
Вот был бы неприятный сюрприз, если где-то стоит if(value == null).
Чтобы поменять это поведение нужно прописать аттрибут mapping-класса
[NullValue(typeof(string), null)]
Последний подводный камень о котором я могу рассказать, это отображение поля типа Guid.
Как вы знаете в MySQL нет специального типа для хранения Guid.
Для этого там используется char(36).
BLToolkit при отображении использует функцию Guid.Parse.
Например, такой код следует использовать для отображения char(36) полей в Guid
[TableName("Boxes")] public class Box: { [PrimaryKey] public Guid BoxId { get; set; } }
Все выглядит прекрасно, пока кто-нибудь не попытается вставить в таблицу что-нибудь отлично от Guid.
И даже несмотря на то, что в классе mapping-а вы определите это поле как string, BLToolkit все равно попытается сделать Guid.Parse
Например, такой код свалится, при попытке прочитать из char(36) поля StringMayContainsGuid что-то не являющееся guid-ом.
[TableName("Boxes")] public class Box: { public string StringMayContainsGuid { get; set; } }
Простое решение в данном случае — поменять тип поля на varchar.
В качестве инструмента для поиска подобных проблем я написал простой функциональный тест, который перечитывает все данные из таблиц:
[TestFixture, Category("Functional")] public class DbFunctionalTest { private readonly IMysqlClient mysqlClient = new MysqlClient(); // тут ваш клиент для подключения к базе [Test] public void ReadAllTables() { var dbMappingClasses = from classType in Assembly.GetExecutingAssembly().GetTypes() where classType.IsClass && classType.GetCustomAttributes(typeof(TableNameAttribute), true).Length > 0 && classType.Namespace == typeof(DbFunctionalTest).Namespace select classType; mysqlClient.PerformRequest(db => { foreach (Type dbMappingClass in dbMappingClasses) { var tableName = ((TableNameAttribute) dbMappingClass.GetCustomAttributes(typeof (TableNameAttribute), true).First()).Name; try { db.SetCommand(CommandType.Text, string.Format("SELECT * FROM {0}", tableName)).ExecuteList(dbMappingClass); }catch(Exception e) { throw new Exception(string.Format("Can not read all records from table {0}", tableName), e); } } }); } }
Надеюсь описанный опыт будет кому-то полезен.
Но на самом-то деле мой коварный план состоял в том, чтобы получить еще больше пользы [4] от ваших комментариев, так что welcome!
Автор: gmlexx
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/news/3263
Ссылки в тексте:
[1] BLToolkit: http://www.bltoolkit.net
[2] вроде этого: http://diegofrata.wordpress.com/2009/11/18/linq-to-sql-expression-trees-and-binary-comparing/
[3] особенности обновления timestamp в MySQL: http://www.mysql.ru/docs/man/DATETIME.html
[4] еще больше пользы: http://bash.im/quote/415851
Нажмите здесь для печати.