0. Vortigaunt 23 16.05.18 00:15 Сейчас в теме

Особенности разделения объектной модели документа и базы данных в 1С 7.7. Забавный глюк

Когда занимаешься разработкой в среде 1С, редко задумываешься о том, что программным кодом ты работаешь с объектной моделью базы данных, а не с самой базой данных. И что это вообще разные вещи. Ты создаешь объекты: документы и справочники, записываешь их - и в базе данных появляются соответствующие записи. Это настолько привычно, что когда сталкиваешься с нетипичным поведением платформы, первым делом думаешь: надо протестировать базу, она битая. В этой статье я хочу разобрать одну интересную ситуацию, которая как раз демонстрирует такое поведение. Описанная ниже ситуация воспроизводится как в файловом, так и в клиент-серверном (SQL) варианте. Тестировалось на версии платформы 1с77 релиз 027.

Перейти к публикации

Комментарии
Избранное Подписка Сортировка: Древо
1. vcv 84 16.05.18 22:21 Сейчас в теме
Вполне логично. После НайтиДокумент вы имеете в памяти закешированные данные объекта и работаете с ними. И тут бабах, меняете состояние объекта в базе. Разом получаете конфликт - в базе одна информация, а в памяти другая. По хорошему в подобных случаях языку бы выдавать ошибку блокировки объекта...
6. CheBurator 3537 17.05.18 15:14 Сейчас в теме
(1)
После НайтиДокумент вы имеете в памяти закешированные данные объекта и работаете с ними. И тут бабах, меняете состояние объекта в базе. Разом получаете конфликт - в базе одна информация, а в памяти другая. По хорошему в подобных случаях языку бы выдавать ошибку блокировки объекта...

- как ты себе это представляешь?
прочитал я объект - меняю его реквизиты
в это время в другой сессии ктото прочитал этот обьект, поменял его напрочь и успел записать. а я еще все меняю и потом записываю - и все изменения второй сесии - похерятся нафиг. Это штатное поведение клюшек. Чтение - всегда грязное.
а в 8-ке - да, при попытке записи измененного объекта - выдается ошибка.
18. vcv 84 18.05.18 10:20 Сейчас в теме
(6)
как ты себе это представляешь?

Чисто теоретически, в 1С8 сделано правильно. По крайней мере при интерактивной работе. Чтение грязное. Но первая же попытка внести изменения в объект приводит к его блокировке. А штатное поведение клюшек работает на уровне прошлого века. Никакого защищенного/безопасного программирования и прочих серебрянных пуль 21 века :)
8. Vortigaunt 23 17.05.18 17:08 Сейчас в теме
Я не говорил, что речь идет о типовой ТиС. Но как ни странно именно в ТиС я такую ситуацию и наблюдал. ТиС тоже есть разных версий и ее зачастую дописывают. А есть еще ТиС для Украины с которой я в основном и работаю, и она сильно отличается от российской. Я утверждаю лишь то, что если в коде модуля формы не значится ПриЗаписиПерепроводить(1), то есть возможность записать проведенный документ без его перепроведения. Какие это вызывает проблемы - указано в статье.
3. CheBurator 3537 17.05.18 14:15 Сейчас в теме
Статья изобилует ошибками.
на типовой ТиС
1.
Оператор добавил в проведенную расходную накладную товар, которого нет на остатке и попытался перепровести её. Документ сначала записался, но обработка проведения завершилась с ошибкой: недостаточно товара на складе и движения документа не изменились. Таким образом в документе мы видим большее количество товара, сумма документа увеличилась, мы даже можем напечатать его в таком состоянии.

- все неверно.
Речь в данном примере идет ОБ ИНТЕРАКТИВНОМ записи и проведении документа (работает оператор). Здесь:
Документ записался. Но не провелся. Транзакция откатилась. ДОКУМЕНТ В БАЗУ НЕ ЗАПИСАН. ИЗМЕНЕН, НО НЕ ЗАПИСАН - об этом свидетельствует заначок незаписанной модификации {*} в заголовке формы документа. таким образом мы в документе видим бОльшее количество товара, но оно в базу не записано. Мы можем напечатать что угодно - но это не значит что напечатанное соответствует реальности. При печати из МОДИФИЦИРОВАННОЙ ФОРмы (если мне не изменяет память) через ВПФ или встроенную печформу - надо анализировать признак модифицированности формы (при передаче в процедуру печати группового контекста формы) или печатать АКТУАЛЬНЫЙ ДОКУМЕНТ по передаваемой ссылке на документ. и цифры в печформе будут соответсоввать реальности. или тому как вы хотите.

