Способ извлечь данные из базы 1С

в 4:55, , рубрики: Без рубрики

Способ извлечь данные из базы 1С

Хочу поделиться способом выгрузки данных из 1С на основе COM-соединения. Этот вариант можно назвать альтернативным использованию стандартных и нестандартных обработок 1С. Для примера взят язык C#, а выгрузка выполняется в один большой XML-файл.

Где можно применить

Для специалиста обмен данными между двумя системами сложен в том плане, что нужно разбираться и в своей работе, и в незнакомой среде. Если вам нужны данные из 1С, но изучение 1С не является для вас перспективным направлением, как вариант предлагаю способ получения данных через внешнее соединение вообще без визуального открытия 1С.
Попробовать применить данный способ можно в разных задачах:

  • загрузить в другую базу данных произвольного формата;
  • перейти с 1С на другую систему учета (миграция данных);
  • направить поток данных в базу для анализа (OLAP);
  • иметь архивную копию данных в открытом виде.

Сразу хочу сказать о недостатке данного способа. В 1С написано много обработок выгрузки данных под разные цели. Использование обработок 1С можно назвать самым официальным способом получения данных. Типичная обработка не только выгружает данные из базы, но и удобным образом их преобразует и компонует через соединения в запросах и путем проведения расчетов на встроенном языке. Выгружая данные описанным в статье способом вы получаете только первичные таблицы, сохраненные в базе данных, и может потребоваться их дальнейшая обработка.

Подробнее о результате

Что мы получим?

Через внешнее соединение можно выгрузить данные в файл произвольного формата или передать полученные записи в другую базу данных вообще не сохраняя промежуточный файл. Здесь выгрузка в xml-файл сделана как демонстрация самой возможности выгрузки.
Данные извлекаются из базы 1С и помещаются в один xml-файл. Вот пример получаемого файла.

