316  /  330

Перенос с помощью Visual Studio

Просмотров: 1070 (Статистика ведётся с 06.02.2017)

В качестве рабочего инструмента для миграции можно использовать Visual Studio, в частности, ее бесплатную редакцию.

  • Установите Visual Studio как это описано в документации Microsoft.
  • Откройте Visual Studio и добавьте ссылку на добавленные пространства имен:

  • Выберите в меню File > New > Project:

  • Выберите в качестве шаблона проекта создание нового консольного приложения на C#:

  • Добавьте к ссылкам (References) проекта пространство имен MySql.Data, которое добавляет MySql Connector/Net 6.0:

  • Выполните следующий код:
    using System;
    using System.Text;
    
    using MySql.Data.MySqlClient;
    using System.Data;
    using System.Diagnostics;
    using System.Data.SqlClient;
    
    class Program
    {
        static MySqlConnection mySqlCnn;
        static SqlConnection sqlSrvCnn;
    
        static void Main(string[] args)
        {
            sqlSrvCnn = new SqlConnection(@"server=(local)\SQLExpress;database=bitrix;trusted_connection=true;MultipleActiveResultSets=true");
            sqlSrvCnn.Open();
            mySqlCnn = new MySqlConnection("server=127.0.0.1;port=31006;uid=root;pwd=;database=bsm_demo;Pooling=False");
            mySqlCnn.Open();
    
            DisEnableFKConstraints(true);
            DataTable tblList = GetSourceTablesFromMySQLDB();
            CleanDestTablesInSQLSrvDB(tblList);
            TransferData(tblList);
            DisEnableFKConstraints(false);
    
            mySqlCnn.Close();
            sqlSrvCnn.Close();
        }
    
        /// 
        /// Копирует данные из таблицы в MySQL в одноименную таблицу в SQL Server
        /// Предполагается, что множества имен полей в таблицах совпадают. Порядок может отличаться.
        /// 
        /// Имя таблицы
        static void CopyDataFromMySQLTblToCorrespondingSQLSrvTbl(string tblName)
        {
            //Читаем по порядку поля в таблице-назначения
            SqlCommand sqlSrvCmd = sqlSrvCnn.CreateCommand();
            sqlSrvCmd.CommandText = "select name from sys.columns where object_id = object_id(@tblName) order by column_id";
            sqlSrvCmd.Parameters.AddWithValue("@tblName", tblName);
            SqlDataReader sqlSrvDr = sqlSrvCmd.ExecuteReader(CommandBehavior.SingleResult);
            //Составляем строку запроса для источника, перечисляя туда поля в том порядке, как они следуют в назначении
            StringBuilder mySqlCmdText = new StringBuilder("select ");
            //Имя поля заключаем в аналог квадратных скобок - на случай, если оно будет совпадать с одним из зарезервированых слов MySQL.
            while (sqlSrvDr.Read()) mySqlCmdText.Append("`" + sqlSrvDr.GetSqlString(0).Value + "`,");
            sqlSrvDr.Close();
            mySqlCmdText.Remove(mySqlCmdText.Length - 1, 1);
            mySqlCmdText.Append(" from " + tblName);
    
            MySqlCommand mySqlCmd = new MySqlCommand(mySqlCmdText.ToString(), mySqlCnn);
            MySqlDataReader mySqlDr = mySqlCmd.ExecuteReader();
    
            SqlBulkCopy bcp = new SqlBulkCopy(sqlSrvCnn, SqlBulkCopyOptions.KeepIdentity, null);
            //KeepIdentity означает set identity_insert  on/off
            //Поскольку в mySqlDr поля идут в том же порядке, что и в назначении, SqlBulkCopy.ColumnMappings не требуется.
            bcp.DestinationTableName = tblName;
    // Заправляем шланг ридера объекту SqlBulkCopy, чтобы он качал из него содержимое в bcp.DestinationTableName
            bcp.WriteToServer(mySqlDr);
    
            mySqlDr.Close();
        }
    
        /// 
        /// Получает список таблиц из MySQLной базы
        /// 
        /// Список таблиц
        static DataTable GetSourceTablesFromMySQLDB()
        {
            DataTable tbl = new DataTable();
            tbl.Load(new MySqlCommand("show tables", mySqlCnn).ExecuteReader());
            return tbl;
        }
    
        /// 
        /// Удаляет в каждой таблице из списка все ее записи
        /// 
        /// Список таблиц
        static void CleanDestTablesInSQLSrvDB(DataTable tblList)
        {
            Debug.WriteLine("Очистка таблиц назначения...");
            foreach (DataRow r in tblList.Rows)
            {
                new SqlCommand("delete " + r[0].ToString(), sqlSrvCnn).ExecuteNonQuery();
                Debug.WriteLine("Очищена таблица " + r[0].ToString());
            }
            Debug.WriteLine("Очистка закончена.");
        }
    
        static void TransferData(DataTable tblList)
        {
            Debug.WriteLine("Загрузка данных...");
            foreach (DataRow r in tblList.Rows)
            {
                CopyDataFromMySQLTblToCorrespondingSQLSrvTbl(r[0].ToString());
                Debug.WriteLine("Перенесена таблица " + r[0].ToString());
            }
            Debug.WriteLine("Загрузка завершена.");
        }
    
        /// 
        /// Процедура отключает/включает все ограничения внешнего ключа над таблицами в БД SQL Server
        /// 
        /// Если да, то отключить, нет - включить
        static void DisEnableFKConstraints(bool switchOff)
        {
            string prefix = switchOff ? "От" : "В";
            Debug.WriteLine(prefix + "ключение FK-ограничений...");
            SqlDataReader sdr = new SqlCommand("select name, object_name(parent_object_id) from sys.foreign_keys", sqlSrvCnn).ExecuteReader();
            while (sdr.Read())
            {
                string fkName = sdr.GetString(0), tblName = sdr.GetString(1);
                new SqlCommand(String.Format("alter table {0} {1}check constraint {2}", tblName, switchOff ? "no" : "", fkName), sqlSrvCnn).ExecuteNonQuery();
                Debug.WriteLine(String.Format("{0}ключено ограничение {1} в таблице {2}", prefix, fkName, tblName));
            }
            sdr.Close();
            Debug.WriteLine(prefix + "ключение FK-ограничений завершено.");
        }
    }