таким образом - данный абзац автора демонстрирует всего лишь а) возможно, невнимательность) и б) незнание особенности программной реализации кода конфигурации (полностью типовой ТиС у меня под рукой нет, но насколько мне помнится там при попытке печати модифицированного но не записанного документа выдавалось предупреждение; наставиать не буду, факт лишь то - что при незаписанных данных обеспечить печать ПРАВИЛЬНЫХ данных - это всего лишь надо знать и уметь использовать предоставляемые платформой возможности).
4. CheBurator 3537 17.05.18 15:09 Сейчас в теме
Лечится такая ситуация очень просто. После шага 3 надо было Записать() документ,

- более того, исходный код обработки НЕВЕРЕН. так как не обеспечивает логическую непротиворечивость данных. От реквизита Документ_1 зависит проведение документа. Вы меняете этот реквизит У ПРОВЕДЕННОГО ДОКУМЕНТА, но при этом мало того что не записываете - ДАЖЕ ЕСЛИ И ЗАПИСАЛИ - его надо еще дополнительно ОБЯЗАТЕЛЬНО ПРОВЕСТИ.
то есть в вашем случае код обработки должен выглядеть так:

Процедура Выполнить()
док2 = СоздатьОбъект("Документ.Документ_2");
док2.НайтиДокумент(ВыбДокумент_2);
док2.Документ_1 = ВыбДокумент_1;
док2.Записать(); //д.б.обязательно в соответстии с логикой использования документа_2
док2.Провести(); //д.б.обязательно в соответстии с логикой использования документа_2
//А ЗДЕСЬ ДАЛЬШЕ ДЕЛАЙТЕ ЧТО ХОТИТЕ С ДОКУМЕНТОМ_2
Сообщить("1. Перед распроведением Документ 1: "+док2.Документ_1);
док2.СделатьНепроведенным();
Сообщить("2. После распроведения Документ 1: "+док2.Документ_1);
док2.Записать();
Сообщить("3. После записи Документ 1: "+док2.Документ_1);
док2.Провести();
Сообщить("4. После проведения Документ 1: "+док2.Документ_1);
КонецПроцедуры
19. vcv 84 18.05.18 10:40 Сейчас в теме
(4) Еще попытки-исключение не хватает и транзакции. Вдруг документ запишется, а при проведении возникнет ошибка.
5. CheBurator 3537 17.05.18 15:10 Сейчас в теме
с такой поправкой - все отрабатывает как надо - в базе появляется документ2 в котором в шапке заполнен документ1
7. CheBurator 3537 17.05.18 15:24 Сейчас в теме
Глючок, конечно, занятный
Суть его в том, что по факту записи документа в базу после распроведения не происходит - почему так ХЗ, надо поинтереосваться у знающих людей.

а так - взять на заметку что после для избежания такого выверта надо либо как отметил выше автор(с моим уточнением) перед распроведением записать+провести документ (то есть программировать как надо по логике - а не тяп-ляп на скорую руку), либо актуализировать документ

Сообщить("1. Перед распроведением Документ 1: "+док2.Документ_1);
док2.СделатьНепроведенным();
док2.НайтиДокумент(ВыбДокумент_2);
Сообщить("2. После распроведения Документ 1: "+док2.Документ_1);
9. Vortigaunt 23 17.05.18 17:16 Сейчас в теме
(7)
(то есть программировать как надо по логике - а не тяп-ляп на скорую руку)

В статье приведен код, очищенный и подготовленный, так сказать. Ясное дело, что по нему сразу видно ошибку. Реальный же код, который выдал эту ошибку был более громоздкий и запутанный. Как минимум распроведение документа скрывалось в отдельной процедуре. Процедура эта была добавлена в процессе доработки: нужно было менять время существующих документов.
И если эту особенность не знать или не думать в эту сторону, то с отладчиком можно рехнуться))
10. CheBurator 3537 17.05.18 19:11 Сейчас в теме
(9) насчет рехнутьяс - это точно.