<?xml version="1.0" encoding="utf-8"?>
<database>
  <catalog name="Контрагенты">
    <item>
      <attribute name="Ссылка" value="0cc56775-0daa-11e3-bf95-f46d04eec7f5" />
      <attribute name="ВерсияДанных" value="AAAAAQAAAAA=" />
      <attribute name="ПометкаУдаления" value="false" />
      <attribute name="Предопределенный" value="false" />
      <attribute name="Код" value="000000001" />
      <attribute name="Наименование" value="Торговый дом" />
      <attribute name="ИНН" value="1111111111" />
      <attribute name="КПП" value="222222222" />
    </item>
    <item>
      <attribute name="Ссылка" value="0cc56776-0daa-11e3-bf95-f46d04eec7f5" />
      <attribute name="ВерсияДанных" value="AAAAAwAAAAA=" />
      <attribute name="ПометкаУдаления" value="false" />
      <attribute name="Предопределенный" value="false" />
      <attribute name="Код" value="000000002" />
      <attribute name="Наименование" value="Магазин" />
      <attribute name="ИНН" value="3333333333" />
      <attribute name="КПП" value="444444444" />
    </item>
  </catalog>
  <catalog name="Номенклатура">
    <item>
      <attribute name="Ссылка" value="0cc56777-0daa-11e3-bf95-f46d04eec7f5" />
      <attribute name="ВерсияДанных" value="AAAAAgAAAAA=" />
      <attribute name="ПометкаУдаления" value="false" />
      <attribute name="Предопределенный" value="false" />
      <attribute name="Код" value="000000001" />
      <attribute name="Наименование" value="Телевизор" />
      <attribute name="Цена" value="15000" />
    </item>
    <item>
      <attribute name="Ссылка" value="0cc56778-0daa-11e3-bf95-f46d04eec7f5" />
      <attribute name="ВерсияДанных" value="AAAAAwAAAAA=" />
      <attribute name="ПометкаУдаления" value="false" />
      <attribute name="Предопределенный" value="false" />
      <attribute name="Код" value="000000002" />
      <attribute name="Наименование" value="Ноутбук" />
      <attribute name="Цена" value="25000" />
    </item>
  </catalog>
  <document name="Счет">
    <attribute name="Ссылка" value="0cc56779-0daa-11e3-bf95-f46d04eec7f5" />
    <attribute name="ВерсияДанных" value="AAAAAQAAAAA=" />
    <attribute name="ПометкаУдаления" value="false" />
    <attribute name="Номер" value="000000001" />
    <attribute name="Дата" value="2013-08-25T21:19:56" />
    <attribute name="Проведен" value="true" />
    <attribute name="Контрагент" value="0cc56776-0daa-11e3-bf95-f46d04eec7f5" />
    <attribute name="Строки">
      <line>
        <attribute name="Ссылка" value="0cc56779-0daa-11e3-bf95-f46d04eec7f5" />
        <attribute name="НомерСтроки" value="1" />
        <attribute name="Номенклатура" value="0cc56778-0daa-11e3-bf95-f46d04eec7f5" />
        <attribute name="Количество" value="1" />
        <attribute name="Цена" value="25000" />
        <attribute name="Сумма" value="25000" />
      </line>
    </attribute>
  </document>
  <document name="Счет">
    <attribute name="Ссылка" value="0cc5677a-0daa-11e3-bf95-f46d04eec7f5" />
    <attribute name="ВерсияДанных" value="AAAAAwAAAAA=" />
    <attribute name="ПометкаУдаления" value="false" />
    <attribute name="Номер" value="000000002" />
    <attribute name="Дата" value="2013-08-25T21:22:49" />
    <attribute name="Проведен" value="true" />
    <attribute name="Контрагент" value="0cc56775-0daa-11e3-bf95-f46d04eec7f5" />
    <attribute name="Строки">
      <line>
        <attribute name="Ссылка" value="0cc5677a-0daa-11e3-bf95-f46d04eec7f5" />
        <attribute name="НомерСтроки" value="1" />
        <attribute name="Номенклатура" value="0cc56778-0daa-11e3-bf95-f46d04eec7f5" />
        <attribute name="Количество" value="1" />
        <attribute name="Цена" value="25000" />
        <attribute name="Сумма" value="25000" />
      </line>
      <line>
        <attribute name="Ссылка" value="0cc5677a-0daa-11e3-bf95-f46d04eec7f5" />
        <attribute name="НомерСтроки" value="2" />
        <attribute name="Номенклатура" value="0cc56777-0daa-11e3-bf95-f46d04eec7f5" />
        <attribute name="Количество" value="2" />
        <attribute name="Цена" value="15000" />
        <attribute name="Сумма" value="30000" />
      </line>
    </attribute>
  </document>
  <document name="Накладная">
    <attribute name="Ссылка" value="0cc5677b-0daa-11e3-bf95-f46d04eec7f5" />
    <attribute name="ВерсияДанных" value="AAAAAgAAAAA=" />
    <attribute name="ПометкаУдаления" value="false" />
    <attribute name="Номер" value="000000001" />
    <attribute name="Дата" value="2013-08-25T21:23:10" />
    <attribute name="Проведен" value="true" />
    <attribute name="Контрагент" value="0cc56776-0daa-11e3-bf95-f46d04eec7f5" />
    <attribute name="Кладовщик" value="Сидоров С.С." />
    <attribute name="Строки">
      <line>
        <attribute name="Ссылка" value="0cc5677b-0daa-11e3-bf95-f46d04eec7f5" />
        <attribute name="НомерСтроки" value="1" />
        <attribute name="Номенклатура" value="0cc56777-0daa-11e3-bf95-f46d04eec7f5" />
        <attribute name="Количество" value="15000" />
        <attribute name="Цена" value="15000" />
        <attribute name="Сумма" value="15000" />
      </line>
    </attribute>
  </document>
</database>

Какие данные выгружаются

