Дозагрузка измененных данных при помощи КД2





Иногда во время каких-то регламентных действий по обслуживанию базы(например, при обновлении измененной базы на много релизов) требуется обеспечить бесперебойность работы пользователей. Если конфигурации баз до и после идентичны, то тут сам Бог велел воспользоваться обработкой «ВыгрузкаЗагрузкаДанныхXML», либо такой же но с отбором(на Инфостарте есть такая). Но что если конфигурации баз различаются/значительно различаются?
Ниже опишу, как вышел из положения я.

 Сразу озвучу, что на истину в последней инстанции не претендую и если вы знаете более лёгкий, логичный и оптимальный способ, то отпишитесь, пожалуйста, в комментариях — я(да и всё сообщество) будет вам безмерно благодарно. 

 Итак, сама задача: есть доработанная база на конфигурации УТАП лохматого релиза(оптовая и розничная торговля алкоголем в одной базе). Обновить надо порядка 15 релизов. Изначально я готовил последний релиз с нашими изменениями и было согласовано с заказчиком, что он самостоятельно обновляется до типовых версий промежуточных релизов по ночам(а днём в это время пользователи работают на типовом функционале) и когда доходит до последнего релиза, мы восстанавливаем все наши изменения. 

 По факту получилось, что на одном из промежуточных релизов обновление дополнительных данных происходит более суток, что приведет к простою торговли и неприемлемо. 

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

 Первым делом загружаю файлы конфигураций старой и новой базы в КД2 и создаю новую конвертацию данных между ними.  ПКО генерирую автоматически и автоматически же создаю для них ПВД. 

 Основная идея: необходимо взять все объекты, которые были изменены/созданы с момента снятия копии в старой базе(в которой пользователи работали до обновления) и конвертировать их в аналогичные элементы новой базы. 

 Задача разбивается на две части:

  1. Выбрать только измененные данные с момента снятия копии в в старой базе;
  2. Конвертировать их в новую базу.

 Создаю параметры, которые понадобятся нам для отбора и хранения изменений:

Первую часть можно решить двумя способами:

 Первый способ — через журнал регистрации. Нам надо прочитать изменения начиная с момента создания копии("ДатаНачалаИзменений") и выбрать их в таблицу значений ("ИзмененныеДанные"). Для того, чтоб отобрать изменения из ЖР в таблицу значений, в событии конвертации "ПередВыгрузкойДанных" прописываем заполнение таблицы изменений: 

    ВнешниеДанные = Новый ТаблицаЗначений;
ФильтрЖурнала = Новый Структура;
МассивСобытий = Новый  Массив;
МассивСобытий.Добавить("_$Data$_.Post");
МассивСобытий.Добавить("_$Data$_.Delete");
МассивСобытий.Добавить("_$Data$_.Update");
ФильтрЖурнала.Вставить("ДатаНачала",НачалоДня(Параметры.ДатаНачалаИзменений));
ФильтрЖурнала.Вставить("ДатаОкончания",КонецДня(ТекущаяДата()));
ФильтрЖурнала.Вставить("Событие", МассивСобытий);
ВыгрузитьЖурналРегистрации(ВнешниеДанные,ФильтрЖурнала);
ВнешниеДанные.Свернуть("Данные");

Для каждого стр Из ВнешниеДанные Цикл
ст = ТЗИзменений.Найти(ТипЗнч(стр.Данные));
Если ст = Неопределено Тогда
ст          = ТЗИзменений.Добавить();
ст.Тип      = ТипЗнч(стр.Данные);
ст.Элементы = Новый Массив;
КонецЕсли;
ст.Элементы.Добавить(стр.Данные);
ст.Количество = ст.Количество + 1;
КонецЦикла;

Параметры.ИзмененныеДанные = ТЗИзменений.Скопировать();

  Плюс данного способа в том, что не требуется вносить изменения в исходную конфигурацию и можно начинать чтение в любой момент, но есть следующие минусы:

  • Может быть настроено удаление записей журнала регистрации — в этом случае надо договариваться с системным администратором, чтоб он отключил его временно;
  • Из ЖР не удастся извлечь изменения регистров, которые нам тоже нужны.

  Было решено воспользоваться вторым способом — через планы обмена. Я создал отдельный план обмена, в составе которого указал все необходимые объекты(Справочники, Документы, ПланыВидовХарактеристик, РегистрыСведений) с авторегистрацией.

 В пользовательском режиме, после того, как будет сделана копия, добавим новый узел в наш план обмена с кодом "002" и с этого момента все изменения объектов из плана обмена совершаемые пользователями, будут фиксироваться в данном узле.

 В событии "ПередВыгрузкойДанных" моей конвертации прописал: 