а то что распроведение было "спрятано" - ну так это и есть - смастерили костыль на скорую руку, без понимания логики ранее написанного запутанного и громоздкого кода.. ;-)
11. CheBurator 3537 17.05.18 20:41 Сейчас в теме
Пообщался со спецами.
Условно (связано с программисткими виртуальными функциями и пр.шнягой):
Модифицировать реквизиты нужно ПОСЛЕ ПОСЛЕДНЕГО ЧТЕНИЯ объекта (или записывать модифицированное перед чтением).
если ты смодифицировал реквизиты, а потом явно или неявно перечитал в память объект - то в зависимости от кучи условий (так как разные функции чтения читают разный набор полей) может быть либо ожидаемый, либо неожидаемый результат.

Причем в зависимости от методов чтения объекта - могут получаться разные "версии" полей одного и того же объекта.

И типа в этом случае глюка:
НайтиДокумент дал объект с набором полей "версия1"
СделатьНеПоведенным для этого же объекта породил набор полей "версия2".
причем в общем случае перечень полей (а это СВОЙСТВА объекта) в разных версиях может быть разным.

и записать() использует вторую версию полей (с незаполненной шапкой Документ_1)
а провести() использует первую версию полей с заполненным шапкой Документ1

То есть вот так очень условно и очень мутно - ибо программеры человеческим языком для простых девЕлоперов пояснить не могут. ;-)
Vortigaunt; +1 Ответить
14. Vortigaunt 23 17.05.18 23:38 Сейчас в теме
(11) Спасибо за проявленный интерес и предоставленный отзыв специалистов по внутреннему миру платформы.
Я правильно понял, что метод СделатьНеПроведенным() создает новый экземпляр объекта куда перечитывает из базы поля документа, которые мы в свою очередь еще не обновили? Только выходит, что ссылка на этот экземпляр не возвращается в нашу переменную и поэтому Метод Записать() не обновляет данные в базе. Ведь мы вызываем метод Записать() из первого объекта, а он уже "не связан" с данными?
Просто я себе никак не могу представить одновременное существование разных состояний одного экземпляра объекта.
15. CheBurator 3537 18.05.18 00:34 Сейчас в теме
(14) не, немножко не так (надо учитывать что мой пересказ - это некая мутная сказка про то что мне пытались рассказать умные люди). как-то так:
объект (1Сный док2) остается тот же самый
но
НайтиДокумент - реализуется некоей функцией чтения, которая читает из базы в поля объекта соответствующие поля базы
СделатьНепроведенным - реализуется ДРУГОЙ функцией чтения, которая тоже читает в поля объекта из базы, но не обязательно тот же самый набор полей, могут читаться ограниченный набор полей - и под этот набор создается новая область в памяти (какие-то виртуальные программистские функции я хз что это за хрень)

условно:
в памяти имеем после этих двух методов
Объект (он один)
|___>(набор полей#1) - использовался при проведении
|___>(набор полей#2) - использовался при записи

Мое объяснение - это как пересказ тупым то что он услышал от умных, то есть полный бред... то есть весьма условное.
Излагать связно, коротко и понятно - должен спец. но они страдают невозможностью доступно изложить неспециалисту узкоспециализированные вещи - поэтому весьма условные и мутные аналогии

Итог прост
1. сначала прочитай все что нужно - потом модифицируй и записывай
2. смодифицировал но не записал и перечитываешь тот же самый объект - а) смысл этого непонятен б) могут быть глюки если перечитка внутри 1С реализуется (в зависимости от разных услвоий) другим вариантом функции чтения..

бред короче несу
16. CheBurator 3537 18.05.18 00:41 Сейчас в теме
(14)
Просто я себе никак не могу представить одновременное существование разных состояний одного экземпляра объекта.

