При работе с внешними источниками данных (ВИД) в 1С программисты часто сталкиваются с ситуацией, когда попытка изменить существующую запись в сторонней базе данных (например, MS SQL Server) приводит к критической ошибке: "Невозможно вставить явное значение для столбца идентификаторов в таблице, когда параметр IDENTITY_INSERT имеет значение OFF". Эта проблема особенно актуальна для таблиц, в которых первичный ключ является автоинкрементным полем (Identity).
В этой статье мы подробно разберем, почему возникает эта ошибка, как работает механизм записи 1С во внешних источниках и рассмотрим несколько способов решения этой задачи: от настройки метаданных до выполнения прямых SQL-запросов.
Для начала проанализируем, что происходит "под капотом" платформы 1С при вызове метода Записать() у менеджера записи внешнего источника данных. Рассмотрим типичный сценарий: у нас есть таблица, подключенная как "Необъектные данные". Мы считываем запись по ключу, меняем одно поле и пытаемся сохранить.
Проблема заключается в том, что для необъектных таблиц платформа 1С часто реализует метод Записать() не через команду UPDATE, а через связку DELETE и INSERT. То есть текущая запись удаляется, и на ее место вставляется новая с обновленными данными. Поскольку 1С пытается передать в команду INSERT старое значение ключевого поля (которое в SQL Server помечено как IDENTITY), СУБД блокирует операцию, так как по умолчанию запрещено вручную указывать значения для автоинкрементных столбцов.
Даже если 1С пытается выполнить UPDATE, она может включать поле ключа в секцию SET, что также вызывает недовольство со стороны SQL-сервера.
Самым надежным и производительным способом решения проблемы является использование механизма прямого выполнения SQL-запросов. Начиная с версии 8.3.10, платформа позволяет отправлять команды внешнему источнику напрямую. Это позволяет нам выполнить классический UPDATE, который не затронет поле IDENTITY.
Рассмотрим пример кода, который заменяет использование менеджера записи на прямой запрос:
// Определяем параметры подключения и текст запроса
ИмяИсточника = "УчетВесовогоМатериала";
ТекстЗапроса = "UPDATE build_main SET ЗагруженоВ1С = 1 WHERE НомерСтроки = " + Формат(СтрокаТЗ.НомерСтроки, "ЧГ=0");
// Выполняем команду напрямую в обход объектной модели 1С
ВнешниеИсточникиДанных[ИмяИсточника].Выполнить(ТекстЗапроса);
Этот метод хорош тем, что мы точно контролируем, какие поля обновляются. Секция WHERE гарантирует изменение нужной строки, а отсутствие ключевого поля в секции SET снимает проблему с IDENTITY_INSERT.
Если специфика задачи позволяет, проанализируем возможность изменения настроек внешнего источника в конфигураторе. В свойствах таблицы внешнего источника можно изменить тип данных с "Необъектные данные" на "Объектные данные".
В чем преимущество этого подхода? Когда таблица помечена как объектная, 1С начинает работать с ней аналогично справочникам. В этом случае вместо менеджера записи используется объектная модель (СоздатьОбъект()), и при вызове Записать() платформа генерирует корректный SQL-запрос UPDATE, где ключ используется только для идентификации строки (в секции WHERE), а не для изменения данных. Это автоматически решает проблему прав доступа к полю-идентификатору.
Разберем ситуацию, когда нам все же необходимо использовать стандартный менеджер записи. В конфигураторе у каждого поля таблицы внешнего источника есть ряд свойств, которые влияют на формирование SQL-запросов.
Истина для всех полей, которые не должны обновляться (включая ключевое поле IDENTITY).DEFAULT, то 1С при попытке выполнить INSERT передаст серверу инструкцию использовать значение по умолчанию (автоинкремент самого SQL).Однако помните, что для необъектных данных (аналогов регистров) платформа все равно может пытаться удалить и вставить запись, что может привести к изменению самого значения ID, если вы не контролируете этот процесс.
В современных версиях платформы (8.3.5+) для наборов записей внешних источников доступен метод УстановитьЗаписываемыеПоля(). Рассмотрим, как это можно применить для решения нашей задачи. Мы можем явно указать платформе, какие колонки должны участвовать в операции записи, исключив из них проблемный идентификатор.
Проанализируем пример кода:
НаборЗаписей = ВнешниеИсточникиДанных.УчетВесовогоМатериала.Таблицы.ОсновнаяТаблицаУчета.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.НомерСтроки.Установить(СтрокаТЗ.НомерСтроки);
НаборЗаписей.Прочитать();
// Если запись найдена
Если НаборЗаписей.Количество() > 0 Тогда
Запись = НаборЗаписей[0];
Запись.ЗагруженоВ1С = 1;
// Указываем, что записывать нужно ТОЛЬКО поле ЗагруженоВ1С
МассивПолей = Новый Массив;
МассивПолей.Добавить("ЗагруженоВ1С");
НаборЗаписей.УстановитьЗаписываемыеПоля(МассивПолей);
НаборЗаписей.Записать(Истина); // Истина - замещение
КонецЕсли;
Важное замечание: При использовании Записать(Истина) в необъектных данных 1С все равно попытается выполнить DELETE, а затем INSERT. Если ваша цель — именно UPDATE без удаления, лучше вернуться к Решению 1.
Иногда странное поведение 1С при работе с внешними источниками связано с используемым драйвером ODBC. Проанализируем строку ошибки из примера: используется SQL Server Native Client 11.0. Это довольно старый драйвер. Для более стабильной работы и корректной трансляции типов данных рекомендуется использовать современные драйверы, такие как ODBC Driver 17 for SQL Server или выше. Новые версии драйверов лучше обрабатывают метаданные и реже вызывают ошибки несоответствия типов при записи.
Подведем итог нашему разбору. Ошибка IDENTITY_INSERT возникает из-за попытки 1С перезаписать значение автоинкрементного столбца. Для эффективного решения проблемы мы рекомендуем:
Выполнить() для прямого SQL-запроса UPDATE — это самый контролируемый способ.Выбор конкретного метода зависит от архитектуры вашего решения, но в большинстве случаев прямой SQL-запрос оказывается наиболее быстрым и простым в реализации вариантом.