Чтение метаданных таблицы Dynamics NAV с помощью SQL

Я хотел бы иметь возможность считывать метаданные таблицы Dynamics NAV 2013 непосредственно из базы данных SQL Server, не требуя среды разработки NAV.

Я могу просмотреть двоичные столбцы больших двоичных объектов SQL "image" с помощью запроса, подобного следующему (отфильтруйте в соответствии с предложением WHERE):

SELECT 
    o.[Name],
    m.[Object Type],
    m.[Metadata], -- XML Metadata
    m.[User Code], -- C# Metadata
    m.[User AL Code] -- C/AL Metadata
FROM [Navision].[dbo].[Object Metadata] AS m
JOIN [Navision].[dbo].[Object] AS o
ON m.[Object ID] = o.[ID]
AND o.[Company Name] = 'YourCompanyName'
AND o.[Type] = 0 -- 0 is NAV Table Object Type

Я могу сохранить двоичные данные из [Метаданные], [Код пользователя] и [Код пользователя AL] в файлы, используя код.Net или быстрый скрипт с драйвером SQL. Я попытался использовать 7-zip для распаковки, шестнадцатеричный редактор для просмотра и команду Cygwin "file" для обнаружения этих типов BLOB-файлов.

К сожалению, я не могу понять, как декодировать или распаковывать двоичные данные в читаемый или пригодный для использования формат. До тех пор, пока я не смогу использовать данные в этих полях напрямую, я должен открыть среду разработки NAV и использовать конструктор объектов для просмотра свойств OptionString, разделенных запятыми, для раскрывающегося списка поиска с нуля (каждый элемент в списке хранится в внутренняя база данных в виде целого числа - 0 для первого элемента, 1 для второго и т. д.). Строковые значения отсутствуют в справочной таблице SQL, но NAV помещает их в большие двоичные метаданные таблицы.

Это недостающее звено для меня, чтобы полностью поддерживать моих пользователей NAV в качестве администратора базы данных без необходимости в NAV-разработчике искать эти сопоставления настраиваемых полей NAV для номеров. Затем я могу просмотреть эти значения списка и создать соответствующие операторы SQL CASE или пользовательские таблицы поиска по мере необходимости.

После того, как я получу эту часть, я смогу создавать расширенные представления SQL, запросы, отчеты и инструменты без необходимости доступа к инструментам внешнего пользователя или разработчика Dynamics NAV.

Пожалуйста, дайте мне знать, если у вас есть знания о формате двоичных данных, используемых для этих свойств BLOB-объектов метаданных объектов. Любой совет о том, как преобразовать в читаемый или пригодный для использования формат, был бы полезен.

6 ответов

Решение

Мне удалось получить ответ для формата этих двоичных полей метаданных от автора блога deV.ch - человек против кода, Dynamics NAV & C# .NET. Основываясь на обратном инжиниринге devch, мы определили, что первые четыре байта (32 бита) этих полей используются NAV для хранения "магического числа", которое определяет пользовательский тип BLOB-объекта NAV.

В случае этих полей метаданных магическое число NAV Compressed Blob-Type равно 0x02457d5b (шестнадцатеричное). Чтобы использовать стандартный.Net DeflateStream для распаковки, просто отбросьте эти первые четыре байта магического числа, а затем обработайте остальную часть потока с помощью DeflateStream как обычно.

Мне удалось успешно протестировать этот процесс с помощью.Net, теперь я планирую протестировать с Python или некоторыми другими инструментами для дефляции сторонних производителей, чтобы проверить, соответствует ли реализация дефлята отраслевому стандарту. Еще раз спасибо devch за статью, которая привела к этому решению: Доступ к сжатым BLOB-объектам извне NAV (NAV2013) (Revisited).

Обновление: протестировано с Python zlib и все работает! Соответствующий стандартам алгоритм Deflate используется после удаления пользовательского магического числа типа NAV Blob. Вот пример кода (Python):

# Example Using Python 3.x
import zlib, sys, struct

# NAV custom Blob-Type identifier (first 4 bytes)
magic = struct.unpack('>I',sys.stdin.buffer.read(4))[0]
print('magic number = %#010x' % magic, file=sys.stderr)
# Remaining binary data is standard DEFLATE without header
input = sys.stdin.buffer.read()
output = zlib.decompress(input,-15)
sys.stdout.buffer.write(output)

Для проверки используйте что-то вроде следующего:

python -u test.py < Input_Meta.blob > Output_Meta.txt

