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

 
 
Опции темы Поиск в этой теме Опции просмотра
Старый 27.08.2022, 12:53   #1  
Damn is offline
Damn
Участник
 
436 / 154 (6) ++++++
Регистрация: 28.05.2003
Адрес: в глуши
D365, SysDatabaseLog, поле NewData
В ах2009 и ах2012 детали каждой записи лога хранятся в контейнере в поле SysDatabaseLog.Data.
Например, мы настроили логирование изменений в таблице CustTable, обновили в записи таблицы CustTable какое-то поле.
Тогда в SysDatabaseLog появится запись, у которой в поле Data будут храниться значения измененных полей. Помимо изменённого нами поля там будут ещё поля типа modifiedDateTime, modifiedBy, recVersion.
В D365 в SysDatabaseLog для хранения деталей лога используется новое поле NewData. Это строковое поле типа Memo. Заполняется оно с помощью триггеров на уровне БД. У этих триггеров вскрылись следующие неприятные моменты:
1. Если мы хотим логировать update только отдельных полей в CustTable и думаем что в SysDatabaseLog будут появляться записи только при изменении этих полей, то мы ошибаемся. Записи будут появляться при изменении любого поля.
2. В поле NewData сохраняется значение всех полей, на которые мы настроили ведение логов. Даже если эти поля не изменялись - всё равно туда записывается значение этих полей. Два раза, типа старое и новое. Что ожидаемо ведёт к чрезмерному росту таблицы SysDatabaseLog .
3. Значения полей типа utcdatetime сохраняются в newdata без секунд. Может конечно у нас каких-то настроек не хватает, чтоб секунды сохранялись.

Вот такая подлянка от микрософта.
__________________
Дмитрий
За это сообщение автора поблагодарили: trud (2), sukhanchik (4).
Старый 27.08.2022, 15:19   #2  
Raven Melancholic is offline
Raven Melancholic
Участник
Аватар для Raven Melancholic
Самостоятельные клиенты AX
Лучший по профессии 2015
 
2,164 / 1296 (48) ++++++++
Регистрация: 21.03.2005
Адрес: Москва-Петушки
Цитата:
Сообщение от Damn Посмотреть сообщение
...
1. Если мы хотим логировать update только отдельных полей в CustTable и думаем что в SysDatabaseLog будут появляться записи только при изменении этих полей, то мы ошибаемся. Записи будут появляться при изменении любого поля.
...

Вот такая подлянка от микрософта.
К сожалению, и в более ранних версиях не всегда записи лога создаются только если изменились поля, настроенные в отслеживании.
Есть какие-то случаи, когда запись в журнале базы данных создается даже тогда изменились поля, которые не настроены для отслеживания.
До Ax3.0 включительно настройка по полям работала. Начиная с обновления Ax3.0, в котором появилось поле RecVersion этот механизм перестал учитывать настройки полей. Возможно это из-за RecVersion, возможно что-то там переделали, но факт на лицо.

Причем, игнорирование настроек изменений по полям отслеживается не на всех таблицах (какая закономерность тут работает так и не понял). Мы, в свое время, наткнулись на это еще в DAX4 при настройке журнала базы данных по InventTableModule.
За это сообщение автора поблагодарили: sukhanchik (4).
Старый 27.08.2022, 19:25   #3  
imir is offline
imir
Участник
 
159 / 161 (6) ++++++
Регистрация: 28.05.2010
Если посмотреть на код триггера

X++:
AND ( UPDATE(KnownAs) OR UPDATE(Name) OR UPDATE(NV_ParentCompany) )
то понятно почему так получается.

