При переходе с обычных форм на управляемые разработчики часто сталкиваются с тем, что привычный код для работы с файлами перестает работать в клиент-серверном варианте базы (на SQL), хотя отлично функционирует в файловом режиме. Давайте подробно разберем эту ситуацию, проанализируем частые ошибки и выясним, как правильно организовать сохранение и чтение настроек.
Рассмотрим типичный пример кода, который вызывает ошибку в клиент-серверном варианте. Разработчик выбирает файл на клиенте, сохраняет путь к нему в реквизит, а затем пытается прочитать этот файл в серверной процедуре:
&НаСервере
Процедура ПрочитатьНастройкиИзФайлаНаСервере()
Объект.СопоставленияПлатежей.Очистить();
ТекНастройки = Новый ЧтениеТекста;
// ОШИБКА ЗДЕСЬ: Попытка открыть локальный файл клиента на сервере
ТекНастройки.Открыть(Объект.ФайлНастроек);
СтрокаНастроек = ТекНастройки.ПрочитатьСтроку();
// ... дальнейший парсинг ...
КонецПроцедуры
Выясним причину такого поведения. В файловой базе клиент и сервер физически выполняются в одном процессе на одной машине, поэтому серверный контекст имеет доступ к локальному диску `C:\` пользователя. Однако в полноценной клиент-серверной архитектуре сервер 1С — это отдельный компьютер. Когда код, выполняющийся под директивой &НаСервере, пытается обратиться по пути локального файла пользователя (например, C:\Users\User\Desktop\settings.txt), он ищет этот файл на жестком диске самого сервера 1С. Разумеется, файла там нет, и система выдает ошибку.
Чтобы код работал корректно, нам нужно передать содержимое файла с клиента на сервер. В платформе 1С для этого используется механизм временного хранилища. Разберем по шагам правильный алгоритм действий:
&НаКлиенте.&НаСервере.В современных релизах платформы (начиная с 8.3.18) рекомендуется использовать асинхронные методы работы с файлами. Посмотрим на пример правильной загрузки файла:
&НаКлиенте
Асинх Процедура ЗагрузитьНастройки(Команда)
Диалог = Новый ДиалогВыбораФайла(РежимДиалогаВыбораФайла.Открытие);
Диалог.Фильтр = "Текстовые файлы (*.txt)|*.txt";
// Ожидаем выбора файла пользователем
РезультатВыбора = Ждать Диалог.ВыбратьАсинх();
Если РезультатВыбора = Неопределено Тогда
Возврат;
КонецЕсли;
// Помещаем файл во временное хранилище
ПараметрыПомещения = Новый ПараметрыПомещенияФайловНаСервер;
РезультатПомещения = Ждать ПоместитьФайлНаСерверАсинх(РезультатВыбора[0], УникальныйИдентификатор, ПараметрыПомещения);
// Передаем адрес хранилища на сервер
ПрочитатьНастройкиНаСервере(РезультатПомещения.Адрес);
КонецПроцедуры
&НаСервере
Процедура ПрочитатьНастройкиНаСервере(АдресВоВременномХранилище)
ДанныеФайла = ПолучитьИзВременногоХранилища(АдресВоВременномХранилище);
// Теперь переменная ДанныеФайла содержит ДвоичныеДанные, которые можно прочитать
// ...
КонецПроцедуры
Если нет строгой необходимости передавать настройки именно в виде текстового файла между разными базами, лучше вообще отказаться от работы с файловой системой. В платформу встроены мощные механизмы: ХранилищеОбщихНастроек и ХранилищеСистемныхНастроек.
Рассмотрим преимущества этого подхода. Встроенные хранилища позволяют одной строкой кода сохранить таблицу значений, структуру или массив прямо в базу данных. Система автоматически привязывает эти настройки к текущему пользователю и указанному ключу объекта.
&НаСервере
Процедура СохранитьНастройкиВБазу()
КлючОбъекта = "МояОбработка_СопоставлениеПлатежей";
КлючНастроек = "ОсновныеНастройки";
// Выгружаем данные табличной части в Таблицу Значений
ТаблицаНастроек = Объект.СопоставленияПлатежей.Выгрузить();
// Сохраняем в системное хранилище
ХранилищеОбщихНастроек.Сохранить(КлючОбъекта, КлючНастроек, ТаблицаНастроек);
КонецПроцедуры
&НаСервере
Процедура ЗагрузитьНастройкиИзБазы()
КлючОбъекта = "МояОбработка_СопоставлениеПлатежей";
КлючНастроек = "ОсновныеНастройки";
ТаблицаНастроек = ХранилищеОбщихНастроек.Загрузить(КлючОбъекта, КлючНастроек);
Если ТаблицаНастроек <> Неопределено Тогда
Объект.СопоставленияПлатежей.Загрузить(ТаблицаНастроек);
КонецЕсли;
КонецПроцедуры
Этот метод полностью снимает все вопросы с передачей файлов между клиентом и сервером и работает молниеносно.
В исходном коде автора используется формирование строки через кастомный разделитель & с последующим разбиением через функцию СтрРазделить. Проанализируем, почему этот подход является хрупким.
Если в наименовании контрагента или договора встретится символ амперсанда (например, "Рога & Копыта"), массив сместится. Индексы элементов перестанут соответствовать ожидаемым колонкам, и код завершится с ошибкой или запишет некорректные данные.
Вместо ручного разбора строк в современной разработке на 1С принято использовать JSON или XML сериализацию. Платформа 1С умеет автоматически конвертировать большинство структур данных в стандартные форматы, заботясь об экранировании всех спецсимволов. Рассмотрим пример преобразования массива структур в JSON:
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.УстановитьСтроку();
// Предположим, ДанныеНастроек - это массив или структура
СериализаторXDTO.ЗаписатьJSON(ЗаписьJSON, ДанныеНастроек);
СтрокаJSON = ЗаписьJSON.Закрыть();
И наконец, разберем еще одну критическую ошибку в логике восстановления данных. В процедуре загрузки настроек мы видим следующий код:
СтрСопоставленияПлатежей.Контрагент = Справочники.Контрагенты.НайтиПоНаименованию(МассивСтроки[1]);
Искать ссылки по текстовому наименованию — это серьезный антипаттерн в 1С. Наименования справочников часто меняются пользователями (исправление опечаток, смена формы собственности ООО/ЗАО, добавление пробелов). Кроме того, в базе могут быть дубли наименований. При следующей загрузке файла система либо найдет не того контрагента, либо вернет пустую ссылку.
Как делать правильно? Если настройки выгружаются во внешний файл, сохранять и читать нужно не строковые имена, а Уникальный Идентификатор (GUID) ссылочного объекта. При записи в файл мы сохраняем результат функции Строка(Контрагент.УникальныйИдентификатор()). А при чтении восстанавливаем ссылку надежным методом:
// Восстановление ссылки по GUID из строки файла
ГУИД_Контрагента = Новый УникальныйИдентификатор(СтрокаИзФайла);
СтрСопоставленияПлатежей.Контрагент = Справочники.Контрагенты.ПолучитьСсылку(ГУИД_Контрагента);
Использование GUID гарантирует 100% точное сопоставление данных вне зависимости от переименований элементов в базе данных.
Применяя описанные подходы — временные хранилища, встроенные механизмы сериализации и сохранение ссылок по уникальным идентификаторам — вы создадите надежный и отказоустойчивый код, который будет корректно работать в любых архитектурах 1С.