Конечно.Net DeflateStream работает и после удаления первых четырех байтов. Этот пример просто показывает, что вы не ограничены использованием языков.Net.

Можно извлечь metadata из этой таблицы через Nav и, как я вижу, он хранится в виде простого текста, но в двоичном поле. Это может быть сохранено в файл, используя простой MemoryStream (в Nav это называется OutSteram). Итак, для таблицы № 3 я получаю следующий XML:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<MetaTable xmlns="urn:schemas-microsoft-com:dynamics:NAV:MetaObjects" ID="3" CaptionML="ENU=Payment Terms;RUS=Условия платежа" DataPerCompany="1" Name="Payment Terms" LookupFormID="4" DataCaptionFields="1,5">
    <Fields>
        <Field ID="1" Datatype="Code" DataLength="10" Enabled="1" FieldClass="Normal" Name="Code" CaptionML="ENU=Code;RUS=Код" BlankNumbers="DontBlank" BlankZero="0" SignDisplacement="0" Editable="1" NotBlank="1" Numeric="0" DateFormula="0" ClosingDates="0" Title="0" AutoIncrement="0" ValidateTableRelation="1" TestTableRelation="1" ExtendedDatatype="None"/>
        <Field ID="2" Datatype="DateFormula" Enabled="1" FieldClass="Normal" Name="Due Date Calculation" CaptionML="ENU=Due Date Calculation;RUS=Расчет срока оплаты" BlankNumbers="DontBlank" BlankZero="0" SignDisplacement="0" Editable="1" NotBlank="0" Numeric="0" DateFormula="0" ClosingDates="0" Title="0" AutoIncrement="0" ValidateTableRelation="1" TestTableRelation="1" ExtendedDatatype="None"/>
        <Field ID="3" Datatype="DateFormula" Enabled="1" FieldClass="Normal" Name="Discount Date Calculation" CaptionML="ENU=Discount Date Calculation;RUS=Расчет даты скидки" BlankNumbers="DontBlank" BlankZero="0" SignDisplacement="0" Editable="1" NotBlank="0" Numeric="0" DateFormula="0" ClosingDates="0" Title="0" AutoIncrement="0" ValidateTableRelation="1" TestTableRelation="1" ExtendedDatatype="None"/>
        <Field ID="4" Datatype="Decimal" Enabled="1" FieldClass="Normal" Name="Discount %" CaptionML="ENU=Discount %;RUS=Скидка (%)" BlankNumbers="DontBlank" BlankZero="0" SignDisplacement="0" Editable="1" MinValue="0" MaxValue="100" NotBlank="0" Numeric="0" DateFormula="0" ClosingDates="0" Title="0" AutoIncrement="0" ValidateTableRelation="1" TestTableRelation="1" ExtendedDatatype="None"/>
        <Field ID="5" Datatype="Text" DataLength="50" Enabled="1" FieldClass="Normal" Name="Description" CaptionML="ENU=Description;RUS=Описание" BlankNumbers="DontBlank" BlankZero="0" SignDisplacement="0" Editable="1" NotBlank="0" Numeric="0" DateFormula="0" ClosingDates="0" Title="0" AutoIncrement="0" ValidateTableRelation="1" TestTableRelation="1" ExtendedDatatype="None"/>
        <Field ID="6" Datatype="Boolean" Enabled="1" FieldClass="Normal" Name="Calc. Pmt. Disc. on Cr. Memos" CaptionML="ENU=Calc. Pmt. Disc. on Cr. Memos;RUS=Расчет скидки оплаты по кредит-нотам" BlankNumbers="DontBlank" BlankZero="0" SignDisplacement="0" Editable="1" NotBlank="0" Numeric="0" DateFormula="0" ClosingDates="0" Title="0" AutoIncrement="0" ValidateTableRelation="1" TestTableRelation="1" ExtendedDatatype="None"/>
    </Fields>
    <Keys>
        <Key Enabled="1" Key="Field1" MaintainSQLIndex="1" MaintainSIFTIndex="1" Clustered="1"/>
    </Keys>
    <FieldGroups>
        <FieldGroup GroupID="1" GroupName="DropDown" GroupFields="Field1,Field5,Field2"/>
    </FieldGroups>
</MetaTable>

Предположим, это то, что вы хотите.

Код, записывающий его в файл в Nav, будет выглядеть так:

ObjectMetadata:Record(Object Metadata)
Code:BigText
File:File       
CodeStream:InStream     
FileStream:OutStream        

ObjectMetadata.INIT;