Комментарии к коду

Необходимо сделать некоторые комментарии к коду.

Как показывает

select * from sys.objects where type = 'F'

(или sys.foreign_keys/ sys.foreign_key_columns) в базе имеются ограничения внешнего ключа. Следовательно, первоначально следует вставлять данные в referenced_object (РК), а затем в parent_object (FK), чтобы избежать нарушений ограничений внешнего ключа.

Возможны ситуации, когда referenced_object сам, в свою очередь, имеет referenced_object. Следовательно, требуется упорядочить таблицы, выбрав сначала те referenced_objects, которые не имеют FK-ограничений, вставить данные в них, затем в те таблицы, для которых они являются РК-таблицами и т.д.

Чтобы не усложнять скрипт миграции, было принято решение на время переноса данных отключить все FK-ограничения, вставить данные, а затем снова включить. Отключение FK-ограничений выполняется при помощи команды:

ALTER TABLE <имя FK таблицы> NOCHECK CONSTRAINT <имя ограничения>

А включение, соответственно, - CHECK.

Отключение/включение ограничений внешнего ключа делает процедура DisEnableFKConstraints (bool switchOff). В том, что FK-ограничения отключены, можно убедиться по запросу

select * from sys.foreign_keys

в результатах которого колонка is_disabled стала 1 для всех записей.

Перед загрузкой данных содержимое таблиц SQL Express следует очистить. Несмотря на отключенные ограничения чистить таблицы при помощи команды TRUNCATE TABLE не получится. Приходится использовать команду DELETE <имя таблицы> для удаления из каждой таблицы всех ее записей.

По команде

select * from sys.columns where is_identity = 1

или

select * from sys.identity_columns

можно увидеть, что на некоторых таблицах имеются колонки с автоинкрементом. Однако специально отключать автоинкремент перед вставкой SET IDENTITY_INSERT <имя таблицы> ON | OFF не требуется, т.к. это "за сценой" делает объект SqlBulkCopy при помощи параметра KeepIdentity.

Общая последовательность действий по переносу данных выглядит следующим образом:

  • Открываются соединения с БД MySQL и SQL Express.
  • Включается MARS в SQL Server’ном соединении для выполнения процедуры DisEnableFKConstraints, где держится на соединении открытый DataReader со списком FK. По этому списку на каждой записи выполняется команда ExecuteNonQuery() на том же соединении.
  • Отключаются все ограничения внешнего ключа в БД SQL Express, чтобы не заботиться о последовательности очистки и заливки.
  • Получается список таблиц из БД MySQL. Он сохраняется в DataTable tblList.
  • Очищаются таблицы из этого списка.
  • Последовательно по этому списку переносятся данные из каждой таблицы MySQL в одноименную таблицу SQL Express.

Перед выполнением загрузки из MySQL рекомендуется выполнить резервное копирование базы данных на SQL Express несмотря на то, что она пуста, т.е. содержит только "заводские" установки и весь наработанный контент хранится в базе MySQL. Резервное копирование базы данных SQL Server можно выполнить при помощи команды:

backup database Bitrix to  disk = 'c:\Bitrix\bitrix.bak' with noformat, init, name = N'Bitrix-Full Database Backup', skip, stats = 10

Восстановление (при необходимости) выполняется командой:

alter database Bitrix set single_user with rollback immediate
use master
restore database Bitrix from disk = 'c:\Bitrix\Bitrix.bak' with recovery, stats = 20
0
Курсы разработаны в компании «1С-Битрикс»

Если вы нашли неточность в тексте, непонятное объяснение, пожалуйста, сообщите нам об этом в комментариях.
Развернуть комментарии