X++:
IF (@spLogType = 3 AND ( UPDATE(KnownAs) OR UPDATE(Name) OR UPDATE(NV_ParentCompany) ) ) --Update 
    BEGIN 
        INSERT INTO SYSDATABASELOG (NEWDATA, LOGRECID, LOGTYPE, TABLE_, DESCRIPTION, USERNAME, CREATEDBY, CREATEDTRANSACTIONID, DATAAREAID, PARTITION, SEQUENCENUMBER)   
            SELECT '03::' +       + 'KnownAs' + ', ÿþ ' +  I.KNOWNAS + ' ÿþ ' + D.KNOWNAS + ' ÿþ ' 
      + 'Name' + ', ÿþ ' +  I.NAME + ' ÿþ ' + D.NAME + ' ÿþ ' 
      + 'NV_ParentCompany' + ', ÿþ ' +  CONVERT(nvarchar(max), I.NV_PARENTCOMPANY) + ' ÿþ ' + CONVERT(nvarchar(max), D.NV_PARENTCOMPANY) + ' ÿþ ' 
      + 'CreatedBy' + ', ÿþ ' +  I.CREATEDBY + ' ÿþ ' + D.CREATEDBY + ' ÿþ ' 
      + 'CreatedDateTime' + ', ÿþ ' +  CONVERT(nvarchar(max), I.CREATEDDATETIME) + ' ÿþ ' + CONVERT(nvarchar(max), D.CREATEDDATETIME) + ' ÿþ ' 
      + 'ModifiedBy' + ', ÿþ ' +  I.MODIFIEDBY + ' ÿþ ' + D.MODIFIEDBY + ' ÿþ ' 
      + 'ModifiedDateTime' + ', ÿþ ' +  CONVERT(nvarchar(max), I.MODIFIEDDATETIME) + ' ÿþ ' + CONVERT(nvarchar(max), D.MODIFIEDDATETIME) + ' ÿþ ' 
      + 'Partition' + ', ÿþ ' +  CONVERT(nvarchar(max), I.PARTITION) + ' ÿþ ' + CONVERT(nvarchar(max), D.PARTITION) + ' ÿþ ' 
      + 'RecId' + ', ÿþ ' +  CONVERT(nvarchar(max), I.RECID) + ' ÿþ ' + CONVERT(nvarchar(max), D.RECID) + ' ÿþ ' 
      + 'RecVersion' + ', ÿþ ' +  CONVERT(nvarchar(max), I.RECVERSION) + ' ÿþ ' + CONVERT(nvarchar(max), D.RECVERSION) + ' ÿþ ' 
     , 
    I.RECID, 2, 1268,I.PARTYNUMBER + ',' + I.NAME,@userId, @userId, 0, 'dat', I.PARTITION, @seqNo 
    FROM INSERTED I LEFT OUTER JOIN DELETED D ON I.RECID = D.RECID; 
 
 
    END;
Дописать сюда дополнительный case UPDATE([имя поля]) на каждое поле отдельно в теории должно бы сработаь, попробуйте открыть запрос в МС, только советую попробовать переписать триггер и предложить готовое решение.
Я в свое время словил в этом триггере ошибку, когда строка логирования обрезалась 1000-ю символами, предложил вариант с добавлением CONVERT(nvarchar(max) ..) - пофиксили оперативно.
За это сообщение автора поблагодарили: sukhanchik (8).
Старый 03.09.2022, 09:52   #4  
Damn is offline
Damn
Участник
 
436 / 154 (6) ++++++
Регистрация: 28.05.2003
Адрес: в глуши
Цитата:
Сообщение от Damn Посмотреть сообщение
1. Если мы хотим логировать update только отдельных полей в CustTable и думаем что в SysDatabaseLog будут появляться записи только при изменении этих полей, то мы ошибаемся. Записи будут появляться при изменении любого поля.
Однако. Я в качестве примера в тексте упомянул CustTable, но оказалось что с ней в пункте 1 нет проблем.
Более детальный анализ показал что ошибка из пункта 1 возникает только в тех таблицах, которые участвуют в наследовании (касается это и "родителей" и "детей"). Конкретно я столкнулся с такой проблемой в таблице DirPerson, которая отнаследована от DirPartyTable.
У таблицы DirPerson (фактически в БД у таблицы DIRPARTYTABLE) код в триггере выглядит так :
X++:
...
    IF (@spLogType = 3 ) --Update
    BEGIN
        INSERT INTO SYSDATABASELOG (NEWDATA, LOGRECID, LOGTYPE, TABLE_, DESCRIPTION, USERNAME, CREATEDBY, CREATEDTRANSACTIONID, DATAAREAID, PARTITION, SEQUENCENUMBER)  
            SELECT '03::' + 					 + 'BirthDay' + ', ÿþ ' +  CONVERT(nvarchar(max), I.BIRTHDAY) + ' ÿþ ' + CONVERT(nvarchar(max), D.BIRTHDAY) + ' ÿþ '
