AXForum  
Вернуться   AXForum > Microsoft Dynamics AX > DAX: Программирование
NAV
Забыли пароль?
Зарегистрироваться Правила Справка Пользователи Сообщения за день Поиск

 
 
Опции темы Поиск в этой теме Опции просмотра
Старый 07.01.2026, 22:08   #1  
Lankey is offline
Lankey
Участник
 
195 / 30 (2) +++
Регистрация: 19.05.2020
Простой код, но UpdateConflict, почему?
Мне попался код, который очень простой и не кажется ошибочным на первый взгляд, но он выдавал updateConflict. Я хочу лучше понять механику процесса, буду благодарна объяснению
X++:
ttsbegin
while select forupdate purchline
where purchid = 'MyCurrentPONumber'
{
   ...
    if (some condition)
    {    
        ttsbegin;
        purchline.FieldA = 'aa';
        purchline.update();
        ttscommit;
    }
}
ttscommit
Здесь проблема была в том, что purchline.update(); давал updateConflict, тк purchline.update() вызывает стандартную фцнкциональность versioning которая делает update на purchline , c тем же PurchID в VersioningPurchaseOrder.archivePurchLine()
Я также понимаю, что этот update(в versioning) меняет recversion и ,соответственно, он уже не тот на линиях, что был выбран в "while select forupdate purchline"

Но я тогда не понимаю,
1) как можно в одной транйакции(то есть, либо все проапдейчены линии, либо нет) сделать апдейт в всех линий какого-нибудь одного заказа за закупку? Это же типичный сценарий, но versioning может его сломать...
2) есть ли в коде выше какие-то очевидные ошибки (если бы вы не знали, что там проблема есть). вроде, классика делать
ttsbegin
while select forupdate purchline
where purchid = 'MyCurrentPONumber'
{
purchline.update();
}
ttscommit
3)Ну, и вообще, это же одна транзакция тут (внешняя, что до while select), то есть lock должен защищать от одновременного апдейста разными транцакциями. В рамках одной все должно быть ок, разве нет?

Последний раз редактировалось Lankey; 07.01.2026 в 22:19.
Старый 08.01.2026, 11:25   #2  
sukhanchik is offline
sukhanchik
Administrator
Аватар для sukhanchik
MCBMSS
Злыдни
Лучший по профессии 2015
Лучший по профессии AXAWARD 2013
Лучший по профессии 2011
Лучший по профессии 2009
 
3,348 / 3564 (125) ++++++++++
Регистрация: 13.06.2004
Адрес: Москва
Ну давайте разберём по порядку.
1. Технологию (платформу, ядро) создают одни, а затем логику (приложение, код на Х++) пишут другие. Т.е. те люди, которые предполагали, что будет цикл по перебору строк в одной транзакции - не предполагали, что другие будут писать логику на purchline.update(). А те, кто писали логику на purchline.update() ... видимо вообще не думали - делали, как им проще закодить.
2. В одной транзакции сделать апдейт всех строк можно. Вопрос лишь ценой каких усилий и каких ограничений Вы же всегда можете написать код вида
ttsbgin;
purchTable = PurchTable::find('MyCurrentPONumber', true);
purchTable.FieldA = 'aa';
purchTable.update();
purchTable = PurchTable::find('MyCurrentPONumber', true);
purchTable.FieldB = 'bb';
purchTable.update();
ttsbegin;

