В пору обновления приложения Axapta 3.0 до AX 2009 я столкнулся с серьезной проблемой при подъеме модификаций для таблиц, поля которых в стандарте используют идентификаторы из диапазона usr-слоя (см. тему
DAX2009: поля таблиц стандартного приложения с идентификаторами из диапазона usr-слоя). Попытки синхронизации таких таблиц неизменно заканчивались ошибками, а поскольку происходило все на пустой разрабоческой базе, было решено обойти придурь ядра с помощью "программной эмуляции": удалить вообще записи в SqlDictionary для проблемных таблиц и воссоздать их, "как положено". Ниже приводится использовавшийся для этого код, немного сдобренный комментариями, - авось еще кому пригодится. В коде поля типа UtcDateTime обрабатываются специальным образом, обсуждение см. в теме
Зачем нужно поле для хранения временной зоны для значений полей типа UtcDateTime?
X++:
public static server void sqlDictionaryFill4Table(tableId _tableId)
{
#macrolib.DictField
#define.RecIdBaseType (49) // для полей с типом RecId/RefRecId/createdTransactionId/modifiedTransactionId используется не Types::Int64, а этот тип
#define.TZIDsuffix ('_TZID') // для несистемных полей типа UtcDateTime в БД создается дополнительное поле с кодом временной зоны, в которой было записано значение
SqlDictionary sqlDict;
SysdictType dictType;
DictTable dictTable = new DictTable( _tableId );
DictField dictField;
ArrayIdx arrIdx;
Counter numOfSqlFields; // сколько записей для полей таблицы должно быть в SqlDictionary
fieldName fieldName;
fieldId fieldId;
boolean processTableField(DictField _dictField, ArrayIdx _arrIdx, boolean _isTzIdField = false)
{
ArrayIdx dictArrIdx;
str infoName; // это имя поля сугубо для сообщений
FieldName sqlName;
boolean ret;
;
if (_isTzIdField)
{
if ( _dictField.baseType() != Types::UtcDateTime
|| _dictField.id() == fieldnum(Common, createdDateTime)
|| _dictField.id() == fieldnum(Common, modifiedDateTime)
)
{
throw error( Error::wrongUseOfFunction( funcname() ) );
}
dictArrIdx = _dictField.arraySize() + _arrIdx;
sqlName = _dictField.dateTimeTimeZoneRuleFieldName( _arrIdx - 1 );
infoName = _dictField.name() + #TZIDsuffix;
}
else
{
dictArrIdx = _arrIdx;
sqlName = _dictField.name( DbBackend::Sql, _arrIdx );
infoName = _dictField.name();
}
select firstonly sqlDict
where sqlDict.tabId == _dictField.tableid()
&& sqlDict.fieldId == _dictField.id()
&& sqlDict.array == dictArrIdx
;
if (!sqlDict)
{
sqlDict.clear();
sqlDict.initValue();
sqlDict.tabId = _dictField.tableid();
sqlDict.fieldId = _dictField.id();
sqlDict.array = dictArrIdx;
sqlDict.name = strupr( _dictField.name( DbBackend::Native, _arrIdx ) );
sqlDict.sqlName = sqlName;
dictType = new SysDictType( _dictField.typeId() );
if (_isTzIdField)
{
sqlDict.fieldType = Types::Integer;
}
else
if ( _dictField.id() == fieldnum(Common, RecId)
|| _dictField.id() == fieldnum(Common, createdTransactionId)
|| _dictField.id() == fieldnum(Common, modifiedTransactionId)
|| _dictField.typeId() == typeid(RecId)
|| _dictField.typeId() == typeid(RefRecId)
|| ( dictType
&& dictType.isExtending( extendedtypenum(RecId) )
)
)
{
// для RecId в поле fieldType прописывается не Types::Int64, а число 49
sqlDict.fieldType = #RecIdBaseType;
}
else
{
sqlDict.fieldType = _dictField.baseType();
}
sqlDict.strSize = _dictField.stringLen();
sqlDict.shadow = bitTest( _dictField.flags(), #DBF_SHADOW );
sqlDict.rightJustify = bitTest( _dictField.flags(), #DBF_RIGHT );
sqlDict.nullable = _dictField.baseType() == Types::Container || _dictField.baseType() == Types::VarString;
sqlDict.flags = sqlDict.shadow; // а вот ни фига не _dictField.flags();
if (sqlDict.validateWrite())
{
sqlDict.insert();
ret = true;
info( strfmt( @"Создана запись для поля %1.%2%3",
dictTable.name(), infoName, _dictField.arraySize() > 1 ? strfmt( @"[%1]", _arrIdx ) : ''
));
// для всех несистемных полей UtcDateTime создаем также связанное поле TZID
if ( !_isTzIdField
&& _dictField.baseType() == Types::UtcDateTime
&& _dictField.id() != fieldnum(Common, createdDateTime)
&& _dictField.id() != fieldnum(Common, modifiedDateTime)
)
{
processTableField( _dictField, _arrIdx, true );
}
}
else
{
ret = checkFailed( strfmt( @"Запись в %1 для поля %2.%3 не была создана", tablestr(SqlDictionary), dictTable.name(), infoName ) );
}
}
return ret;
}
;
if (!dictTable)
{
throw error( strfmt( @"Не удалось создать объект %1 для таблицы '%2' (%3)", classstr(DictTable), tableid2name( _tableId ), _tableId ) );
}
if (dictTable.isSystemTable())
{
throw error( strfmt( @"Таблица '%1' - системная, на это я пойтить не могу", dictTable.name() ) );
}
if (!dictTable.isSql())
{
throw error( strfmt( @"Таблицы '%1' вообще не должно быть в базе", dictTable.name() ) );
}
for (fieldId = dictTable.fieldNext( 0 ); fieldId; fieldId = dictTable.fieldNext( fieldId ))
{
dictField = dictTable.fieldObject( fieldId );
if (dictField && (dictField.flags() & #DBF_STORE))
{
fieldName = dictField.name();
for (arrIdx = 1; arrIdx <= dictField.arraySize(); arrIdx++)
{
numOfSqlFields++;
processTableField( dictField, arrIdx );
}
}
}
select firstonly sqlDict
where sqlDict.tabId == _tableId
&& sqlDict.fieldId == 0
;
if (!sqlDict)
{
sqlDict.clear();
sqlDict.initValue();
sqlDict.tabId = _tableId;
sqlDict.name = strupr( dictTable.name() );
sqlDict.sqlName = dictTable.name( DbBackend::Sql );
sqlDict.strSize = numOfSqlFields; // для "заголовка" таблицы тут указывается, сколько у нее полей в БД
sqlDict.flags = dictTable.isView(); // "мой дедуктивный метод..."
sqlDict.insert();
info( strfmt( @"Создана запись для таблицы %1", dictTable.name() ) );
}
}