ТЗИзменений = Новый ТаблицаЗначений;
тзИзменений.Колонки.Добавить("Тип");
тзИзменений.Колонки.Добавить("Элементы");
тзИзменений.Колонки.Добавить("Количество",Новый ОписаниеТипа("Число"));

Узел = ПланыОбмена.ФиксацияДанныхДляОбновления.НайтиПоКоду("002");
ЗаписьXML = Новый ЗаписьXML();
ЗаписьXML.УстановитьСтроку();
ЗапСообщения = ПланыОбмена.СоздатьЗаписьСообщения();
ЗапСообщения.НачатьЗапись(ЗаписьXML, Узел);
Количество = 0;
Выборка = ПланыОбмена.ВыбратьИзменения(Узел, ЗапСообщения.НомерСообщения);
Пока Выборка.Следующий() Цикл
Данные = Выборка.Получить();
Попытка
Если Найти(Данные.Метаданные().ПолноеИмя(),"Регистр")=0 Тогда
ст = ТЗИзменений.Найти(ТипЗнч(Данные));
Если ст = Неопределено Тогда
ст          = ТЗИзменений.Добавить();
ст.Тип      = ТипЗнч(Данные);
ст.Элементы = Новый Массив;
КонецЕсли;
ст.Элементы.Добавить(Данные);
ст.Количество = ст.Количество + 1;
Иначе
ст = ТЗИзменений.Найти(ТипЗнч(Данные));
Если ст = Неопределено Тогда
ст          = ТЗИзменений.Добавить();
ст.Тип      = ТипЗнч(Данные);
ст.Элементы = Новый Массив;
КонецЕсли;
ст.Элементы.Добавить(Данные);
ст.Количество = ст.Количество + 1;
КонецЕсли;
Количество = Количество + 1;
Исключение
ПланыОбмена.УдалитьРегистрациюИзменений(Узел, Данные);
КонецПопытки;
КонецЦикла;

Параметры.ИзмененныеДанные = ТЗИзменений.Скопировать();

ЗапСообщения.ПрерватьЗапись(); 

 Создаю алгоритм, который будет выполняться в событии "ПередОбработкой" в каждом ПВД и отбирать данные из параметра "ИзмененныеДанные":

ЭтоРегистр = Ложь;
ИмяОбъекта = Правило.Наименование;

Если Найти("Справочники, ПланыВидовХарактеристики, Документы, БизнесПроцессы, Задачи",Строка(Правило.Родитель.Наименование))>0 тогда
ТипОбъектов = Правило.ОбъектВыборки;
Иначе
ТипОбъектов = Тип("РегистрСведенийНаборЗаписей."+Правило.Наименование);
ЭтоРегистр = Истина;
КонецЕсли;

строкаТипа = Параметры.ИзмененныеДанные.Найти(ТипОбъектов);