...
А например у таблицы CREDITCARDACCOUNTSETUP код в триггере выглядит так :
X++:
...
    IF (@spLogType = 3 AND ( UPDATE(ConnectorName) OR UPDATE(ConnectorProperties) OR UPDATE(IsTest) ) ) --Update
    BEGIN
        INSERT INTO SYSDATABASELOG (NEWDATA, LOGRECID, LOGTYPE, TABLE_, DESCRIPTION, USERNAME, CREATEDBY, CREATEDTRANSACTIONID, DATAAREAID, PARTITION, SEQUENCENUMBER)  
            SELECT '03::' + 					 + 'ConnectorName' + ', ÿþ ' +  I.CONNECTORNAME + ' ÿþ ' + D.CONNECTORNAME + ' ÿþ '
...
В одном случае записи в лог вставляются всегда, в другом - только при изменении указанных полей.
__________________
Дмитрий
За это сообщение автора поблагодарили: Raven Melancholic (5), S.Kuskov (5).
Старый 03.09.2022, 15:26   #5  
Raven Melancholic is offline
Raven Melancholic
Участник
Аватар для Raven Melancholic
Самостоятельные клиенты AX
Лучший по профессии 2015
 
2,164 / 1296 (48) ++++++++
Регистрация: 21.03.2005
Адрес: Москва-Петушки
И опять иерархия наследования таблиц, грустно.
Я считал, что те проблемы, которые наследование DAX2012 создало в ранее существующих механизмах, это просто проблемы роста. Но вот уже D365 идет по планете, а воз и ныне там.
Впечатление, что команда предложившая и реализовавшая наследование таблиц, появилась на проекте, что-то сделала и слиняла в другие проекты.
Старый 04.09.2022, 12:11   #6  
Damn is offline
Damn
Участник
 
436 / 154 (6) ++++++
Регистрация: 28.05.2003
Адрес: в глуши
Цитата:
Сообщение от Damn Посмотреть сообщение
3. Значения полей типа utcdatetime сохраняются в newdata без секунд. Может конечно у нас каких-то настроек не хватает, чтоб секунды сохранялись.
Причина не в настройках.
Для преобразования полей типа datetime в строку в триггере используется функция CONVERT(nvarchar(max), ...)
https://docs.microsoft.com/ru-Ru/sql...l-server-ver15
А в MS SQL Server формат преобразования datetime по умолчанию - mon dd yyyy hh:miAM (or PM) .
Если мы хотим секунды - необходимо в функцию CONVERT добавлять параметр style. Например, style 120 - это "yyyy-mm-dd hh:mi:ss (24h)".
__________________
Дмитрий
Старый 04.09.2022, 12:37   #7  
sukhanchik is offline
sukhanchik
Administrator
Аватар для sukhanchik
MCBMSS
Злыдни
Лучший по профессии 2015
Лучший по профессии AXAWARD 2013
Лучший по профессии 2011
Лучший по профессии 2009
 
3,322 / 3547 (125) ++++++++++
Регистрация: 13.06.2004
Адрес: Москва
Цитата:
Сообщение от Raven Melancholic Посмотреть сообщение
И опять иерархия наследования таблиц, грустно.
Я считал, что те проблемы, которые наследование DAX2012 создало в ранее существующих механизмах, это просто проблемы роста. Но вот уже D365 идет по планете, а воз и ныне там.
Впечатление, что команда предложившая и реализовавшая наследование таблиц, появилась на проекте, что-то сделала и слиняла в другие проекты.
Если посмотреть на это глазами большой компании - то ведь как было:
1. Реализация наследования в AX2012RTM. Когда реально каждая таблица, участвующая в наследовании была реальной таблицей в БД.
2. Реализация наследования в AX2012R2. Когда всю иерархию таблиц слили по структуре данных в одну корневую родительскую таблицу. Т.е. теперь физически с т.з. БД любой запрос к подчиненной таблице в иерархии (в АХ) превращается (в БД) в запрос к корневой родительской таблице с фильтром по полю InstanceRelationType, в котором сидит TableId подчиненной таблицы.

При этом в такой реализации использовать наследование таблиц абсолютно бессмысленно - проще сделать одну большую таблицу и уже с ней работать. Как минимум с индексами проблем не будет (в АХ нельзя сделать индекс с участием поля из подчиненной таблицы и родительской таблицы одновременно. А это напрямую влияет на производительность в том случае, когда этот индекс сильно нужен)