я тоже, если рассматривать обьект как простую линейную структуру. Но там все сложнее - объект - это куча связанных структур/списков... или например как реквизиты объекта - это ведь даже в СП они называются СВОЙСТВА и может быть одновременно один и тот же объект полученный разными МЕТОДАМИ (НайтиПоРеквизиту,ПолучитьЭлемент() итд. а разные МЕТОДЫ имеют свой набор СВОЙСТВ (прочитанных полей-реквизитов)

короче не надо меня слушать.. бредятина...
спецы ща ржут в голос наблюдая как один слепой пытается объяснить другому слепому что такое "слон"
17. hogik 426 18.05.18 03:36 Сейчас в теме
(14)
Просто я себе никак не могу представить одновременное существование разных состояний одного экземпляра объекта.

Дмитрий.
Ваше утверждение
При этом все отметки о модифицированности документа снимаются
можно принять как объяснение происходящих процессов в движке 1С-а. Только снимается не "отметки ... документа", а "отметки" об модификации "свойств" (полей документа) в оперативной памяти. И не заморачиваться - каким образом реализованы в движке эти самые "отметки".
20. vcv 84 18.05.18 10:47 Сейчас в теме
(14) Скорее всего СделатьНеПроведённым() никакого нового экземпляра объекта не создаёт. Лезет в базу данных, помечает документ как непроведённый, удаляет его движения, пересчитывает итоги. А объект в памяти остаётся как есть. Со всеми изменениями. Больше хитростей в методе Провести. Он работает с объектом в памяти. Но с инициализацией объекта там есть конкретные косяки.
23. Vortigaunt 23 18.05.18 14:17 Сейчас в теме
(20) Такое объяснение не дает ответ на вопрос, почему метод Записать() не возвращает наши измененные значения реквизитов из объекта в памяти в запись в базе данных.
24. vcv 84 18.05.18 15:33 Сейчас в теме
(23)
Такое объяснение не дает ответ на вопрос...


Боюсь, что ответ на вопрос, получить невозможно в принципе. Ответить могут только разработчики 1С 7.7, которые уже давно забыли что это такое. Мы можем только гадать. Предположите, что 1С пытается оптимизировать обращение с базой данных и не писать те реквизиты документа, которые не изменялись.
Проверить это предположение вы легко можете сами, если есть под рукой sql-база. Ловите профайлером запрос на изменение данных и смотрите, все ли реквизиты в него попали. Возьмётесь?

P.S.
Ну и зачем вам этот ответ? Вы подметили тонкое место, на котором могут огрести проблем изготовители бардачного кода. Отлично. Но при правильном кодировании проблемы возникнуть не должно
НачатьТранзакцию
Попытка
СделатьНепроведенным
<изменения в документе>
Записать
Провести
ЗафиксироватьТранзакцию
Исключение
ОтменитьТранзакцию
КонецПопытки
25. hogik 426 18.05.18 23:55 Сейчас в теме
(20)
(23)
Дмитрий, Владислав.
Я посмотрел трассировку действий движка 1С-а при выполнении СделатьНеПроведённым() с помощью https://infostart.ru/public/15211/
Да. Чтения самого документа там нету. Есть обращения (чтение/запись/удаление) к журналу документов, ссылкам документов, регистрам. Но, для осознания происходящего в тесте из данной публикации надо иметь в виду, что для работы с полями одного и того же документа используется несколько "массивов" (списков) в оперативной памяти 1С-а. И значения полей "перемещаются" и предоставляются разным алгоритмам из разных "массивов". Например (очень примерно!!!), присвоение "док2.Документ_1 = ВыбДокумент_1" помещает значение в "массив" переменных текущей функции. Алгоритм присвоения значения переменной "понимает", что речь идёт о поле документа. И помещает это же значений ещё и в другой "массив" для последующего выполнения Записать() или Провести(). И уже этот "массив" использует движок (что я уже и вижу в трассировке) для выдачи команд СУБД по переносу значений полей в буфер ввода/вывода СУБД. Этот второй "массив" очищается, меняет состав строк, пересоздаётся и т.д. в зависимости от используемых типов/видов/методов объекта 1С-а по работе с базой данных. Логично предположить, что метод СделатьНеПроведённым() очищает этот второй "массив".
21. vcv 84 18.05.18 10:55 Сейчас в теме
(14)
Просто я себе никак не могу представить одновременное существование разных состояний одного экземпляра объекта.

Ты не поверишь :)

Док1 = СоздатьОбъект("Документ");
Док2 = СоздатьОбъект("Документ");
Док1.НайтиДокумент(Док);
Док2.НайтиДокумент(Док);