Ну и для PurchLine - аналогично. Другое дело, что это создаст нагрузку на БД при массовом обновлении и могут появиться блокировки опять-таки при обработке большого количества данных, но... сделать же можно
3. В отдельных случаях (когда логика на update небольшая) - можно вынести в код эту логику и написать .doupdate(). Да, это нарушит принцип "не надо дублировать код", но... работать будет. А логику можно вынести в отдельный метод и его просто вызывать в разных ситуациях и тогда проблемы дублирования кода уйдут.
4. В рамках одной транзакции всё должно быть ок, но при условии, что каждый следующий update() получает актуальный курсор. Т.е. если мы выбрали purchLine, затем изменили у него FieldA и сделали update(), то перед обновлением FieldB - нужно, чтобы в переменной purchLine содержался бы курсор с уже обновленным FieldA, а не тот, который был бы на момент начала транзакции (кстати, именно для этого на таблице есть метод reread()).
5. И еще надо учесть такое понятие, как пессимистическая и оптимистическая блокировки. В оптимистической - да, именно так, как Вы описали, и так у многих табличек работает по умолчанию. Однако, если курсор второй раз выбран под пессимистическую блокировку - то вторая выборка на обновление будет "висеть" до завершения транзакции по первому update(). Пессимистическая блокировка - это когда в коде пишется select pessimisticlock purchLine. Либо select forupdate purchLine, а на purchLine установлено свойство optimisticConcurrency = No (такие конструкции встречаются в коде у таблички InventSum). В Вашем случае конечно такого нет, но в общем случае (не с purchLine) момент с блокировками нужно учитывать
__________________
Возможно сделать все. Вопрос времени
Старый 08.01.2026, 22:27   #3  
Lankey is offline
Lankey
Участник
 
195 / 30 (2) +++
Регистрация: 19.05.2020
Спасибо, но, мне кажется, я плохо объяснила
1) Стандарт вызывает в purchLine.update() апдейт др строк той же закупки в VersioningPurchaseOrder.archivePurchLine().
Поэтому, после purchLine.update() первой строки, когда мы переходим в цикле ко второй той же закупки, то она уже проапдейчена и буфер из while select forupdate purchline
where purchid = 'MyCurrentPONumber' имеет "старый" recVersion
Тк закупки - одна из основных функциональностей, а уж апдейт всех строк закупок в транзакции - типичный сценарий, я не понимаю, как может быть , что описанный код с простым while select forupdate purchline не работает.
Как бы вы написали апдейт всех строк одной закупки в одной транзакции , если бы вам понадобилось?

2) while select forupdate purchline должно накладывать пессимистичную блокировку , но это, имхо, тут не важно, тк все происходит в контексте одной и той же транзакции. Разве нет?

Последний раз редактировалось Lankey; 08.01.2026 в 22:36.
Старый 10.01.2026, 15:13   #4  
Logger is offline
Logger
Участник
Лучший по профессии 2015
Лучший по профессии 2014
 
4,017 / 3301 (119) ++++++++++
Регистрация: 12.10.2004
Адрес: Москва
Записей в блоге: 2
Соглашусь с sukhanchik

попробуйте перед
X++:
if (some condition)
поставить
X++:
purchline.reread();
тогда точно будет актуальная версия.
Еще я бы куда-нибудь, например в Set запоминал RecId обработанной записи и в цикле дополнительно проверял что запись не содержится в списке обработанных, а если содержится то пропускал бы. В подобных вашему примеру есть вероятность что запись может повторно в выборку цикла попасть и будет бесконечный цикл.

Такие случаи (с зацикливанием) обсуждались на форуме ранее.
Старый 11.01.2026, 12:55   #5  
Lankey is offline
Lankey
Участник
 
195 / 30 (2) +++
Регистрация: 19.05.2020
Цитата:
Сообщение от Logger Посмотреть сообщение
Соглашусь с sukhanchik

попробуйте перед
X++:
if (some condition)
поставить
X++:
purchline.reread();
тогда точно будет актуальная версия.
Еще я бы куда-нибудь, например в Set запоминал RecId обработанной записи и в цикле дополнительно проверял что запись не содержится в списке обработанных, а если содержится то пропускал бы. В подобных вашему примеру есть вероятность что запись может повторно в выборку цикла попасть и будет бесконечный цикл.

Такие случаи (с зацикливанием) обсуждались на форуме ранее.
Спасибо, но мне кажется, Вы мои вопросы даже не прочитали
Старый 12.01.2026, 12:13   #6  
Владимир Максимов is offline
Владимир Максимов
Участник
КОРУС Консалтинг
 
1,732 / 1220 (44) ++++++++
Регистрация: 13.01.2004
Записей в блоге: 3
Вопрос уже поднимался

отладка AX2012
PurchLine update conflict ??

Я для себя нашел такой "костыль"

X++:
    ttsbegin;
    while select forUpdate PurchLine where ...
    {
        if (purchLine.IsModified)
        {
            purchLine.reread();
        }
        
        purchLine.FieldXXX = ...;
        purchLine.update();
    }
    ttsCommit;