Ну т.е. (как вариант развития событий) сначала сделали "по уму", затем видимо "не взлетело" по производительности - сделали костыль, "проштрафившуюся" команду уволили и продолжили с этим жить.
__________________
Возможно сделать все. Вопрос времени
За это сообщение автора поблагодарили: twilight (1).
Старый 04.09.2022, 13:02   #8  
twilight is offline
twilight
MCTS
MCBMSS
 
881 / 237 (9) ++++++
Регистрация: 17.10.2004
Адрес: Королёв
Цитата:
Сообщение от sukhanchik Посмотреть сообщение
Как минимум с индексами проблем не будет (в АХ нельзя сделать индекс с участием поля из подчиненной таблицы и родительской таблицы одновременно. А это напрямую влияет на производительность в том случае, когда этот индекс сильно нужен)
Так в первом варианте реализации даже теоретически нельзя индекс такой сделать. Т. е. получается концепция наследования таблиц в целом нежизнеспособна?
__________________
I could tell you, but then I would have to bill you.
Старый 04.09.2022, 13:26   #9  
Damn is offline
Damn
Участник
 
436 / 154 (6) ++++++
Регистрация: 28.05.2003
Адрес: в глуши
Цитата:
Сообщение от twilight Посмотреть сообщение
Т. е. получается концепция наследования таблиц в целом нежизнеспособна?
Почему в целом нежизнеспособна.
Концепция из пункта 1 могла бы нормально жить. Когда в БД каждая таблица - отдельно. Непонятно почему этот вариант отвергли.
__________________
Дмитрий
Старый 05.09.2022, 07:30   #10  
sukhanchik is offline
sukhanchik
Administrator
Аватар для sukhanchik
MCBMSS
Злыдни
Лучший по профессии 2015
Лучший по профессии AXAWARD 2013
Лучший по профессии 2011
Лучший по профессии 2009
 
3,322 / 3547 (125) ++++++++++
Регистрация: 13.06.2004
Адрес: Москва
Цитата:
Сообщение от twilight Посмотреть сообщение
Т. е. получается концепция наследования таблиц в целом нежизнеспособна?
Лично я считаю, что в той реализации, которая была сделана в АХ - да, нежизнеспособна. Да, такая вещь, как наследование методов может быть и удобна, но она легко (и с бОльшим удобством) может быть вынесена на иерархию классов. В остальном - пользы нет.

Цитата:
Сообщение от Damn Посмотреть сообщение
Почему в целом нежизнеспособна.
Концепция из пункта 1 могла бы нормально жить. Когда в БД каждая таблица - отдельно. Непонятно почему этот вариант отвергли.
Всё верно, в ситуации, когда каждая таблица отдельная - действительно эта концепция более жизненная.... Если не учитывать вопросы производительности. Но вопрос производительности - ключевой. Джойн из нескольких таблиц всегда работает медленнее и требует бОльших вычислительных ресурсов, нежели выборка из одной таблицы. К тому же когда имеется одна большая таблица - многие выборки легко оптимизируются индексами. Когда целая связка таблиц - этот вопрос решается гораздо менее эффективно (хотя казалось - везде также есть индексы).

Собственно - изменение архитектуры БД, частичная нормализация таблиц и привели к тому, что AX 2012 стала требовать (на демо-виртуалке) минимум 32 Гб, в то время, как для AX 2009 4 Гб хватало за глаза.

В D365FO в демо-виртуалке такого "взрывного роста" к требованиям уже не случилось, но надо понимать, что:
1. Структуру БД радикально уже не меняли
2. Убрали клиента, который использовал определенные ресурсы системы.
3. Скорость работы в системе при этом не увеличилась
__________________
Возможно сделать все. Вопрос времени
Старый 05.09.2022, 14:46   #11  
Damn is offline
Damn
Участник
 