Это, конечно, не "один экземпляр объекта", но кто его знает, как всё устроено внутри. При вызове СделатьНепроведенным явно не нужна вся информация о документе, поэтому незачем тащить документ в память. И незачем пользоваться объектом в памяти, потому что непроведенным делается документ в базе, а не в памяти.
А то, что 1С разрешает такой конфликт заполучить на ровном месте, это уже другой вопрос.
22. Vortigaunt 23 18.05.18 14:13 Сейчас в теме
(21) Вы привели пример создания 2-х экземпляров объекта. Мне кажется если у одного из них попытаться либо Записать(), либо Провести() либо СделатьНеПроведенным() сработает исключение "Объект заблокирован". Но не уверен точно. Надо проверить.
12. CheBurator 3537 17.05.18 20:45 Сейчас в теме
СделатьНеПоведенным - вызывает чтение объекта из базы... и порождает вторую "версию" полей объекта (набор полей не обяхательно совпадает с набором полей из первой версии). и одновременно у объекта есть поле "Документ1" которое заполнено и второе, которое назаполнено.
И как себя теперь поведет система при дальнейшем использовании объекта - мы уже ХЗ.

поэтому программить надо аккуратно. Именно то о чем я говорил выше. Расставлляя костыли - смотреть чтобы костыль был поставлен в нужное место.
а в исходном варианте - как написал сам автор - костылей было столько, что еще один костыль среди этого леса поставили не там где надо...
13. CheBurator 3537 17.05.18 20:50 Сейчас в теме
В итоге все что нужно (как написал выше) - модифицировать ПОСЛЕ псоледнего чтения, т.е. после распроведения.

такой код - ведет себя ожидаемо и предсказуемо

//*******************************************
Процедура Выполнить()
док2 = СоздатьОбъект("Документ.Документ_2");
док2.НайтиДокумент(ВыбДокумент_2);
Сообщить("1. Перед распроведением Документ 1: "+док2.Документ_1);
док2.СделатьНепроведенным();
Сообщить("2. После распроведения Документ 1: "+док2.Документ_1);
док2.Документ_1 = ВыбДокумент_1;
Сообщить("3. Перед записи Документ 1: "+док2.Документ_1);
док2.Записать();
Сообщить("3. После записи Документ 1: "+док2.Документ_1);
док2.Провести();
Сообщить("4. После проведения Документ 1: "+док2.Документ_1);
КонецПроцедуры
26. Vortigaunt 23 21.05.18 22:21 Сейчас в теме
(13) Кстати да. Я провел небольшое тестирование. При любом присваивании в реквизиты документа после метода СделатьНепроведенным() (причем неважно в какой реквизит) объект опять оживает, и метод Записать() записывает данные в базу.
27. hogik 426 22.05.18 16:22 Сейчас в теме
(26)
Дмитрий.
Я сделал сравнение двух трассировок для:
1)
Док.Поле1="11111";
Док.СделатьНепроведенным(); 
Док.Записать();
Док.Провести();

2)
Док.Поле1="11111";
Док.СделатьНепроведенным(); 
Док.Поле3="33333";
Док.Записать();
Док.Провести();

В первом случае в таблицы БД документа ничего не пишется. Обновляется только журнал документов. Во втором случае производится обновление только таблица шапки документа (Поле1 и Поле3 - реквизиты шапки). Аналогичные действия для табличной части документа вызывают обновление только табличной части. Т.е. в кишках 1С-а существует два "флага" (признака) модифицированности для шапки и для табличной части.
Т.е. надо всегда делать как написано в (24) сообщении.
Только поменять порядок операторов:
НачатьТранзакцию 
Попытка 
Vortigaunt; +1 Ответить
Оставьте свое сообщение
Новые вопросы с вознаграждением
Автор темы объявил вознаграждение за найденный ответ, его получит тот, кто первый поможет автору.

Вакансии

Программист 1С
Нижний Новгород
зарплата до 120 000 руб.
Полный день

Бизнес-аналитик 1С ERP
Пермь
зарплата от 80 000 руб.
Полный день

Программист 1С УТ 11
Омск
зарплата от 60 000 руб.
Полный день

Бизнес-аналитик 1С УТ 11
Омск
зарплата от 60 000 руб.
Полный день

Программист 1С
Воронеж
Полный день