Работа с многопользовательскими базами данных в платформе «1С:Предприятие 8» неизбежно сталкивает разработчиков с механизмами блокировок объектов. Блокировки необходимы для предотвращения одновременного редактирования одних и тех же данных разными пользователями. Однако поведение платформы при проверке состояния блокировки, а также специфика работы через веб-сервисы часто вызывают вопросы у начинающих и даже опытных специалистов. Разберем по шагам, почему стандартные методы могут работать не так, как ожидается, как правильно проверять занятость объектов и как побороть задержки при обновлении данных на клиенте.
Проанализируем ситуацию: разработчик пытается вызвать метод Объект.Заблокирован() для того, чтобы узнать, редактирует ли кто-то документ в данный момент. К удивлению разработчика, этот метод возвращает Ложь, хотя форма документа открыта и изменяется другим пользователем в соседнем сеансе. Выясним причину такого поведения.
Согласно синтакс-помощнику платформы «1С:Предприятие», метод Заблокирован() предназначен исключительно для проверки локального контекста текущего программного объекта в текущем сеансе. Он возвращает значение Истина только в том случае, если данный конкретный экземпляр объекта (переменная, к которой применяется метод) был заблокирован вызовом метода Заблокировать() в этом же самом сеансе. Данный метод физически не способен опросить базу данных на предмет того, наложена ли пессимистическая блокировка на соответствующую запись СУБД из других сеансов.
Рассмотрим единственный надежный способ узнать, свободен ли объект базы данных для редактирования сторонними пользователями. Для этого нам необходимо программно попытаться наложить на него пессимистическую блокировку внутри конструкции Попытка — Исключение.
Если объект свободен, блокировка установится успешно, после чего мы должны сразу ее снять, чтобы не мешать другим пользователям. Если же объект уже занят другим сеансом, платформа сгенерирует исключение, которое мы перехватим и обработаем. Посмотрим на пример кода, реализующий эту логику:
Функция ОбъектСвободенДляРедактирования(СсылкаНаОбъект) Экспорт
ПрограммныйОбъект = СсылкаНаОбъект.ПолучитьОбъект();
Попытка
ПрограммныйОбъект.Заблокировать();
// Если код дошел до этой строки, объект успешно заблокирован нами
// Сразу снимаем блокировку, чтобы не занимать объект
ПрограммныйОбъект.Разблокировать();
Возврат Истина;
Исключение
// Объект заблокирован другим пользователем или сеансом
Возврат Ложь;
КонецПопытки;
КонецФункции
Данный подход является классическим, но имеет один важный нюанс: если мы используем метод Заблокировать(), блокировка привязывается к программному объекту в памяти. Если в коде произойдет критическая ошибка до вызова Разблокировать(), объект может остаться заблокированным вплоть до завершения текущего сеанса. Чтобы избежать этого, рассмотрим более современный и безопасный метод.
В управляемом приложении платформы «1С:Предприятие 8» (начиная с версии 8.2) стандартом разработки является использование глобального метода ЗаблокироватьДанныеДляРедактирования(). Этот метод имеет принципиальные преимущества перед вызовом Заблокировать() у объекта:
Разблокировать() в обработчиках закрытия.Разберем пример использования этого метода при открытии формы документа, чтобы ограничить доступ, если документ уже кем-то занят:
&НаСервере
Процедура ПриОткрытииНаСервере()
Попытка
// Пытаемся заблокировать данные с привязкой к текущей форме
ЗаблокироватьДанныеДляРедактирования(Объект.Ссылка, , УникальныйИдентификатор);
Исключение
// Блокировка не удалась, переводим форму в режим "Только просмотр"
ЭтаФорма.ТолькоПросмотр = Истина;
ПоказатьПредупреждениеПользователю(ОписаниеОшибки());
КонецПопытки;
КонецПроцедуры
Проанализируем крайне интересную проблему, с которой часто сталкиваются разработчики интеграционных решений. Внешняя система обращается к 1С через веб-сервис, открывает объект, изменяет его, записывает и вызывает метод Разблокировать(). Однако реальный пользователь в интерфейсе 1С при попытке сразу же зайти в этот документ получает ошибку: «Операция не может быть выполнена из-за несоответствия версии или отсутствия записи базы данных». При этом через 10 секунд ошибка исчезает сама собой.
Выясним причину этой задержки. В платформе 1С реализованы два типа блокировок: пессимистическая (когда мы явно блокируем запись в СУБД) и оптимистическая (основанная на версионировании объектов).
Когда веб-сервис записывает объект базы данных, у этого объекта в СУБД обновляется скрытый реквизит — ВерсияДанных. В этот же момент на клиенте у пользователя в открытой форме или в кэше динамического списка хранится старая версия объекта. На стороне клиентского приложения работает так называемый сеансовый кэш метаданных и версий объектов. Период обновления этого кэша (опроса таблиц базы данных) по умолчанию составляет около 10 секунд.
Пока эти 10 секунд не истекут, клиентское приложение пользователя «не знает», что внешняя система через веб-сервис обновила версию данных в СУБД. Когда пользователь пытается начать редактирование или провести документ, платформа сравнивает устаревшую версию из кэша клиента с новой версией из СУБД и блокирует действие ошибкой несоответствия версий. Как только проходит интервал обновления кэша, клиент считывает актуальную версию данных, и работа возобновляется.
Чтобы исключить появление ошибки несоответствия версий у интерактивных пользователей после программной работы веб-сервисов или фонового обновления данных, необходимо принудительно синхронизировать кэш клиента с сервером базы данных. Для этого в коде формы или перед началом редактирования следует вызывать метод Прочитать(). Рассмотрим пример:
&НаКлиенте
Процедура ПередНачаломРедактирования(Отказ, ПараметрыЗаписи)
// Принудительно перечитываем данные из СУБД, сбрасывая локальный кэш
Попытка
ПрочитатьОбъектНаСервере();
Исключение
Отказ = Истина;
ОбщегоНазначенияКлиентСервер.СообщитьПользователю("Не удалось обновить данные объекта: " + ОписаниеОшибки());
КонецПопытки;
КонецПроцедуры
&НаСервере
Процедура ПрочитатьОбъектНаСервере()
// Метод Прочитать() обновляет данные программного объекта непосредственно из базы данных
ЗначениеВРеквизитФормы(РеквизитФормыВЗначение("Объект").Ссылка.ПолучитьОбъект(), "Объект");
КонецПроцедуры
Использование вызова Прочитать() или обновление реквизита формы напрямую из актуального объекта базы данных мгновенно решает проблему «зависания» версий на 10 секунд, обеспечивая пользователям комфортную и непрерывную работу.
Часто бизнес-процессы требуют не просто сообщить пользователю о том, что объект занят, но и вывести понятное сообщение с указанием имени коллеги, который в данный момент редактирует документ. Платформа 1С помещает эти данные в системное описание ошибки блокировки.
Рассмотрим, как можно программно извлечь эту информацию. Текст системной ошибки блокировки обычно содержит подстроку вида «Объект уже заблокирован пользователем: Администратор». Мы можем распарсить строку с помощью строковых функций платформы:
&НаСервере
Функция ПолучитьИнформациюОБлокировке(СсылкаНаОбъект) Экспорт
ПрограммныйОбъект = СсылкаНаОбъект.ПолучитьОбъект();
Попытка
ПрограммныйОбъект.Заблокировать();
ПрограммныйОбъект.Разблокировать();
Возврат ""; // Объект свободен
Исключение
ТекстОшибки = ОписаниеОшибки();
// Попытаемся вырезать имя пользователя из системного сообщения
КлючевоеСлово = "заблокирован";
Позиция = Найти(НРег(ТекстОшибки), КлючевоеСлово);
Если Позиция > 0 Тогда
Возврат СокрЛП(Сред(ТекстОшибки, Позиция));
Иначе
Возврат "Объект занят другим сеансом.";
EndIf;
КонецПопытки;
КонецФункции
Важное примечание: Если в вашей конфигурации используется Библиотека Стандартных Подсистем (БСП), для решения этой задачи настоятельно рекомендуется использовать готовые функции БСП, такие как функции общего назначения для разбора структуры ошибок блокировок. Они корректно обрабатывают мультиязычные интерфейсы и различные СУБД (файловый вариант, MS SQL, PostgreSQL), предоставляя разработчику точные данные о блокирующем сеансе без ручного разбора строк.