Т.е. при обновлении первой записи PurchLine у всех остальных записей может быть снят признак IsModified. Но если в буфере этот признак стоит, значит мы видим "старое" значение. И требуется буфер обновить

Если интересует чисто технический момент "как такое может быть", то дело в том, что "за раз" Axapta забирает несколько записей. По умолчанию, если не ошибаюсь, по 2 записи. По этой причине, собственно, конфликт и возникает. Цикл взял вторую запись из буфера, но она уже изменена при обработке первой записи
__________________
- Может, я как-то неправильно живу?!
- Отчего же? Правильно. Только зря...
За это сообщение автора поблагодарили: S.Kuskov (10), Lankey (1).
Старый 12.01.2026, 17:18   #7  
Lankey is offline
Lankey
Участник
 
195 / 30 (2) +++
Регистрация: 19.05.2020
Спасибо, Владимир!
Вторая ссылка - это как раз мой случай.

Потрясающе, что есть какой-то стандарт разработки(как писать апдейт) . А микрософт его, получается, "хакнул". Причём не на какой-то третьесортной таблице, а одной из основных. Как аксапта работает без постоянных конфликтов на этой таблице, если там такая бомба? Стандарт вещде reread делает?
Плюс , потрясающе, что в d365 все ещё та же проблема, что, судя по ссылкам, была в AX2012

Последний раз редактировалось Lankey; 12.01.2026 в 17:23.
Старый 13.01.2026, 19:01   #8  
SRF is offline
SRF
Участник
MCBMSS
Axapta Retail User
 
377 / 562 (19) +++++++
Регистрация: 08.08.2007
Записей в блоге: 1
Посмотрите, как работает отмена покупки, метод PurchCancel\updatePurchTable()

X++:
        if (!purchTable.selectForUpdate())
        {
            purchTable = PurchTable::findRecId(purchTable.RecId, true);
        }

        if (purchTable.ChangeRequestRequired && purchTable.DocumentState >= VersioningDocumentState::Approved)
        {
            VersioningPurchaseOrder::newPurchaseOrder(purchTable).createChangeRequest();
        }
        else if (!VersioningPurchaseOrder::newPurchaseOrder(purchTable).isLastVersionArchived() && purchTable.DocumentState == VersioningDocumentState::Confirmed)
        {
            // Force archiving to avoid it during line cancellation as that would lead to update conflicts.
            purchTable.update();
        }
Смысл я думаю тут простой - при использовании версионности (purchTable.ChangeRequestRequired) - создавайте запрос на изменение (ведь для чего то вы включили его, получается используется коробочный механизм, а для него есть специальный флоу и есть api которое можно\нужно использовать)

Без версионности - после того как вы покупку подтвердили - в коробке фиксируются какие-то параметры (не только в самой строке покупки) и если вы хотите изменить уже подтвержденную покупку - то вам тоже надо сделать какой то "сброс" и затем подтвердить эту покупку повторно.

В целом если использовать активацию обновления покупки как например сделано в purchCancel\updatePurchTable(), то дальше спокойно можно обновлять строки в цикле по update, без дополнительных reread
__________________
Sergey Nefedov
 

Похожие темы
Тема Автор Раздел Ответов Посл. сообщение
DAX2009 почему setprefix не работает в циклах oleggy DAX: Программирование 1 03.05.2020 19:37
FormSegmentedEntryControl и FormSegment control. Почему нет mandatory свойства ? Logger DAX: Программирование 1 06.11.2018 19:29
А как в ax7 код метода получить? mazzy DAX: Программирование 13 17.10.2017 23:44
Принадлежит ли код определенной номерной серии? Poleax DAX: Программирование 7 23.09.2010 13:06
Channel9: Peter Villadsen and Gustavo Plancarte: X++ to MSIL Blog bot DAX Blogs 30 24.08.2010 17:11

Ваши права в разделе
Вы не можете создавать новые темы
Вы не можете отвечать в темах
Вы не можете прикреплять вложения
Вы не можете редактировать свои сообщения

BB коды Вкл.
Смайлы Вкл.
[IMG] код Вкл.
HTML код Выкл.
Быстрый переход

Рейтинг@Mail.ru
Часовой пояс GMT +3, время: 19:53.