Мы обнаружили недостаток утилиты выгрузки тел документов при миграции из Directum 5 в Directum RX в некоторых ситуациях и предложили решение для оптимизации процесса переноса.
Программа для переноса файлов из хранилищ Directum 5 в Directum RX располагается в папке RXStorageMigrator конвертера. В случае отсутствия тел документов в SQL-хранилище базовая версия утилиты переноса тел будет работает корректно, однако основной недостаток заключается в выгрузке тел в оперативную память при переносе тел документов, располагающихся в SQL-хранилище, что в свою очередь будет работать со сбоями при переносе объема данных выше вместительности оперативной памяти.
Ниже предложена модификация исходного кода данной утилиты для поочередной выгрузки в оперативную память и переноса тел документов в хранилища Directum RX.
Исходные коды хранятся в папке RXStorageMigrator_src конвертера. Код выгрузки всех тел документов в оперативную память располагается в файле StorageMigrtator.cs в методе GetMigratedFiles и вызывается внутри метода MigrateBodiesFromD5ToRXFS.
/// <summary>
/// Получить список файлов для миграции.
/// </summary>
/// <param name="connection">Соединение.</param>
/// <returns>Список файлов, подлежащих миграциия.</returns>
private List<IFSFile> GetMigratedFiles(SqlConnection connection)
{
var result = new List<IFSFile>();
var query = Constants.ConstLib.SelectMigratedDocsFromD5;
var command = new SqlCommand(query, connection);
command.CommandTimeout = 0;
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
var file = new FSFile();
file.Id = reader.GetInt32(0);
file.Version = reader.GetInt32(1);
file.SourceFileStorage = this.SourceFileStorages[reader.GetInt32(2)];
//Guid bodyId;
//var guidToParse = reader.GetString(3);
//if (!Guid.TryParse(guidToParse, out bodyId))
//{
// Program.logger.Error($"Body with id {guidToParse} is ignored.");
// continue;
//}
file.BodyId = reader.GetGuid(3);
file.Body = reader.IsDBNull(4) ? null : (byte[])reader[4];
file.Extension = reader.GetString(5);
file.RXDocId = reader.GetInt32(6);
file.TargetFileStorage = this.TargetFileStorages[reader.GetInt32(7)];
file.MigrStatus = Constants.ConstLib.MigrStatusError;
file.SourceFilePath = string.Empty;
result.Add(file);
}
}
return result;
}
Оптимизация заключается в переносе тела документа сразу после его получения внутри метода GetMigratedFiles с помощью вызова метода MigrateFile.
/// <summary>
/// Получить список файлов для миграции.
/// </summary>
/// <param name="connection">Соединение.</param>
/// <returns>Список файлов, подлежащих миграциия.</returns>
private void GetMigratedFiles(SqlConnection connection)
{
// Пути, к которым не удалось получить доступ.
var errorPath = new System.Collections.Concurrent.ConcurrentDictionary<int, string>();
var query = Constants.ConstLib.SelectMigratedDocsFromD5;
var command = new SqlCommand(query, connection);
byte[] fileBody;
command.CommandTimeout = 0;
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
var file = new FSFile();
file.Id = reader.GetInt32(0);
file.Version = reader.GetInt32(1);
file.SourceFileStorage = this.SourceFileStorages[reader.GetInt32(2)];
file.BodyId = reader.GetGuid(3);
fileBody = reader.IsDBNull(4) ? null : (byte[])reader[4];
file.Extension = reader.GetString(5);
file.RXDocId = reader.GetInt32(6);
file.TargetFileStorage = this.TargetFileStorages[reader.GetInt32(7)];
file.MigrStatus = Constants.ConstLib.MigrStatusError;
file.SourceFilePath = string.Empty;
MigrateFile(file, fileBody, errorPath);
}
}
}
public void MigrateFile(FSFile file, byte[] body, ConcurrentDictionary<int, string> errorPath)
{
Console.WriteLine($"Перенос тела документа с RX Id = {file.RXDocId} и 5 Id = {file.BodyId}");
if (file.SourceFileStorage.StorageType == Constants.ConstLib.SQLStorage)
{
MigrateBodyFromDBToFS(file, body);
Console.WriteLine($"Завершено");
}
else
{
var message = string.Empty;
var storagePath = file.SourceFileStorage.Path;
if (!errorPath.ContainsKey(file.SourceFileStorage.Id))
{
try
{
file.SourceFilePath = Directory.EnumerateFiles(file.SourceFolderPath, "*.*")
.FirstOrDefault(x => x.EndsWith($"({file.Id} v{file.Version}).{file.Extension}") && !x.Contains("~$"));
if (string.IsNullOrWhiteSpace(file.SourceFilePath))
{
var files = Directory.EnumerateFiles(file.SourceFolderPath, "*.*");
foreach (var item in files)
{
if (Path.GetFileNameWithoutExtension(item).EndsWith($"({file.Id} v{file.Version})"))
{
file.SourceFilePath = item;
break;
}
}
}
}
catch (Exception e)
{
try
{
Directory.GetFiles(storagePath);
}
catch
{
errorPath.TryAdd(file.SourceFileStorage.Id, e.Message);
}
message = $"Ошибка при получении доступа к папке {storagePath}: {e.Message}";
Program.logger.Error(message);
if (this.FixFileStorageError)
SetDataInTransferredTable(file, message);
return;
}
if (string.IsNullOrWhiteSpace(file.SourceFilePath))
{
message = $"Файл с окончанием '({file.Id} v{file.Version}).{file.Extension}' в папке по пути {file.SourceFolderPath} не найден.";
Program.logger.Error(message);
SetDataInTransferredTable(file, message);
return;
}
MigrateBodyFromFSToFS(file);
Console.WriteLine($"Завершено");
}
else
{
if (this.FixFileStorageError)
SetDataInTransferredTable(file, $"Ошибка при получении доступа к папке {storagePath}: {errorPath[file.SourceFileStorage.Id]}");
}
}
}
В данном коде также расширено логирование для мониторинга активности переноса тел документов. Он переносит файл сразу в момент обработки записи из результирующего запроса к БД Directum 5, работает в одном потоке и может быть модифицирован.
В методе MigrateBodiesFromD5ToRXFS необходимо убрать лишнюю обработку файлов. Итоговый код метода будет выглядеть так:
public void MigrateBodiesFromD5ToRXFS()
{
try
{
using (var connection = new SqlConnection())
{
connection.ConnectionString = this.ConnectionStringD5;
connection.Open();
Console.WriteLine("Connection State: {0}", connection.State);
var query = Constants.ConstLib.SelectCountMigratedDocsFromD5;
var command = new SqlCommand(query, connection);
command.CommandTimeout = 0;
GetMigratedFiles(connection);
}
}
catch (Exception ex)
{
Program.logger.Error(ex);
}
}
Авторизуйтесь, чтобы написать комментарий