Числовые и строковые типы сохраняются в выгружаемом файле в обычном строковом виде. Поля ссылок, которые отображаются в формах с кнопкой выбора с тремя точками «...», выгружаются как значения GUID. Например, в счете указан атрибут «Контрагент» со значением «0cc56776-0daa-11e3-bf95-f46d04eec7f5». В справочнике контрагентов по этой ссылке мы можем найти «Торговый дом».
Выгрузка выполняется без расчета на какую-то конкретную конфигурацию и структуру данных. Путем перебора метаданных выгружаются все справочники и все документы. У каждого элемента справочника и у каждого документа выгружаются все атрибуты. Если у документа есть табличные части, по каждой табличной части выгружаются все строки.
В примере реализована выгрузка только основных типов таблиц: справочников, документов и табличных частей документов. Не реализована выгрузка констант, регистров сведений и табличных частей справочников. Выгрузка регистров накопления и регистров бухгалтерии не выполняется, т.к. эти данные могут быть получены из первичных документов. Но для целей анализа, возможно, удобнее было бы выгружать записи регистров.

Как происходит получение данных

Запуск программы должен выполняться с установленной 1С, т.к. к ней выполняется подключение через COM-соединение. Строка подключения в примере создается для файлового варианта, но при необходимости можно переправить на подключение к серверу 1С. Пользователь с указанным паролем должен быть прописан в базе. У пользователя должны иметься права на чтение всех данных и на возможность установить внешнее соединение.
Монопольный режим не требуется. «Выгрузить» базу таким образом можно не прерывая работу пользователей.

Альтернативные способы

Данный способ извлечения данных не единственный. Можно даже сказать, что доступ через COM-соединение самый медленный и перечисленные ниже способы более производительные. Но по этим способам есть и принципиальные недостатки.

Внешняя или встроенная в конфигурацию обработка

Тот же результат в виде xml-файла можно получить из внешней или встроенной обработки 1С. Преимущество выполнения обработки 1С в ее скорости. Все-таки это внутренняя кухня, а вызовы из COM-соединения в плане временных затрат чего-то да стоят.
Недостаток использования обработки в том, что на этом автоматизация и заканчивается. Пользователь должен вручную открыть форму обработки, указать параметры, нажать кнопку выгрузки.

Процедура в модуле, доступном для внешнего соединения

Процедуру выгрузки можно разместить в общем модуле, доступном для внешнего соединения. Сама процедура при этом должна быть объявлена как экспортная.

Процедура ExportDatabase(Путь) Экспорт
	
	Файл = Новый ЗаписьXML;
	Файл.ОткрытьФайл(Путь);
	Файл.ЗаписатьОбъявлениеXML();
	Файл.ЗаписатьНачалоЭлемента("database");
	...
	Файл.ЗаписатьКонецЭлемента();
	Файл.Закрыть();
	
КонецПроцедуры

Теперь достаточно обратиться к процедуре выгрузки из C#.

	Connection.ExportDatabase(path);

Преимущество данного способа в быстром выполнении. Реализация процедуры выгрузки может эффективно использовать вызовы сервера и обмен данных между сервером и клиентом.
Основной недостаток в необходимости вносить изменения в конфигурацию для редактирования общего модуля.

Типовые средства

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

Физический доступ к базе

Есть еще способы доступа к данным извне, но они опираются на знание структуры базы данных. Если база 1С расположена на SQL сервере, фактически таблицы открыты для чтения. Можно делать запросы и получать все данные. Проблема лишь в том, что структура данных сложна и требует очень внимательной обработки.
Еще более сложно выполнить чтение из файлового варианта базы 1С. Структура файла 1CD не документирована, и в принципе может меняться. Здесь нужно отметить, что для целей доступа к информации можно выполнять конвертирование из файлового режима в SQL и наоборот.

Полный текст программы

Приведу код C# класса установки соединения с базой 1С и выгрузки данных, а также пример использования данного класса.

using System;
using System.Text;
using System.Xml;

namespace ConsoleApplication
{
    class Export1C
    {
        dynamic Connection;

        public void Connect(string path, string user = "", string password = "")
        {
            dynamic connector = Activator.CreateInstance(Type.GetTypeFromProgID("V82.COMConnector"));
            string connectionString = "File="" + path + """;
            if (user != "")
                connectionString += ";Usr="" + user + """;
            if (password != "")
                connectionString += ";Pwd="" + password + """;
            Connection = connector.Connect(connectionString);
        }