436 / 154 (6) ++++++
Регистрация: 28.05.2003
Адрес: в глуши
Цитата:
Сообщение от imir Посмотреть сообщение
Я в свое время словил в этом триггере ошибку, когда строка логирования обрезалась 1000-ю символами, предложил вариант с добавлением CONVERT(nvarchar(max) ..) - пофиксили оперативно.
Про 1000 символов мне вспомнилось как я попробовал выбирать данные из БД с помощью классов Connection, Statement и ResultSet.
Выяснилось что строковые поля обрезаются до 1000 символов. Пришлось писать SQL-запросы с помощью классов .NET.
__________________
Дмитрий
Старый 05.09.2022, 16:06   #12  
Raven Melancholic is offline
Raven Melancholic
Участник
Аватар для Raven Melancholic
Самостоятельные клиенты AX
Лучший по профессии 2015
 
2,164 / 1296 (48) ++++++++
Регистрация: 21.03.2005
Адрес: Москва-Петушки
А в связи с тем, что заполнение переехало на уровень триггеров указание common.skipDataBaseLog(true) теперь работает?
Старый 05.09.2022, 16:43   #13  
Damn is offline
Damn
Участник
 
436 / 154 (6) ++++++
Регистрация: 28.05.2003
Адрес: в глуши
Цитата:
Сообщение от Raven Melancholic Посмотреть сообщение
А в связи с тем, что заполнение переехало на уровень триггеров указание common.skipDataBaseLog(true) теперь работает?
Скорее всего работает. Я специально не проверял.
Но в триггере есть такой участок кода :
X++:
    SELECT @userId = DBO.SysGetUserIdFromContextInfo();
    SELECT @sessionId = DBO.SysGetSessionIdFromContextInfo();
    IF EXISTS(SELECT TOP 1 TABLEID FROM SYSSKIPDATABASELOG WHERE USERID = @userId AND SESSIONID = @sessionId AND TABLEID = 13271)
    BEGIN
        RETURN;
    END
__________________
Дмитрий
Старый 11.09.2022, 12:59   #14  
Damn is offline
Damn
Участник
 
436 / 154 (6) ++++++
Регистрация: 28.05.2003
Адрес: в глуши
Обнаружилось ещё одно "новшество" в форме ЖБД в D365.
На вкладке "История" теперь нельзя увидеть изменения регистра в строковых полях. Имеются в виду случаи когда вы в поле поменяли значение с "test" на "Test". В форме при отображении эти изменения игнорируются. А в журнал базы данных они попадают.
X++:
            if (fieldId != 0 && (!System.String::IsNullOrEmpty(newValue) || !System.String::IsNullOrEmpty(oldValue)))
            {
                // If field was saved as array field, we use the field index to get the field id
                fieldId = this.getExtendedFieldIdByIdx(useExtendedFieldId, fieldId, SysDatabaseLogDataParser::IsArrayFieldName(fieldNameDb) ? idx : 1);
                if (newValue != oldValue && (this.LogType != DatabaseLogType::Update || fieldName != 'RecVersion'))
                {
                    list.addStart([fieldId, newValue, oldValue]);
                }
            }
Причина в том что в X++ сравнение двух переменных newValue и oldValue происходит без учёта регистра. Аксапта - это case insensitive система, мы это знаем.
Если в этом участке кода переменные newValue и oldValue сравнивать с помощью newValue.Equals(oldValue), то всё нормализуется и мы в форме ЖБД увидим изменения, связанные с регистром текста.
__________________
Дмитрий
За это сообщение автора поблагодарили: S.Kuskov (2).
 

Похожие темы
Тема Автор Раздел Ответов Посл. сообщение
patrickmouwen: D365 F&O/Commerce interfacing via Azure API Management: My Best Practices Blog bot DAX Blogs 0 10.03.2022 02:47
Sumit Potbhare: Retail Warehousing | Wrap up | Approach to D365 for Commerce with Adv WH Mgmt Blog bot DAX Blogs 0 28.04.2021 13:12
patrickmouwen: How to Unlock Many Hidden D365 Retail Features! Blog bot DAX Blogs 0 13.05.2020 22:13
patrickmouwen: D365 Retail APIs Part III: How to use the Retail APIs from Power Automate (Flow) and Logic App Blog bot DAX Blogs 0 28.01.2020 02:15
patrickmouwen: D365 Retail APIs Part II: How to know exactly what happens inside D365 Retail Blog bot DAX Blogs 0 14.12.2019 01:17
Опции темы Поиск в этой теме
Поиск в этой теме:

Расширенный поиск
Опции просмотра

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

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

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