При интеграции различных баз 1С через COM-соединение (OLE) разработчики часто сталкиваются с неочевидными ошибками. Рассмотрим одну из таких проблем: при попытке найти документ в другой базе с помощью метода НайтиПоНомеру платформа прерывает выполнение с сообщением: «Поле объекта не доступно для записи (Дата)». Выясним причину этой ошибки и разберем по шагам, как правильно ее исправить.
Представим, что мы пишем обработку для синхронизации документов между двумя базами. Мы перебираем выборку документов в текущей базе и пытаемся найти соответствующие документы в базе-приемнике (базе OLE). Посмотрим на пример кода, который вызывает ошибку:
бухСФВыдан = БазаОле.CreateObject("Документ.СчетФактура");
Выборка = Документы.СчетФактураВыданный.Выбрать(НачалоДня(Отчет.НачалоПериода), КонецДня(Отчет.КонецПериода));
Пока Выборка.Следующий() Цикл
Если бухСФВыдан.НайтиПоНомеру(ПрефиксацияОбъектовКлиентСервер.НомерНаПечать(Выборка.Номер), Выборка.Дата) = 0 Тогда
Сообщить("Не нашли документ - " + Выборка);
Продолжить;
КонецЕсли;
КонецЦикла;
Внешне код выглядит абсолютно корректным. Мы не пытаемся присвоить новое значение полю Выборка.Дата, а лишь передаем его в качестве параметра для поиска. Однако платформа упорно выдает ошибку на строке с условием.
Чтобы понять суть проблемы, разберем особенности работы COM/OLE-соединений в 1С. По умолчанию методы встроенного языка 1С при вызове через COM передают параметры по ссылке (ByRef). Это означает, что вызываемый метод в базе-приемнике имеет право изменить переданную ему переменную, и COM-интерфейс попытается записать это новое значение обратно в исходную базу.
В нашем случае параметром выступает Выборка.Дата. Поскольку переменная Выборка является результатом запроса или выборки документов, все ее поля (включая дату и номер) доступны только для чтения. Когда COM-объект пытается вернуть измененное значение и перезаписать это свойство, платформа блокирует операцию, выдавая ошибку.
Возникает закономерный вопрос: почему метод НайтиПоНомеру вообще пытается что-то записать в параметр даты? Ведь дата нужна только для того, чтобы ограничить период поиска (период уникальности номеров документов).
Здесь кроется слабо задокументированная особенность платформы. Второй параметр метода НайтиПоНомеру(Номер, Дата) используется не только для указания интервала. Если документ успешно найден, платформа модифицирует этот параметр, записывая в него фактическую дату найденного документа. Например, если мы передали дату «26.10.2021 12:00:08», а найденный документ имеет время «26.10.2021 0:00:00», платформа попытается вернуть именно эту новую дату обратно в вызывающую среду.
Самый простой и быстрый способ исправить эту ошибку — отказаться от передачи реквизитов выборки напрямую. Закинем номер и дату в обычные локальные переменные и передадим уже их. Локальные переменные доступны для перезаписи, поэтому COM-интерфейс сможет без проблем вернуть в них измененные значения.
Посмотрим на исправленный вариант кода:
бухСФВыдан = БазаОле.CreateObject("Документ.СчетФактура");
Выборка = Документы.СчетФактураВыданный.Выбрать(НачалоДня(Отчет.НачалоПериода), КонецДня(Отчет.КонецПериода));
Пока Выборка.Следующий() Цикл
// Используем промежуточные переменные
ТекНомер = Выборка.Номер;
ТекДата = Выборка.Дата;
// Передаем локальные переменные в метод поиска
Если бухСФВыдан.НайтиПоНомеру(ПрефиксацияОбъектовКлиентСервер.НомерНаПечать(ТекНомер), ТекДата) = 0 Тогда
Сообщить("Не нашли документ - " + Выборка);
Продолжить;
КонецЕсли;
КонецЦикла;
Теперь ошибка «Поле объекта не доступно для записи» больше не появится, так как мы передаем обычную переменную ТекДата, в которую метод сможет безопасно записать фактическую дату документа.
Хотя первый метод решает проблему, рассмотрим более профессиональный подход. Опытные разработчики знают, что вызов объектных методов, таких как НайтиПоНомеру или НайтиПоКоду, в цикле через OLE-соединение работает крайне медленно из-за высоких накладных расходов на каждый COM-вызов.
Гораздо эффективнее и безопаснее отказаться от объектного поиска и использовать выполнение запроса в базе-приемнике. В запросах параметры всегда передаются строго по значению, что полностью исключает подобные ошибки с перезаписью полей. Кроме того, запрос отработает значительно быстрее.
Разберем пример того, как можно реализовать проверку наличия документов через запрос:
ЗапросОле = БазаОле.NewObject("Запрос");
ЗапросОле.Текст =
"ВЫБРАТЬ
| СчетФактураВыданный.Ссылка КАК Ссылка
|ИЗ
| Документ.СчетФактураВыданный КАК СчетФактураВыданный
|ГДЕ
| СчетФактураВыданный.Номер = &Номер
| И СчетФактураВыданный.Дата МЕЖДУ &НачалоГода И &КонецГода";
Пока Выборка.Следующий() Цикл
ТекНомерДляПоиска = ПрефиксацияОбъектовКлиентСервер.НомерНаПечать(Выборка.Номер);
ЗапросОле.УстановитьПараметр("Номер", ТекНомерДляПоиска);
ЗапросОле.УстановитьПараметр("НачалоГода", НачалоГода(Выборка.Дата));
ЗапросОле.УстановитьПараметр("КонецГода", КонецГода(Выборка.Дата));
РезультатОле = ЗапросОле.Выполнить().Выбрать();
Если Не РезультатОле.Следующий() Тогда
Сообщить("Не нашли документ - " + Выборка);
Продолжить;
КонецЕсли;
КонецЦикла;
В этом примере мы используем объект Запрос через COM-соединение. Мы передаем параметры без риска их случайного изменения и получаем результат значительно быстрее, минимизируя количество обращений к OLE-объектам.
Выборка, которые доступны только для чтения.НайтиПоНомеру неявно изменяет параметр даты, возвращая в него фактическую дату найденного документа.