        public void Export(string path)
        {
            XmlTextWriter xml = new XmlTextWriter(path, Encoding.UTF8);
            xml.Formatting = Formatting.Indented;
            xml.WriteStartDocument();
            xml.WriteStartElement("database");

            // Catalogs
            foreach (dynamic catalog in Connection.Metadata.Catalogs)
            {
                xml.WriteStartElement("catalog");
                xml.WriteAttributeString("name", catalog.Name);
                dynamic query = Connection.NewObject("Query");
                query.Text = "select * from catalog." + catalog.Name;
                dynamic items = query.Execute().Unload();
                for (int i = 0; i < items.Count(); i++)
                {
                    xml.WriteStartElement("item");
                    for (int j = 0; j < items.Columns.Count(); j++)
                    {
                        xml.WriteStartElement("attribute");
                        xml.WriteAttributeString("name", items.Columns.Get(j).Name);
                        xml.WriteAttributeString("value", Connection.XMLString(items.Get(i).Get(j)));
                        xml.WriteEndElement();
                    }
                    xml.WriteEndElement();
                }
                xml.WriteEndElement();
            }

            // Documents
            foreach (dynamic document in Connection.Metadata.Documents)
            {
                dynamic query = Connection.NewObject("Query");
                query.Text = "select * from document." + document.Name;
                dynamic table = query.Execute().Unload();
                for (int i = 0; i < table.Count(); i++)
                {
                    xml.WriteStartElement("document");
                    xml.WriteAttributeString("name", document.Name);
                    dynamic docref = null;
                    for (int j = 0; j < table.Columns.Count(); j++)
                    {
                        xml.WriteStartElement("attribute");
                        string field = table.Columns.Get(j).Name;
                        xml.WriteAttributeString("name", field);
                        dynamic tabular = document.TabularSections.Find(field);
                        if (tabular == null)
                        {
                            xml.WriteAttributeString("value", Connection.XMLString(table.Get(i).Get(j)));
                            if (field == "Ссылка")
                                docref = table.Get(i).Get(j);
                        }
                        else
                        {
                            dynamic subquery = Connection.NewObject("Query");
                            subquery.Text = "select * from document." + document.Name + "." + field +
                                            " as lines where lines.Ref=&Ref";
                            subquery.SetParameter("Ref", docref);
                            dynamic lines = subquery.Execute().Unload();
                            for (int line = 0; line < lines.Count(); line++)
                            {
                                xml.WriteStartElement("line");
                                for (int col = 0; col < lines.Columns.Count(); col++)
                                {
                                    xml.WriteStartElement("attribute");
                                    xml.WriteAttributeString("name", lines.Columns.Get(col).Name);
                                    string value = Connection.XMLString(lines.Get(line).Get(col));
                                    xml.WriteAttributeString("value", value);
                                    xml.WriteEndElement();
                                }
                                xml.WriteEndElement();
                            }
                        }
                        xml.WriteEndElement();
                    }
                    xml.WriteEndElement();
                }
            }
            xml.WriteEndElement();
            xml.WriteEndDocument();
            xml.Close();
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Export1C export = new Export1C();
            export.Connect("D:\TestBase", "User", "pass");
            export.Export("D:\Export.xml");
        }
    }
}

Экспорт разбит на два блока: выгрузка справочников и выгрузка документов. Для получения видов справочников и видов документов выполняется обращение к метаданным.
Данные каждой таблицы извлекаются отдельными запросами. По документам также выгружаются табличные части. Для этого формируются подзапросы к табличным частям с отбором по ссылке на выгружаемый документ.
Для конкретных задач, возможно, придется вносить изменения в код. Формируемые имена тегов xml-файла (catalog, document, attribute, name, value) выбраны произвольно. Если внести небольшие правки в код, структуру получаемого xml-файла можно изменить. Также можно ограничить выгружаемые данные только определенными таблицами и атрибутами, используя условия в циклах выборки метаданных.

Автор: movsb

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js