Если СтрокаТипа<>Неопределено Тогда
МассивКВыгрузке = СтрокаТипа.Элементы;
Если ЭтоРегистр Тогда
Для каждого Элемент Из МассивКВыгрузке Цикл
НЗ = РегистрыСведений[ИмяОбъекта].СоздатьНаборЗаписей();
Для каждого Измерение Из Метаданные.РегистрыСведений[ИмяОбъекта].Измерения Цикл
НЗ.Отбор[Измерение.Имя].Установить(Элемент.Отбор[Измерение.Имя].Значение);
КонецЦикла;
НЗ.Прочитать();
Для каждого Запись Из НЗ Цикл
ВыгрузитьПоПравилу(Запись,,,,ИмяПКО);
КонецЦикла;
КонецЦикла;
Иначе
ВыборкаДанных = МассивКВыгрузке;
КонецЕсли;
Сообщить("Выполнена выгрузка по ПВД """+ИмяОбъекта+""". Выгружено "+МассивКВыгрузке.Количество()+" объектов/записей.");
Иначе
Сообщить("Пропущено ПВД """+ИмяОбъекта+""" - не было найдено ни одного объекта.");
КонецЕсли;

  Следующим шагом требуется изменить ПВД всех обрабатываемых объектов(Справочники, Документы, ПланыВидовХарактеристик, РегистрыСведений) в "ПроизвольныйАлгоритм" и присвоить реквизиту "АлгоритмПередОбработкойПравила" посредством групповой обработки значение "Выполнить(Алгоритмы.ПВД_ПередОбработкой);". 

 По-идее, доработка конвертации таким образом, как у меня, необязательна и достаточно выбрать в обработке "Универсальный обмен данными в формате XML" план обмена, из которого необходимо выбирать изменения. Я сначала так пробовал, ещё на стадии отладки правил. Но заметил, чтоб после выгрузки изменений, осуществленной подобным образом у меня пропадают регистрации по изменениям справочников и документов. И закралось подозрение, что не выгружаются изменения регистров сведений. Потому реализовал так как описано выше. Возможно, было бы проще поправить обработку. 

 P.S. Чтоб вы знали — я, как истинно ленивый одинэсник, искал и другие способы попроще, чтоб перенести данные из старой базы в новую. В частности хотел попробовать посредством выгрузки через универсальный формат, но у меня в конфигурации отраслевые документы и потому он не подошел. Думал попробовать обработкой "Универсальная выгрузка/загрузка данных для отличающихся конфигураций (JSON, Такси+ОФ)" от уважаемой  Евгении Карук, но обработка при попытке загрузки первого же документа сказала мне 

Дальше решил судьбу не испытывать.

P.S.P.S.

 В процессе подготовки правил на тестовой базе выяснил, что подобным образом, как я описал выше изменения из плана обмена можно будет выбрать только один раз. При последующих попытках выбрать изменения им уже присвоены номера сообщений. Таким образом переделал алгоритм конвертации следующим образом: в обработчике события "ПередВыгрузкойДанных" конвертации закомментировал весь код, а в алгоритме "ПВД_ПередОбработкой" изменил код на следующий:

Узел = ПланыОбмена.ФиксацияДанныхДляОбновления.НайтиПоКоду("002");

КлассРодителя = Строка(Правило.Родитель.Наименование);

//Заменим в наименовании правила постфиксы, исключающие дубли
ИмяОбъекта    = СтрЗаменить(Правило.Наименование,"00001","");
ИмяОбъекта    = СтрЗаменить(Правило.Наименование,"00002","");
ИмяОбъекта    = СтрЗаменить(Правило.Наименование,"00003","");
ЭтоРегистр    = Ложь;

//Определим класс объектов правила
Если КлассРодителя = "ПланыВидовХарактеристик" Тогда
Класс = "ПланВидовХарактеристик";
ИначеЕсли КлассРодителя = "Задачи" Тогда
Класс = "Задача";
ИначеЕсли КлассРодителя = "РегистрыСведений" Тогда
Класс = "РегистрСведений";

Иначе
Класс = Лев(КлассРодителя,СтрДлина(КлассРодителя)-1) ;
КонецЕсли;

Если Найти("Справочники, ПланыВидовХарактеристики, Документы, БизнесПроцессы, Задачи",Строка(Правило.Родитель.Наименование))>0 тогда
ТипОбъектов = Правило.ОбъектВыборки;
ТипОбъекта  = ИмяОбъекта;
Иначе
ТипОбъектов = Тип("РегистрСведенийНаборЗаписей."+ИмяОбъекта);
ЭтоРегистр = Истина;
ТипОбъекта  = ИмяОбъекта;
КонецЕсли;

Если Не ЭтоРегистр Тогда
//{{КОНСТРУКТОР_ЗАПРОСА_С_ОБРАБОТКОЙ_РЕЗУЛЬТАТА
// Данный фрагмент построен конструктором.
// При повторном использовании конструктора, внесенные вручную изменения будут утеряны!!!

Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Изменения.Ссылка КАК Ссылка
|ИЗ
| <Класс>.<типОбъекта>.Изменения КАК Изменения
|ГДЕ
| Изменения.Узел = &Узел";

Запрос.Текст = СтрЗаменить(Запрос.Текст, "<Класс>",     Класс);
Запрос.Текст = СтрЗаменить(Запрос.Текст, "<типОбъекта>",ТипОбъекта);

Запрос.УстановитьПараметр("Узел", Узел);

РезультатЗапроса = Запрос.Выполнить();

ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать();

Выгружено  =   0;

Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
ВыгрузитьПоПравилу(ВыборкаДетальныеЗаписи.Ссылка,,,,ИмяПКО);
Выгружено  = Выгружено + 1;
КонецЦикла;
Если Выгружено > 0 Тогда
Сообщить(""+Класс+"."+ИмяПКО+" выгружено "+Выгружено);
КонецЕсли;
//}}КОНСТРУКТОР_ЗАПРОСА_С_ОБРАБОТКОЙ_РЕЗУЛЬТАТА
Иначе
РегистрСведений = Метаданные.РегистрыСведений[ИмяОбъекта];

//Исключим РС, не имеющие измерений с признаком "Основной отбор"
ЕстьИзмерения = Ложь;
Для каждого Измерение из РегистрСведений.Измерения Цикл
Если Измерение.ОсновнойОтбор Тогда
ЕстьИзмерения = Истина;
Прервать;
КонецЕсли;
КонецЦикла;

//Исключим измерения, подчиненные регистратору
Если РегистрСведений.РежимЗаписи = Метаданные.СвойстваОбъектов.РежимЗаписиРегистра.Независимый и ЕстьИзмерения тогда
Запрос = Новый Запрос;
Запрос.Текст =  "ВЫБРАТЬ
| *
|ИЗ
| РегистрСведений.<типОбъекта>.Изменения КАК Изменения
|ГДЕ
| Изменения.Узел = &Узел";
Запрос.Текст = СтрЗаменить(Запрос.Текст, "<типОбъекта>",ТипОбъекта);

Запрос.УстановитьПараметр("Узел", Узел);

РезультатЗапроса = Запрос.Выполнить();

ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать();
Выгружено  =   0;
Пока ВыборкаДетальныеЗаписи.Следующий() Цикл

СтруктураОтбора = Новый Структура;

Если Не РегистрСведений.ПериодичностьРегистраСведений = Метаданные.СвойстваОбъектов.ПериодичностьРегистраСведений.Непериодический Тогда
СтруктураОтбора.Вставить("Период");
КонецЕсли;

Для каждого Измерение Из Метаданные.РегистрыСведений[ИмяОбъекта].Измерения Цикл
СтруктураОтбора.Вставить(Измерение.Имя);
//НЗ.Отбор[Измерение.Имя].Установить(Элемент.Отбор[Измерение.Имя].Значение);
КонецЦикла;

ЗаполнитьЗначенияСвойств(СтруктураОтбора, ВыборкаДетальныеЗаписи);

НЗ = РегистрыСведений[ИмяОбъекта].СоздатьНаборЗаписей();

Для каждого Элемент Из СтруктураОтбора Цикл
НЗ.Отбор[Элемент.Ключ].Установить(Элемент.Значение);
КонецЦикла;

НЗ.Прочитать();
Для каждого Запись Из НЗ Цикл
ВыгрузитьПоПравилу(Запись,,,,ИмяПКО);
КонецЦикла;
Выгружено  = Выгружено + 1;
КонецЦикла;
Если Выгружено > 0 Тогда
Сообщить(""+Класс+"."+ИмяПКО+" выгружено "+Выгружено);
КонецЕсли;


КонецЕсли;
КонецЕсли;

 Теперь всё работает без нареканий.

Буду рад, если мой опыт окажется кому-нибудь полезен!

Спасибо за внимание!

2 Comments

  1. user-z99999

    1) Берем пустую базу без пользователей и обновляем. (пустая база получается из cf-файла рабочей базы)

    2) Выгружаем cf-файл. т.е. уже база обновлена.

    3) Накатываем cf-файл на тестовую базу и проверяем. Если надо тестируем правила КД 2.0

    4) из шага 2 накатываем cf-файл на рабочую базу (Конфигурация — Загрузить конфигурацию из файла)

    Покритикуйте такой подход.

    Reply
  2. al_zzz

    (1) Я не буду критиковать ваш подход. Сам по сути так делаю, когда изменения касаются лишь структуры. В моём же случае происходит обработка данных в регламентных, запускающихся после обновления. Вот они-то и выполняются очень долго. А если их пропустить, то розничные продажи в обновленной базе не выполняются.

    Reply

Leave a Comment

Ваш адрес email не будет опубликован. Обязательные поля помечены *