IF ObjectMetadata.GET(1,3) THEN
 BEGIN
  ObjectMetadata.CALCFIELDS(Metadata);
  File.CREATE('C:\temp\Code.txt');
  File.CREATEOUTSTREAM(FileStream);

  clear(codestream);
  ObjectMetadata."Metadata".CREATEINSTREAM(CodeStream);
  Code.READ(CodeStream);
  Code.WRITE(FileStream);

  file.close();
 END;

Теперь у вас есть варианты: попробуйте сделать то же самое в SQL/.Net (я не заинтересован в этом), или вы можете попросить своего разработчика Nav выполнить какое-то пакетное задание, которое будет периодически (или по запросу) обрабатывать все таблицы ". matadata и сохранить его во внешнюю таблицу / файл / что угодно, что вы можете получить доступ из SQL.

Приношу извинения за то, что поднял эту тему из мертвых, но я наконец-то заработал и подумал, что другим это может оказаться полезным:

      select
  CONVERT(
    varchar(max)
    , DECOMPRESS(
        CONVERT(varbinary(max), 0x1F8B0800000000000400) 
        + 
        CONVERT(varbinary(max), SUBSTRING([Metadata],5,8000))
      )
  )
from 
  [dbo].[Object Metadata] 
where 
  [Object Type] = 1 and [Object ID] = @objId

Это будет работать «из коробки», еслиDECOMPRESS(SQL Server 2016+) имел возможность игнорировать заголовок gzip, но, увы, этого не происходит. Я проверил несколько таблиц, и заголовок не изменился, поэтому я просто разместил его спереди.

Надеюсь, это поможет кому-то еще!

Почему бы не использовать веб-сервисы и не получать нужные данные, не делая ничего необычного?

Затем пользователи могут использовать PowerPivot для Excel и создавать свои собственные отчеты.

Вы также можете использовать надстройку Excel для NAV и просто экспортировать данные в Excel непосредственно из NAV, а затем обновлять их в Excel.

Написал это на основе приведенных выше данных Брэндона Робертса (спасибо, Брэндон!) Я получаю некоторые таблицы, которые не распаковываются должным образом, и я думаю, что это, возможно, проблема с заголовком.

С cte AS ( выберите om.[Тип объекта], om.[ID объекта], TRY_CAST(CONVERT(varchar(max), DECOMPRESS(CONVERT(varbinary(max), 0x1F8B0800000000000400)+CONVERT(varbinary(max), SUBSTRING(om) .[Метаданные],5,80000)) ) КАК XML) КАК [XML],CONVERT(varchar(max), DECOMPRESS(CONVERT(varbinary(max), 0x1F8B0800000000000400)+CONVERT(varbinary(max), SUBSTRING(om. [Метаданные],5,80000)) ) ) AS [Raw] из [dbo].[Метаданные объекта] om где om.[Тип объекта] = 1 )

SELECT TOP 1000 XML.value('declare namespace ns="urn:schemas-microsoft-com:dynamics:NAV:MetaObjects"; (/ns:MetaTable/@ID)[1]', 'nvarchar(100)') AS [Таблица №_],XML.value('объявить пространство имен ns="urn:schemas-microsoft-com:dynamics:NAV:MetaObjects"; (/ns:MetaTable/@Name)[1]', 'nvarchar(100)' ) AS [Имя таблицы], FieldData.value('@ID', 'int') AS [Номер поля_],FieldData.value('@Name', 'nvarchar(100)') AS [Имя поля], FieldData.value('@Datatype', 'nvarchar(100)') AS [Тип данных], FieldData.value('@DataLength', 'int') AS [Длина данных], FieldData.value('@OptionString', 'nvarchar (max)') AS [Option String], XML FROM cte CROSS APPLY XML.nodes('declare namespace ns="urn:schemas-microsoft-com:dynamics:NAV:MetaObjects"; /ns:MetaTable/ns:Fields/ ns:Field[@FieldClass="Normal" и @Enabled="1"]') AS Fields(FieldData);

Прежде всего спасибо за сценарий!

У меня также возникли некоторые проблемы с этим. Для меня в NAV2015 Таблица 27 и 5363 не будут работать... Я вижу, что он способен распаковывать метаданные, но похоже, что строка заканчивается в одной точке, что делает XML неполным и, как таковое, терпит неудачу...

Я решил это сейчас с помощью следующей настройки:

DECOMPRESS(CONVERT(varbinary(max), 0x1F8B0800000000000400) +CONVERT(varbinary(max), SUBSTRING(CONVERT(varbinary(max), om.[Metadata]),5,2147483647 )) ) AS [XML],

Другие вопросы по тегам