Демонстрация прямого чтения данных 1С:Предприятие через LINQ

Появление в .Net framework технологии LinqToSql вызвало у многих и у меня, в том числе, резонный вопрос: «А зачем это нужно?». Ведь многие как использовали SQL-выражения или хранимые процедуры для доступа к MSSQL-базе, так и продолжают использовать их даже после выхода LinqToSql. Только недавно технология мне приглянулась с точки зрения прямого доступа к данным 1С:Предприятие 8.x, размещенным на сервере MS SQLServer. Дело в том, что нет абсолютно никакого соглашения о том, как 1С называет свои поля и таблицы, когда хранит их внутри базы данных. Именно здесь облегчит жизнь относительно новая технология Microsoft. К статье прилагается пример на Asp.Net MVC 2, демонстрирующий прямой доступ к данным 1С, работающей с установленной конфигурацией Управление Торговлей.

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

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

Системные требования

Для просмотра примера вам ничего не нужно – только перейти по ссылке: http://linq-demo.1csoftware.com/

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

Для того, чтобы воспользоваться предложенным способом на практике необходимо следующее:

  • 1С:Предприятие 8.x, работающее на MSSQL (возможно, в будущем будут охвачены другие типы СУБД)
  • Elisy .Net Bridge SDK, содержащий обработку LinqTo1C для генерации Dbml- и CS-файлов на основе метаданных 1С:Предприятие

Зачем нужен Linq-доступ к 1С:Предприятие

Linq-доступ к 1С:Предприятие является более родным с точки зрения .Net-программирования. Такой доступ экономит деньги на лицензии 1С, уменьшает число преобразований типов, не требует организации пула соединений, как при доступе через COM-соединения.

Предложенный способ может быть использован для наполнения контентом веб-сайтов (внутренних и внешних для предприятия), организации внутренних/внешних .Net-приложений, например, каталогов продукции.

Кто в данных хозяин

Прямой доступ к данным 1С рождает противоречие между законодательством Российской Федерации и лицензией 1С:Предприятие.

Позиция компании 1С по поводу прямого доступа следующая: «Нельзя обращаться к данным информационной базы напрямую, минуя уровень объектов работы с данными "1С:Предприятия" – например, при помощи средств СУБД».

Фирма 1С, закладывая такие пункты в лицензию, лукавит и сама нарушает законодательство РФ, а именно статьи: Статья 1334 п.1 "Исключительное право изготовителя базы данных" ГК РФ часть 4, а также Статья 25 п.1 и п.3 "Свободное воспроизведение программ для ЭВМ и баз данных. Декомпилирование программ для ЭВМ" Закона об авторском праве и смежных правах. В Гражданском Кодексе сказано:

"Изготовителю базы данных, создание которой (включая обработку или представление соответствующих материалов) требует существенных финансовых, материальных, организационных или иных затрат, принадлежит исключительное право извлекать из базы данных материалы и осуществлять их последующее использование в любой форме и любым способом".

Как еще можно обращаться к данным 1С

К 1С:Предприятие можно обращаться из .Net следующими способами:

  • COM-доступ,
  • Используя средство: web-extension,
  • Опубликовав 1С- веб-сервис,
  • Сделав экспорт в промежуточное хранилище, например, в Access;

COM-доступ – это традиционный вид доступа к данным 1С извне. Его можно использовать, но в .Net способ превращается в вереницу InvokeMember-вызовов. В .Net framework 4 ситуация улучшается через использование dynamic-типа. Но по гибкости и типизированности способ уступает LINQ. Сомневаюсь, что скорость работы данного способа будет выше прямого доступа к СУБД.

Web-расширение для 1С– это платный продукт, требующий дополнительной лицензии 1С. Ядро построено на WebForms, что может существенно затруднить использование в среде Asp.Net MVC.

Публикация веб-сервиса 1С требует особых знаний конфигурирования 1С. Кроме того, хорошим тоном при публиации веб-сервисов считается использование SSL-протокола, а это дополнительные проблемы получения сертификатов, конфигурации и доступа.

Четвертый вариант промежуточного хранилища лишает решение "свежести" данных. Измененные данные не будут моментально отображаться во внешнем приложении.

Проблема прямого доступа к 1С:Предприятие

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

Структура данных 1С:Предприятие внутри MSSQL

Это обстоятельство не дает при доступе к данным из .Net использовать SQL-выражения, генерируемые в строках, и вызов их для получения DataReader и DataTable-объектов. Однажды сгенерированный запрос может потерять смысл и не работать при значимом обновлении конфигурации 1C или на конфигурации другого предприятия. В нашем случае запрос, объединяющий справочники Номенклатура, ЕдиницыИзмрения и регистр накопления ТоварыНаСкладах, может выглядеть так:

SELECT TOP 5 NomenclatureRef,
Nomenclature._Description, Nomenclature._Fld720RRef AS UnitRef,
Units._Description,
SUM(_Fld6354) AS Quantity, MAX(Units._Fld501) AS Weight
FROM
(
SELECT _Period, _Fld6349RRef AS WarehouseRef, _Fld6350RRef AS NomenclatureRef, _Fld6354
FROM _AccumReg6348
WHERE _Active = 1 AND _Period BETWEEN DATEADD(month, DATEDIFF(month, 0, GETDATE()), 0) AND GetDate()
UNION ALL
SELECT _Period, _Fld6349RRef AS WarehouseRef, _Fld6350RRef AS NomenclatureRef, _Fld6354
FROM _AccumRegTotals6356
WHERE _Period = DATEADD(month, DATEDIFF(month, 0, GETDATE()), 0)
) AS Balance
INNER JOIN _Reference53 AS Nomenclature ON Nomenclature._Marked = 0 AND NomenclatureRef = Nomenclature._IDRRef AND Nomenclature._IDRRef IN (SELECT _IDRRef FROM ##tt)
INNER JOIN _Reference35 AS Units ON Units._Marked = 0 AND Nomenclature._Fld720RRef = Units._IDRRef
GROUP BY NomenclatureRef,
Nomenclature._Description, Nomenclature._Fld720RRef,
Units._Description

А что, если в следующий раз 1С решит поменять имя регистра _AccumReg6348 на, например, _AccumReg7000 или поменяет где-нибудь название поля. Весь запрос перестанет работать.

В LinqToSql проблема остается – меняющиеся названия объектов БД, но ее значимость можно уменьшить, сгенерировав dbml/cs-файл из 1С, присвоив названия классам из метаданных конфигурации 1С. Если при обновлении конфигурации изменятся имена таблиц или полей БД, то необходимо будет заново сгенерировать dbml/cs-файл и поместить его, например, в App_Code-каталог без изменения другого кода программы.

Автоматическая генерация LinqToSql-файлов

Для работы механизмов LinqToSql необходимы специальные .Net – классы, которые генерируются компилятором автоматически на основе DBML-файла. DBML-файл – это XML-файл с определением соответствий между названиями таблиц, полей и названиями .Net-классов. DBML-файл можно посмотреть визуально:

Внешний вид DBML-файла в редакторе Visual Studio

Для облегчения генерации DBML-файла была создана внешняя обработка Elisy.LinqTo1CSql.81.epf для 1С:Предприятие. Поставляется она в составе с Elisy .Net Bridge SDK, так как обращается к функциональности .Net framework из 1C:Предприятие.

После указания строки подключения к СКЛСерверу, имени выгружаемого файла, имен выгружаемых объектов обработка Elisy.LinqTo1CSql на выходе получает dbml- и cs-файлы. CS-файл содержит все необходимые описания классов и может быть вставлен в .Net-проект. На DBML-файл можно смотреть из редактора Visual Studio.

После вставки CS-файла в проект получение остатков товаров по складу из кода C# будет выглядеть так:

public static IQueryable<Номенклатура> ПолучитьОстатки(УправлениеТорговлей dc, DateTime текущаяДата)
{
DateTime началоМесяца = dc.GetTable<РегистрНакопленияТоварыНаСкладахИтоги>().Where(item => item._Period <= текущаяДата).Max(item => item._Period);

var товарыНаСкладахИтоги = from товарНаСкладе in dc.GetTable<РегистрНакопленияТоварыНаСкладахИтоги>()
where (товарНаСкладе._Period == началоМесяца)
select new { Номенклатура = товарНаСкладе.Номенклатура, Количество = товарНаСкладе.Количество };

var товарыНаСкладахДетали = from товарНаСкладе in dc.GetTable<РегистрНакопленияТоварыНаСкладах>()
where (началоМесяца <= товарНаСкладе.Период) && (товарНаСкладе.Период <= текущаяДата) && (товарНаСкладе.Активность == new byte[] { 1 })
select new { Номенклатура = товарНаСкладе.Номенклатура, Количество = товарНаСкладе._RecordKind == 0 ? товарНаСкладе.Количество : -1 * товарНаСкладе.Количество };

IQueryable<Номенклатура> списокНоменклатурыСОстатками = from товарНаСкладе in товарыНаСкладахИтоги.Concat(товарыНаСкладахДетали)
group товарНаСкладе by new { товарНаСкладе.Номенклатура } into группа
let количествоСумма = группа.Sum(элемент => элемент.Количество)
where количествоСумма > 0
select new Номенклатура { Ссылка = группа.Key.Номенклатура, Остаток = количествоСумма };

return списокНоменклатурыСОстатками;
}

Выводы

Представленный способ LinqToSql прямого доступа (чтения) к данным 1С имеет свои преимущества по отношению к другим способам. Стоимость его невысока. Скорость доступа к данным соизмерима или выше других способов. Данные всегда остаются актуальными и "свежими". Изменение данных в 1С приведет к немедленному изменению отображаемых данных в .Net-приложении.

Недостатки также присутствуют. Необходимость регенерации cs-файла при значимом изменении конфигурации. Ограниченное использование – невозможность использовать в файловом режиме работы 1С. Работа возможна только на чтение для уменьшения вероятности ошибок при записи данных в 1С.

Способ можно рекомендовать для наполнения контентом внутренних и внешних сайтов предприятий, организации каталогов продукции. LinqToSql конкурирует с web-доступом, появившимся в 1С:Предприятие 8.2. В 1С:Предприятие 8.2 веб-доступ лишен SEO-оптимизации, управления на уровне HTML/CSS-кода, AJAX-средствами, требует дополнительные лицензии, а 1С-программистам легче переходить с 8.1 версии на Asp.Net, чем на 8.2, «благодаря» ограничениям и мешанине из серверного и клиентского кода в каждой форме.

Предложенный к статье пример с открытым исходным кодом LinqTo1CWeb демонстрирует основные операции обращения к данным 1С: получение остатков по регистру, получение среза по ценовому регистру, соединение справочников и регистров в одну таблицу, их группировку. Пример написан на основе новой технологии Asp.Net MVC 2, дающей дополнительные преимущества в отношении гибкости и SEO-оптимизации.