Как я обработку на альтернативный сервер выносил

Публикация № 998389

Разработка - Практика программирования

экспорт импорт обработка com C# 1c альтернативный сервер тяжелая обработка

В данном посте хочу поделиться опытом. Однажды возник инцидент, который смотивировал реализовать обработку, которую запускал бы обычный пользователь 1С, в основной системе. Но весь процесс обработки должен происходить за пределами рабочей базы. А юзабилити должно остаться на уровне простого пользователя. В качестве решения я выбрал службу Windows (С#), приложение инициации на клиенте и далее прошу под кат...

Всем доброго времени суток. Не буду сейчас вдаваться во все подробности, ибо это займет много Вашего и моего времени. 

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

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

Как происходит запуск? Ежедекадно, специалисты департамента актуарных расчетов, получив отмашку от департамента статистики, как правило это происходит ближе к 19:00 вечера, запускают обработочку, где указывают дату сведений, на которую необходимо сформировать базу действующих договоров и связанные с ней расчеты, указывают флажками отчеты которые необходимо получить по завершении (отчеты выгружаются в Excel, в заранее определенную директорию), и нажимают кнопку "Сформировать". Все. Далее специалисты уходят домой, а 1С продолжает работать приблизительно до полуночи. Но это не суть.

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

Решение оказалось весьма простым. 

На альтернативном сервере я установил службу Windows, которая прослушивает IP и Port. Как только по указанному порту на этот IP адрес, поступает сообщение с определенным кодовым словом, запускается процесс. В данном процессе я прописал все то, что до этого я какое-то время делал руками. А именно - программа снимает свежий бэкап рабочей базы. Данный бэкап помещается в расшаренную директорию рабочего сервера. Затем из этой директории файл перемещается на альтернативный сервер. Свежий бэкап восстанавливается на специальную базу для подготовки отчетности. А далее через COM соединение, на альтернативном сервере, в специальной базе данных запускается та самая обработка, с теми самыми параметрами, которые до этого указал пользователь. По завершению обработки, данные, средствами SQL bcp (bulk copy program) переносятся на рабочую базу данных. А тем временем, на альтернативном сервере запускается регламентное задание по заданному расписанию, и начинает выгружать необходимые отчеты. Таким образом, все тяжелые запросы и обработки данных выносятся за пределы рабочего сервера, что существенно снижает нагрузку. А так же предотвращает ошибки связанные с блокировками. Единственная серьезная нагрузка в этом алгоритме происходит в момент коммита данных переносимых с помощью sql bcp. 

 

А теперь попробую привести некоторые наглядные фрагменты этого всего процесса. 

Весь процесс у нас начинается с приложения-инициатора. Там все просто. 

Создаем в VS консольное приложение на C#. В тексте основного модуля пишем такой код:

using System;
using System.Net.Sockets;
using System.Text;


namespace ExtFS_Client_Consol
{
    class Program
    {

        /// <summary>
        /// Отправляем на сервер запрос на инициацию процесса формирования БДН
        /// </summary>
        private static string SendMess(string IP, int Port)
        {

            //Инициализация
            string SMess = @"StartFormation";
            char[] outMessage = SMess.ToCharArray();

            try
            {
                TcpClient client = new TcpClient(IP, Port); 
                Byte[] data = Encoding.UTF8.GetBytes(outMessage);
                NetworkStream stream = client.GetStream();

                try
                {
                    stream.Write(data, 0, data.Length);

                    Byte[] comingdata = new Byte[256];
                    stream = client.GetStream();

                    string responseData = String.Empty;
                    StringBuilder completeMessage = new StringBuilder();
                    int numberOfBytesRead = 0;
                    do
                    {
                        numberOfBytesRead = stream.Read(comingdata, 0, comingdata.Length);
                        completeMessage.AppendFormat("{0}", Encoding.UTF8.GetString(comingdata, 0, numberOfBytesRead));
                    }
                    while (stream.DataAvailable);
                    responseData = completeMessage.ToString();
                    return responseData;
                }
                finally
                {
                    stream.Close();
                    client.Close();
                }
            }
            catch (Exception err)
            {
                Console.WriteLine(err.Message);
                return @"";
            }
        }

        static void Main(string[] args)
        {
            if (args.Length > 0)
            {
                try
                {
                    string IP = args[0];
                    int Port = Convert.ToInt16(args[1]);
                    string response = SendMess(IP, Port);
                }
                catch (Exception err)
                {
                    Console.WriteLine(err.Message);
                }
            }
            else
            {
                Console.WriteLine(@"Не переданы параметры запуска! Выполнение - не возможно!");
                Console.ReadKey();
            }
        }
    }
}

Как понятно из текста, значение IP адреса и номер порта мы передаем через параметры запуска. Если такие параметры не заданы при запуске, то и результат работы будет нулевой. Выйдет лишь уведомление с предупреждением о том что ничего не запущено. 

Далее, создаем в нашей системе 1С, константу с типом "ХранилищеЗначений". Она нам понадобиться для хранения скомпилированного консольного приложения. Я не буду тут описывать как именно этот exe попадет в константу. А вот что мы делаем когда пользователь нажимает кнопку "Сформировать" смотрите далее.

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

// Метод инициализации запуска формирования БДН/ЯО на альтернативном сервере. 
// Происходит сохранение во временную директорию exe файла, и запуск его с 
// параметрами (ip, port). EXE файл в свою очередь отправляет через tcp/ip 
// команду старта на сервер.
Процедура ИнициироватьЗапускФормированияНаАльтернативномСервере() Экспорт
	
	Попытка
		ПФ = ПолучитьПараметрыВнешнегоФормирования();
		ВремКаталог = КаталогВременныхФайлов();
		ФайловыйПакет = Константы.ИсполняемыйФайлИнициацииВнешнегоФормированияЯО.Получить().Получить();
		ДвоичныеДанные = ФайловыйПакет.Файл.Получить();
		ПутьФайла = ВремКаталог + ФайловыйПакет.ИмяФайла;
		НашФайл = Новый Файл(ПутьФайла);
		Если НашФайл.Существует() Тогда
			УдалитьФайлы(ПутьФайла);
			ДвоичныеДанные.Записать(ПутьФайла);
		Иначе
			ДвоичныеДанные.Записать(ПутьФайла);
		КонецЕсли;
		WSHShell = Новый COMОбъект("WScript.Shell");
		WSHShell.CurrentDirectory = ВремКаталог;
		WSHShell.Run(ФайловыйПакет.ИмяФайла + " " + ПФ.IP + " " + ПФ.Port, 0);
	Исключение
		#Если Клиент Тогда
			Сообщить("Ошибка при попытки запустить программу инициализации удаленного формирования БДН\ЯО: " + ОписаниеОшибки());
		#КонецЕсли
	КонецПопытки;
	
КонецПроцедуры

Сразу после запуска данного метода подключаем обработчик ожидания такого плана:

		ПодключитьОбработчикОжидания("ПроверкаСтатусаЗапускаВнешнегоФормирования", 4);

Сам подключаемый метод:  

// Обработчик каждые 2 секунды проверяет соответствующий регистр сведений, поменялся ли там статус на Истина.
// Если после 7й попытки статус не изменился - выводится сообщение о возможной ошибке при инициализации. 
// Если статус поменялся - выводится сообщение об успешном запуске внешнего формирования.
// В любом случае после заверешения обработчика ожидания, запись в регистре сведений - удаляется. 
Процедура ПроверкаСтатусаЗапускаВнешнегоФормирования()
	Запрос = Новый Запрос;
	Запрос.Текст = "ВЫБРАТЬ
	               |	УдаленноеФормированиеБДН.ДатаСведений КАК ДатаСведений
	               |ИЗ
	               |	РегистрСведений.УдаленноеФормированиеБДН КАК УдаленноеФормированиеБДН
	               |ГДЕ
	               |	УдаленноеФормированиеБДН.ДатаСведений = &ДатаСведений
	               |	И УдаленноеФормированиеБДН.ПроцессЗапущен";
	Запрос.УстановитьПараметр("ДатаСведений", ДатаСведений);
	Если Запрос.Выполнить().Выгрузить().Количество() > 0 Тогда
		Сообщить("Внешнее формирование успешно запущено!");
		ОтключитьОбработчикОжидания("ПроверкаСтатусаЗапускаВнешнегоФормирования");
		Набор = РегистрыСведений.УдаленноеФормированиеБДН.СоздатьНаборЗаписей();
		Набор.Очистить();
		Набор.Записать();
	Иначе
		КолПроверокСтатусаВФЯО = КолПроверокСтатусаВФЯО + 1;
		Если КолПроверокСтатусаВФЯО = 15 Тогда
			Сообщить("Внешнее формирование не было запущено на стороне альтернативного сервера, либо возникли проблемы с обратной связью. Необходимо проверить процесс выполнения.", СтатусСообщения.Важное);
			ОтключитьОбработчикОжидания("ПроверкаСтатусаЗапускаВнешнегоФормирования");
			Набор = РегистрыСведений.УдаленноеФормированиеБДН.СоздатьНаборЗаписей();
			Набор.Очистить();
			Набор.Записать();
		КонецЕсли;
	КонецЕсли;
КонецПроцедуры

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

Так как же устроена серверная часть данной системы, которая по сути своей, является службой Windows? Давайте разбираться по порядку. 

Создаем проект Службы Windows. Добавляем новый класс. Назовем его по смыслу. В моем случае это удаленный сервис формирования, поэтому я назвал его RFS_Serv. Содержимое класса привожу ниже.

using System;
using System.ServiceProcess;
using System.IO;
using System.Threading;

namespace RFS_Service
{
    public partial class RFS_Serv : ServiceBase
    {
        private static IPListener MyListener = null;
        private static Thread MyThread;
        private LogWriter Logger;
        private Props XMLSett;

        public RFS_Serv()
        {
            InitializeComponent();
        }

        protected override void OnStart(string[] args)
        {
            try
            {
                Logger = new LogWriter(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location));

                XMLSett = new Props();
                XMLSett.ReadXml();

                MyListener = new IPListener(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), XMLSett.Fields.LocalIP, XMLSett.Fields.Port);

                MyThread = new Thread(new ThreadStart(MyListener.StartListener));
                MyThread.Name = "ListenerThread";
                MyThread.Start();
                Logger.Log(@"Служба запущена");
            }
            catch (Exception err)
            {
                string mess = new ExceptionMessages().GetExMess(err);
                Logger.Log(@"error: " + mess);
            }
        }

        protected override void OnStop()
        {
            try
            {
                MyListener.NeedListen = false;
                Logger.Log(@"Служба остановлена");

                MyListener.Dispose();
                Logger.Dispose();
                GC.SuppressFinalize(XMLSett);
                GC.SuppressFinalize(MyThread);
            }
            catch (Exception err)
            {
                string mess = new ExceptionMessages().GetExMess(err);
                Logger.Log(@"error: " + mess);
            }
        }
    }
}

Данный класс является ключевым классом сервиса, и в обработчике события OnStart мы инициируем запуск "прослушки" ip адреса. Для этого мы описываем класс IPListener. Его описание так же привожу ниже:

using System;
using System.Text;
using System.Net;
using System.Net.Sockets;

namespace RFS_Service
{
    class IPListener
    {

        /// <summary>
        /// Путь от куда была запущена программа
        /// </summary>
        private static string _ProgrammLocation = @"";
        public string ProgrammLocation { get { return _ProgrammLocation; } set { _ProgrammLocation = value; } }

        /// <summary>
        /// IP адресс по которому ведем прослушивание
        /// </summary>
        private static string _curIPAdress = @"";
        public string curIPAdress { get { return _curIPAdress; } set { _curIPAdress = value; } }

        /// <summary>
        /// Port по котором ведем прослушивание
        /// </summary>
        private static int _curPort = 951;
        public int curPort { get { return _curPort; } set { _curPort = value; } }

        private static bool _NeedListen = false;
        public bool NeedListen { get { return _NeedListen; } set { _NeedListen = value; } }

        LogWriter Logger;

        /// <summary>
        /// Конструктор экземпляра класса
        /// </summary>
        /// <param name="ApplicationPath">Директория из которой запущено приложение</param>
        public IPListener(string ApplicationPath, string IP, int Port)
        {
            Logger = new LogWriter(ApplicationPath);
            ProgrammLocation = ApplicationPath;
            curIPAdress = IP;
            curPort = Port;
            NeedListen = false; //по умолчанию ничего не слушаем
        }

        /// <summary>
        /// Деструктор экземпляра класса
        /// </summary>
        public void Dispose()
        {
            Logger.Dispose();
            GC.SuppressFinalize(this);
        }

        public void StartListener()
        {

            _NeedListen = true;

            IPAddress localAddr = IPAddress.Parse(_curIPAdress);
            int port = _curPort;
            TcpListener server = new TcpListener(localAddr, port);
            string CommandFromClient = @"";
            char[] TrimChars = { ' ', '0', '\0' };
            server.Start();

            while (_NeedListen)
            {
                try
                {
                    TcpClient client = server.AcceptTcpClient();
                    NetworkStream stream = client.GetStream();
                    try
                    {
                        if (stream.CanRead)
                        {
                            byte[] myReadBuffer = new byte[1024];
                            StringBuilder myCompleteMessage = new StringBuilder();
                            int numberOfBytesRead = 0;
                            do
                            {
                                numberOfBytesRead = stream.Read(myReadBuffer, 0, myReadBuffer.Length);
                                myCompleteMessage.AppendFormat("{0}", Encoding.UTF8.GetString(myReadBuffer, 0, myReadBuffer.Length));

                            }
                            while (stream.DataAvailable);

                            CommandFromClient = myCompleteMessage.ToString().Trim(TrimChars);
                            if (CommandFromClient == @"StartFormation")
                            {
                                Logger.Log(@"Принята команда запуска формирования.");
                                RFS rfs = new RFS(); //Запустим процесс формирования ЯО БДН на текущем сервере. 
                                rfs.Execute();
                                rfs.Dispose();
                            }
                            else
                            {
                                Byte[] responseData = Encoding.UTF8.GetBytes(DateTime.Now.ToString(@"Полученный запрос не имеет описания на сервере!"));
                                stream.Write(responseData, 0, responseData.Length);
                                Logger.Log(@"Принята не опознанная команда!");
                            }
                        }

                    }
                    finally
                    {
                        stream.Close();
                        client.Close();
                    }
                }
                catch
                {
                    Logger.Log(@"Ошибка во время прослушивания");
                    break;
                }
            }

        }

        public void StopListen()
        {
            _NeedListen = false;
        }

    }
}

Как видим из содержимого, суть класса проста. При определенных условиях запускается цикл в отдельном потоке, в котором происходит прослушка IP И Port. Приходящие сообщения анализируются, и если пришла определенная команда, например "StartFormation", программа инициализирует класс RFS и запускает процесс обработки на удаленном (альтернативном) сервере. Класс RFS в моем случае расшифровывается как Remote Forming System.

Давайте рассмотрим подробнее, как именно происходит работа в этом классе. 

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

// Получает из структуры хранения наименование таблицы в СУБД(SQL) и наименования всех её полей.
// Предназначена для вызова из других программ, не предназначена для использования внутри 1С. 
// Возвращает строку разделенную точкой с запятой. На первом месте имя таблицы, затем список полей.
//
// Параметры: 
//	ИмяТаблицы - Строка - Наименование таблицы которую необходимо транслировать.
Функция GetTableStructure(ИмяТаблицы) Экспорт
	Попытка
		ОбъектМетаданных = Метаданные.НайтиПоПолномуИмени(ИмяТаблицы); 
		Если Не ОбъектМетаданных = Неопределено Тогда
			СписокПолей = "";
			Объект = Новый Массив;
			Объект.Добавить(ОбъектМетаданных);
			СХ = ПолучитьСтруктуруХраненияБазыДанных(Объект, Истина);
			Для Каждого ТекТаб Из СХ Цикл
				Если ТекТаб.Метаданные = ИмяТаблицы Тогда
					НаименованиеТаблицы = ТекТаб.ИмяТаблицыХранения;
				КонецЕсли;
				Для Каждого ТекПоле Из ТекТаб.Поля Цикл
					СписокПолей = СписокПолей + ";" + ТекПоле.ИмяПоляХранения;
				КонецЦикла;
				Возврат НаименованиеТаблицы + СписокПолей;
			КонецЦикла;
		Иначе
			Возврат "";
		КонецЕсли;
	Исключение
		ЯО.ВЛог(ОписаниеОшибки(), "TSQLMethods.GetTableStructure");
		Возврат "";
	КонецПопытки;
КонецФункции

Далее в классе RFS, в методе Execute пишем следующее.

                //Получим наименование таблицы статусов удаленного запуска
                Logger.Log(@"Получаем наименование таблицы рег.сведений УдаленноеФормированиеБДН");
                My1C Com1C = new My1C(AppPath);
                string StateTablesName = Com1C.GetTableStructure(WorkDBComConnect, @"РегистрСведений.УдаленноеФормированиеБДН");
                Com1C.Dispose();
                Com1C = null;

                //Отметимся в рабочей базе о том что процесс формирования запущен успешно
                Logger.Log(@"Установили статус в рабочей базе означающий что все запустилось");
                SetGoodStateOnMainServer(WorkUDLFile, StateTablesName);

В этом фрагменте кода видно как мы используем выше описанную функцию 1С. Она овзращает в виде строки наименование таблицы на уровне SQL, так же и список её полей. Получив имя физической таблицы на уровне SQL мы можем проставить в регистре сведений рабочей базы, флаг об успешном старте работы RFS. Используем для этого SQL скрипт Update.

Выглядеть это будет вот так:

        /// <summary>
        /// Метод проставляет в рабочей базе 1С, в регистре сведений "УдаленноеФормированиеБДН" признак "ПроцессЗапущен" в значение истина. Для всех существующих записей.
        /// </summary>
        /// <param name="UDLFile">Имя файла с настройками подключения к рабочей базе данных</param>
        /// <param name="TableStruct">Строка с физическим наименованием таблицы и её полей через ";"</param>
        private void SetGoodStateOnMainServer(string UDLFile, string TableStruct)
        {
            try
            {
                string[] TableNames = TableStruct.Split(';');
                string QueryText = String.Format(@"UPDATE {0} SET {2} = 0x01 where {2} = 0x00", TableNames);
                OleDbConnection con = new OleDbConnection(String.Format("File Name = {0}", UDLFile));
                OleDbCommand cmd = new OleDbCommand();

                cmd.Connection = con;
                con.Open();
                cmd.CommandType = CommandType.Text;
                cmd.CommandText = QueryText;
                cmd.CommandTimeout = 0;
                cmd.ExecuteScalar();
            }
            catch (Exception err)
            {
                string mess = new ExceptionMessages().GetExMess(err);
                Logger.Log(@"RFS->SetGoodStateOnMainServer() error: " + mess);
            }
        }

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

                //Запустим процесс создания Backup'а на рабочем сервере
                Logger.Log(@"Запущен процесс создания бэкапа рабочей базы");
                string BackupFileName = @"";
                bool WeHaveBackup = CreateFreshBK(WorkUDLFile, out BackupFileName);

Код функции CreateFreshBK не имеет ничего особенного но все же приведу его текст. 

        /// <summary>
        /// Метод создает на рабочем сервере БД, новый, самый свежий бэкап и помещает его в специальную, публичную директорию для скачивания на локальный.
        /// </summary>
        /// <param name="UDLFile">Имя UDL файла с настройками подключения к рабочей базе данных</param>
        /// <param name="FileName">Имя файла создаваемого бэкапа. Имя возвращается наружу для дальнейшего использования.</param>
        /// <returns>Если бэкап удалось создать - возвращается true</returns>
        private bool CreateFreshBK(string UDLFile, out string FileName)
        {

            string BackUpFileName = @"upp_eurasia_" + DateTime.Now.ToString(@"yyyy-MM-dd_hhmmss") + @".bak";
            FileName = BackUpFileName; //Вернем наружу имя файла бэкапа

            try
            {
                Logger.Log(@"Создаем новый бэкап рабочей базы данных в: " + MainServerCreateBackUpToPath + @"\" + BackUpFileName);

                string[] Params = new string[2];
                Params[0] = WorkDBConnect.DataBase;
                Params[1] = MainServerCreateBackUpToPath + @"\" + BackUpFileName;

                string query = String.Format(@"
                BACKUP DATABASE[{0}]
                TO DISK = N'{1}'
                WITH COMPRESSION, 
                INIT,
                NOFORMAT, 
                SKIP, 
                STATS = 10
                ", Params);

                string QueryText = query;
                OleDbConnection con = new OleDbConnection(String.Format(@"File Name = {0}", UDLFile));
                OleDbCommand cmd = new OleDbCommand();
                cmd.Connection = con;
                con.Open();
                cmd.CommandType = CommandType.Text;
                cmd.CommandText = QueryText;
                cmd.CommandTimeout = 0; 
                cmd.ExecuteScalar();

                return true;

            }
            catch (Exception err)
            {
                string mess = new ExceptionMessages().GetExMess(err);
                Logger.Log(@"RFS->CreateFreshBK() error: " + mess);
                return false;
            }
        }

После того как отработает метод создания бэкапа, проверим, был ли он создан успешно. Если все прошло успешно, продолжаем основное действие.

                // Если бэкап базы был успешно создан - продолжаем дальнейшие действия
                if (WeHaveBackup)
                {
                    //Перенесем созданный бэкап на текущий сервер
                    Logger.Log(@"Переносим свежий бэкап на локальный сервер");
                    FileInfo BackupFile = new FileInfo(MainServerBackUpPublicPath + @"\" + BackupFileName);
                    if (BackupFile.Exists)
                    {
                        BackupFile.MoveTo(LocalBackupPath + @"\" + BackupFileName);
                    }

                    //Зачистим все активные подключения к базе данных перед накатыванием бэкапа.
                    Logger.Log(@"Перед разверткой свежего бэкапа, удалим все существующие соединения с базой для формирования отчетности");
                    KillAllDBConnections(MasterUDLFile, ReportsDBConnect.DataBase);

                    //Развернем бэкап на базу для формирования регистров ЯО
                    Logger.Log(@"Запустили восстановление базы из бэкапа");
                    bool DB_Is_Restored = RestoreDB(MasterUDLFile, ReportsDBConnect.DataBase);

                    //Удалим файл бэкапа из которого подняли базу для формирования
                    FileInfo localBackupFile = new FileInfo(LocalBackupPath + @"\" + BackupFileName);
                    if (localBackupFile.Exists)
                    {
                        localBackupFile.Delete();
                    }

                    if (DB_Is_Restored)
                    {
                        //Запустим процесс формирования ЯО на местной базе
                        Com1C = new My1C(AppPath);
                        Logger.Log(@"Выставим корректные настройки подключения к БД для тестовой базы.");
                        //Выставим корректные настройки подключения к БД для тестовой базы.
                        Com1C.SetTestDBConnectionFor1C(ReportsDBComConnect, ReportsDBServerName);
                        Logger.Log(@"Запускаем на тестовой базе формирование регистров ЯО.");
                        //Запустим формирования БДН/ЯО и выгрузку отчетов.
                        Com1C.StartFormationReportCore(ReportsDBComConnect, ReportDay, @"", @"", @"");

                        //Проверим статус формирования в базе формирования отчености. Убедимся что формирование прошло успешно.
                        FormationgState state = (FormationgState)Com1C.GetState(ReportsDBComConnect, ReportDay);
                        Com1C.Dispose(); // больше нам не нужно COM соединение с базой
                        Com1C = null;

                        if (state == FormationgState.fsOK) // Перенесем данные за период с тестовой базы на боевую, при условии что все удачно сформировалось.
                        {
                            //Когда все сформированно выгрузим все необходимые регистры в спец. директорию с помощью bcp
                            Logger.Log(@"Экспортируем данные в файлы");
                            ExportData(ReportDay, ReportsDBConnect, ReportsDBComConnect);

                            //Если стоит соответствующая настройка - перенесем выгруженные данные на рабочую базу данных
                            if (NeedExportDataToWorkBase)
                            {
                                Logger.Log(@"Импортируем данные из файлов в рабочую базу");
                                ImportData(ReportDay, WorkDBConnect, WorkDBComConnect);
                            }
                            else
                            {
                                Logger.Log(@"Импорт данных в рабочую базу - отключен");
                            }
                        }
                        else
                        {
                            SMSBox.SendSMS(@"RFS State not fsOK! Data not migrated!");
                        }
                    }
                    else
                    {
                        Logger.Log(@"Так как не удалось восстановить базу из свежего бэкапа, дальнейшие действия - не возможны. Работа программы - завершена.");
                        SMSBox.SendSMS(@"RFS can't restore DB.");
                        return false;
                    }

                }
                else
                {
                    SMSBox.SendSMS(@"RFS: No backup Work stopped.");
                    return false;
                }

Как видно, сначала мы переносим созданный бэкап на локальный (альтернативный) сервер (от куда запущено приложение). 

Далее мы зачищаем все потенциальные активные сеансы подключения к базе данных, на которую будем восстанавливать свежий бэкап. Делается это с помощью не хитрого запроса. Но надо понимать что не всегда это на 100% помогает. Поэтому эта база данных не должна использоваться кем-то из вне в качестве дополнительной тестовой базы данных. 

        /// <summary>
        /// Убивает все подключения к базе на уровне MS SQL.
        /// </summary>
        /// <param name="UDLFile">Путь к UDL файлу с подключением к базе данных</param>
        /// <param name="DBName">Имя базы данных, подключения к которой - необходимо зачистить.</param>
        /// <returns>Если все прошло успешно - возвращает истину.</returns>
        private bool KillAllDBConnections(string UDLFile, string DBName)
        {
            try
            {
                string QueryText = String.Format(@"
                --Убиваем все активные сеансы подключенные к базе NameDB
                DECLARE @spid VARCHAR(200)
                DECLARE @kill_spid VARCHAR(200)
                DECLARE kill_session CURSOR FOR
                SELECT spid FROM [master].dbo.sysprocesses
                WHERE dbid=db_id('{0}')
                --spid идентификатор сеанса SQL Server.
                OPEN kill_session;
                FETCH NEXT FROM kill_session INTO @spid
                WHILE @@FETCH_STATUS = 0
                BEGIN
                    SET @kill_spid='KILL ' + @spid + char(10)
                    EXEC (@kill_spid)
                    FETCH NEXT FROM kill_session
                    INTO @spid;
                END;
                DEALLOCATE kill_session;", DBName);
                OleDbConnection con = new OleDbConnection(String.Format(@"File Name = {0}", UDLFile));
                OleDbCommand cmd = new OleDbCommand();
                cmd.Connection = con;
                con.Open();
                cmd.CommandType = CommandType.Text;
                cmd.CommandText = QueryText;
                cmd.CommandTimeout = 0; 
                cmd.ExecuteScalar();
            }
            catch (Exception err)
            {
                string mess = new ExceptionMessages().GetExMess(err);
                Logger.Log(@"EFS->KillAllDBConnections() error: " + mess);
                return false;
            }
            return true;
        }

Сам процесс восстановления базы тоже не хитрый. Смотрим далее

        /// <summary>
        /// Накатывает на указанную базу данных - последний актуальный бэкап рабочей базы.
        /// </summary>
        /// <param name="UDLFile">Имя UDL файла с настройками подключения к SQL инстансу, на котором числится база что необходимо восстановить из свежего бэкапа.</param>
        /// <param name="DBName">Имя базы данных - которую необходимо восстановить из бэкапа.</param>
        /// <returns>Если восстановление из бэкапа прошло успешно - возвращает истину.</returns>
        public bool RestoreDB(string UDLFile, string DBName)
        {

            bool result = false;

            string BackUpFileName = @"";

            try
            {
                string[] pfileslocal = Directory.GetFiles(LocalBackupPath, @"*.bak", SearchOption.TopDirectoryOnly);
                foreach (string pf in pfileslocal)
                {
                    BackUpFileName = pf.ToString();
                    break;
                }
                if (BackUpFileName != "")
                {
                    FileInfo fi = new FileInfo(BackUpFileName);
                    if (!fi.Exists)
                    {
                        result = false;
                        return result;
                    }
                }
                else
                    return false;

                Logger.Log(@"Восстанавливаем базу данных из бэкапа: " + BackUpFileName);

                string[] Params = new string[5];
                Params[0] = DBName;
                Params[1] = BackUpFileName;
                Params[2] = ReportsSQLDataPath;
                Params[3] = LogicalDBName;
                Params[4] = LogicalDBLogName;

                string query = String.Format(@"RESTORE DATABASE [{0}]
                FROM DISK = N'{1}'
                WITH FILE = 1,
                MOVE N'{3}' TO
                N'{2}\{0}.mdf',  
                MOVE N'{4}' TO
                N'{2}\{0}_log.ldf',  
                NOUNLOAD,  
                REPLACE,  
                STATS = 10", Params);

                string QueryText = query;
                OleDbConnection con = new OleDbConnection(String.Format(@"File Name = {0}", UDLFile));
                OleDbCommand cmd = new OleDbCommand();
                cmd.Connection = con;
                con.Open();
                cmd.CommandType = CommandType.Text;
                cmd.CommandText = QueryText;
                cmd.CommandTimeout = 0; 
                cmd.ExecuteScalar();

                return true;

            }
            catch (Exception err)
            {
                string mess = new ExceptionMessages().GetExMess(err);
                Logger.Log(@"RFS->RestoreDB() error: " + mess);
                return false;
            }
        }

Как известно, во всем должен быть порядок, поэтому удаляем за собой файл свежего бэкапа, сразу после того как восстановили его на нашу базу данных.

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

В самой 1С, у меня так же имеется регистр сведений, куда пишется флаг об успешном завершении моей обработки, поэтому в конце я проверяю что этот флаг установлен. Потому как если его нет, значит в процессе могла возникнуть ошибка, и тогда необходимо об этом уведомить меня, что бы я заблаговременно начал с этим разбираться. Разумеется это больше для перестраховки. И разумеется если была ошибка, нет большого смысла переносить данные в рабочую базу данных. А вот если все в порядке, то начинаем процесс переноса таблиц средствами BCP (bulk copy program). Как это происходит? Очень просто. Для этого у меня заготовлено два метода:

        /// <summary>
        /// Основной метод экспорта таблицы базы данных в файл с помощью утилиты bcp.
        /// </summary>
        /// <param name="RegName">Наименование регистра 1С, который необходимо выгрузить.</param>
        /// <param name="FileID">Идентификатор регистра 1С, который будет содержаться в наименовании файла, в который будут выгружаться данные.</param>
        /// <param name="InfoDate">Дата сведений, на которую необходимо произвести выгрузку.</param>
        /// <param name="ConnectionString">Строка подключения к базе данных.</param>
        /// <param name="Connect">Параметры подключения к базе данных (SQL).</param>
        /// <param name="ComConnect">Параметры подключения к базе данных (COM).</param>
        /// <param name="DateFilterFieldIndex">Индекс поля, по которому будет происходить отбор по периоду. Значение по умолчанию = 0 (_Period) (Необходимо для не переодических регистров)</param>
        private void ExportTable(string RegName, string FileID, DateTime InfoDate, string ConnectionString, DBConnectStruct Connect, DBConnectStruct ComConnect, int DateFilterFieldIndex = 0)
        {
            try
            {
                string DateFilterFieldName = @"_Period";
                My1C etCom1C = new My1C(AppPath);
                if (DateFilterFieldIndex != 0)
                {
                    string TableNameAndField = etCom1C.GetTableStructure(ComConnect, RegName);
                    string[] Fiels = TableNameAndField.Split(';');
                    DateFilterFieldName = Fiels[DateFilterFieldIndex];
                }
                string QueryText = @"Select * from " + Connect.DataBase + @".dbo." + etCom1C.GetTableStructure(ComConnect, RegName, true) + @" where " + DateFilterFieldName + " = " + QuotedStr(InfoDate.ToString("yyyy-MM-dd"), "'");
                string FileName = BCPTransitPath + @"\" + bcpFilePrefix + "_" + FileID + "_" + DateTime.Now.ToString(bcpFileMask) + ".bcp";
                string CommandBCP = GetExportBCPCommandText(QueryText, FileName, ConnectionString);
                ExecuteAndWait(@"bcp ", CommandBCP);
                etCom1C.Dispose();
            }
            catch (Exception err)
            {
                string mess = new ExceptionMessages().GetExMess(err);
                Logger.Log(@"RFS->ExportTable() error: " + mess);
            }
        }

        /// <summary>
        /// Основной метод импорта таблицы базы данных из файла с помощью утилиты bcp.
        /// </summary>
        /// <param name="RegName">Наименование регистра 1С, который необходимо загрузить из файла.</param>
        /// <param name="FileID">Идентификатор регистра 1С, который будет содержаться в наименовании файла, из которого будут загружаться данные.</param>
        /// <param name="InfoDate">Дата сведений, на которую необходимо произвести загрузку данных.</param>
        /// <param name="ConnectionString">Строка подключения к базе данных.</param>
        /// <param name="Connect">Параметры подключения к базе данных (SQL).</param>
        /// <param name="ComConnect">Параметры подключения к базе данных (COM).</param>
        /// <param name="UDLConnect">Наименование UDL файла с параметрами подключения к базе, в которую будет производиться импорт данных.</param>
        /// <param name="DateFilterFieldIndex">Индекс поля, по которому будет происходить отбор по периоду. Значение по умолчанию = 0 (_Period) (Необходимо для не переодических регистров)</param>
        private void ImportTable(string RegName, string FileID, DateTime InfoDate, string ConnectionString, DBConnectStruct Connect, DBConnectStruct ComConnect, string UDLConnect, int DateFilterFieldIndex = 0)
        {
            try
            {
                string DateFilterFieldName = @"_Period";
                My1C itCom1C = new My1C(AppPath);
                if (DateFilterFieldIndex != 0)
                {
                    string TableNameAndField = itCom1C.GetTableStructure(ComConnect, RegName);
                    string[] Fiels = TableNameAndField.Split(';');
                    DateFilterFieldName = Fiels[DateFilterFieldIndex];
                }
                string SQLTableName = Connect.DataBase + @".dbo." + itCom1C.GetTableStructure(ComConnect, RegName, true);
                itCom1C.Dispose();
                string QueryText = @"WITH CTE_DELETE AS (SELECT * FROM " + SQLTableName + @" where " + DateFilterFieldName + " = " + QuotedStr(InfoDate.ToString("yyyy-MM-dd"), "'") + ") Delete From CTE_DELETE"; //Запрос для удаления записей, которые будем сейчас вставлять. 
                OleDbConnection con = new OleDbConnection(String.Format(@"File Name = {0}", UDLConnect));
                OleDbCommand cmd = new OleDbCommand();
                cmd.Connection = con;
                con.Open();
                cmd.CommandType = CommandType.Text;
                cmd.CommandText = QueryText;
                cmd.CommandTimeout = 0; 
                cmd.ExecuteScalar();

                string FileName = BCPTransitPath + @"\" + bcpFilePrefix + "_" + FileID + "_" + DateTime.Now.ToString(bcpFileMask) + ".bcp";
                string CommandBCP = SQLTableName + @" in " + GetImportBCPCommandText(FileName, ConnectionString);

                ExecuteAndWait(@"bcp ", CommandBCP);
            }
            catch (Exception err)
            {
                string mess = new ExceptionMessages().GetExMess(err);
                Logger.Log(@"RFS->ImportTable() error: " + mess);
            }
        }
		

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

Вот собственно и все. Данные успешно сформированы и перенесены. Если же что-то пошло не так, ответственные люди получают СМС (при наличии такой возможности в компании), либо e-mail c предупреждением об ошибке. 

Про отчеты. На альтернативном сервере глушаться все регламентные задания кроме выгрузки отчетов. И по расписанию начинает происходить проверка, завершилось ли формирование данных или нет. Как только оно завершилось, в спец. регистрах хранится список настроек и список выгружаемых отчетов. И они начинают выгружаться. 

Изначально я планировал выгружать их так же через COM, но почему-то именно с отчетами возникли проблемы, в процессе выгрузки, COM-соединение рвалось и процесс умолкал. Когда появится время - думаю я найду верное решение. И постараюсь найти время что бы им поделиться. Но пока и регламентное задание не плохо справляется, конечный результат - есть.

p.s.

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

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

К статье прикрепляю архив с основными классами на C#. Весь проект целиком, разумеется, выложить не могу по вполне ясным причинам. 

Всем добра! :)

(Если на Ваш взгляд, в статье не хватает какой-то более развернутой информации, пишите, постараюсь дополнить).

Скачать файлы

Наименование Файл Версия Размер
Архив с исходным кодом класса My1C для C#, ряд вспомогательных классов. Может выступить в качестве шаблона для нового проекта. Не универсальное решение.

.zip 8,31Kb
2
.zip 1.0 8,31Kb 2 Скачать

Специальные предложения

Комментарии
В избранное Подписаться на ответы Сортировка: Древо развёрнутое
Свернуть все
1. Lem0n 214 08.02.19 16:16 Сейчас в теме
"Еще больший интерес предоставляет возможность запускать «только фоновые задания» на рабочем сервере кластера без сеансов пользователей. Таким образом можно высоконагруженные задачи (код) вынести на отдельный машины. При чем можно одно фоновое задание «закрытия месяца» через «Значение дополнительного параметра» запускать на одном компьютере, а фоновое задание «Обновление полнотекстового индекса» на другом.Уточнение происходит через указание «Значение дополнительного параметра». Например если указать BackgroundJob.CommonModule в качестве значения, то можно ограничить работу рабочего сервера в кластере только фоновыми заданиями с любым содержимым. Значение BackgroundJob.CommonModule.<Имя модуля>.<Имя метода> — укажет конкретный код"
2. ixilimuse 176 11.02.19 06:16 Сейчас в теме
(1) Безусловно кластеризация 1С крутая штука, но попробую влить ложку дегтя. Если где-то буду не прав - исправьте меня.

1. Тот самый сервер, который сейчас я использую для того, что описано в посте, мы заказывали как раз для кластеризации. Была такая идея фикс улучшить производительность и отказоустойчивость. На деле, мы не ощутили значимого прироста в производительности, а вот в стабильности даже потеряли. В чем это проявилось? Наша база (конфигурация) обновляется минимум 1 раз в сутки, а бывает что 2-3 раза в сутки. (Вынуждены подстраиваться под бизнес очень динамично) Так вот, очень часто возникал баг, после обновления конфигурации, 1С переставала запускаться. Для фикса приходилось останавливать сервер приложений на обоих серверах, перезапускать SQL серверную службу, и лишь после этого все оживало. Это было накладно по времени, особенно в тех случаях, когда обновление необходимо сделать в рабочее время, и тут возникает такой глюк. В результате от кластеризации отказались.
(Тут конечно есть такой фактор как версия... Возможно в последней версии это исправлено, но мы, увы, ради стабильности не прыгаем на каждый новый релиз платформы во избежании испытывать на себе все новые баги. Ибо нас за это не похвалят. Поэтому, на текущий момент положение дел такое, что мы не используем кластеризацию по вполне объяснимым причинам.)
2. Помимо пункта 1, все таки, кластеризация - подразумевает распределение процессорной нагрузки и нагрузки по ОЗУ. Но при этом по прежнему БД остается тонким местом. И в моем примере, мой запрос вытягивает приблизительно от 3 до 5 млн записей (на текущий момент, но это количество неуклонно растет с каждым месяцем), затем часть этих данных распределяется по регистрам. Это хорошо если это все запущено ночью и лишь 2-3 человека в этот момент могут сидеть и получать тоже довольно увесистые отчеты. Но допустим бывают ситуации когда все же необходимо эту "мега обработку" запустить днем. Днем в базе работают все филиалы со всех городов. Все они дружно заключают договора, получают отчеты, тем временем из разных источников интеграционными методами в базу льются тысячи договоров и заключаются (проводятся) автоматически, что в общей массе создает не слабую очередь, периодические блокировки. Засим, выполнять мою "мега обработку", куда более приятно и спокойно, на отдельной базе данных, где будет работать только моя обработка.

p.s. У нас еще есть одна идея фикс, это сделать зеркалирование БД, и для отчетов использовать зеркало, а запись вести в основную базу (диск). Но тут тоже море подводных граблей, и как пишут опытные в этом деле люди, жизнь зеркалированной БД для 1С длится ровно до первой реструктуризации БД. И поэтому для нас это вообще не подходит) Слишком часто придется сталкиваться с лишними проблемами. Поэтому сложившихся условиях, то что описано в посте, очень помогает и разгружает наше решение. Но разумеется это не панацея и не в любых решениях будет оправдано.
3. A_Max 18 11.02.19 14:21 Сейчас в теме
(2)
p.s. У нас еще есть одна идея фикс, это сделать зеркалирование БД, и для отчетов использовать зеркало

Скоро это будет уже в типовых механизмах, т.ч. эксперименты можно отложить. 1С озвучила эту фичу ещё весной прошлого года на партнёрской конференции.
ixilimuse; +1 Ответить
4. ixilimuse 176 11.02.19 15:07 Сейчас в теме
(3) Благодарю за наводку, будем ожидать с трепетом и надеждами =) Экспериментировать даже и не пытались, так как предварительная разведка показала, что шансов на успех маловато)
5. DonAlPatino 151 12.02.19 15:09 Сейчас в теме
(3)Учитывая, что весна этого года уже на носу, то "обещанного три года ждут?" :-)
6. DonAlPatino 151 12.02.19 15:09 Сейчас в теме
(1)Вроде это только в корпоративной версии работает?
Оставьте свое сообщение

См. также

Безопасная работа с транзакциями во встроенном языке Промо

Практика программирования v8 1cv8.cf Абонемент ($m)

Разбираемся с опасностями использования транзакций во встроенном языке 1С. Познаем ошибку "В данной транзакции уже происходили ошибки". Учимся защищаться от них.

1 стартмани

25.03.2019    37313    tormozit    54    

Как сдать экзамен 1С:Специалист по платформе?

Решение задач на 1С:Специалист v8 Россия Абонемент ($m)

Не пора ли получить сертификат 1С:Специалист по платформе? Для этого ...

1 стартмани

18.01.2021    3547    vasilievil    7    

Cбор и анализ ошибок при помощи Sentry, или как упростить жизнь себе и пользователям

Практика программирования Интеграция v8 Абонемент ($m)

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

1 стартмани

09.10.2020    3402    hexhoc    12    

Программная корректировка при выводе отчета СКД

Практика программирования v8 v8::СКД 1cv8.cf Абонемент ($m)

Большинство отчетов на СКД, требующих программной корректировки, реализуются с помощью программной настройки СКД или обработки табличного документа уже после вывода отчета. Но во многих случаях более оптимально будет выполнять программную корректировку в процессе вывода отчета. Для этого существуют программные объекты, о которых я расскажу в данной статье.

1 стартмани

08.10.2020    4364    dabu-dabu    10    

План подготовки к аттестации на 1С: Специалиста по платформе (+ Ссылки на материалы) Промо

Решение задач на 1С:Специалист v8 Россия Абонемент ($m)

Хочу поделиться собственным планом подготовки к аттестации на 1С: Специалист по платформе 8.3 со ссылками на материалы (и указанием стоимости).

1 стартмани

23.12.2017    23648    tmn72.1C    39    

Библиотека программного изменения формы (УФ)

Инструментарий разработчика Работа с интерфейсом Универсальные функции v8 1cv8.cf Абонемент ($m)

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

1 стартмани

07.08.2020    4887    BuriyLesha    17    

Работа с хранилищем конфигурации из режима 1С: Предприятие минуя конфигуратор

Хранилище v8 1cv8.cf Абонемент ($m)

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

3 стартмани

11.06.2020    6538    MaxxG    19    

История данных и БСП

БСП (Библиотека стандартных подсистем) v8 1cv8.cf Абонемент ($m)

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

1 стартмани

09.06.2020    4040    zeegin    17    

Как выполнить отчет на СКД через COM и получить данные отчета? Промо

Практика программирования v8 УПП1 Россия Абонемент ($m)

Для чего это нужно. Например, нужно в одной базе получить какой-либо показатель из другой базы. Этот показатель вычисляется в каком-либо сложном отчете, который написан на СКД. Можно, конечно, "скопипастить" текст запроса из другой базы, немного подправить его и выполнять в том же COM подключении. Но с этим теряется гибкость: если отчет изменился, то нужно помнить о том, что где-то есть его "немного модифицированная" копия. В статье будет рассмотрен пример получения данных из базы ЗУП.

2 стартмани

08.05.2018    27421    wowik    3    

Выбираем российского провайдера для интеграции с WhatsApp

WEB Интеграция v8 Россия Абонемент ($m)

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

1 стартмани

19.05.2020    4877    ripreal1    5    

Telegram bot API - разбор документации с примерами

WEB v8 Абонемент ($m)

Перевод документации на язык 1С.

1 стартмани

06.04.2020    44682    leongl    49    

Как нарисовать граф на 1С Промо

Практика программирования v8 Абонемент ($m)

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

1 стартмани

09.08.2013    71126    ildarovich    117    

Методика обновления формы объекта данных при изменении объекта

Практика программирования v8 v8::УФ 1cv8.cf Абонемент ($m)

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

1 стартмани

09.03.2020    10033    tormozit    14    

Отправка уведомлений с помощью командной строки, Оповещения с сервера на клиент с помощью командной строки

Практика программирования v8 1cv8.cf Россия Абонемент ($m)

Отправка уведомлений с помощью команды командной строки msg. Оповестить пользователей из серверного модуля или регламентного задания, с помощью командной строки msg.

1 стартмани

05.03.2020    6228    user5300    3    

Конвертация данных 2. Использование исходящих и входящих данных. Свойство "Получить из входящих данных"

Обмен данными 1С Перенос данных из 1C8 в 1C8 v8 КД Абонемент ($m)

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

17.02.2020    17834    Drivingblind    31    

Простой способ индексирования интервалов Промо

Практика программирования v8 Абонемент ($m)

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

1 стартмани

28.09.2016    40147    ildarovich    22    

Вывод сообщений в HTML поле средствами 1С

Практика программирования v8 v8::УФ Абонемент ($m)

Пример использования вывода большого количества сообщений в поле HTML. С возможностью открывать ссылочные объекты и создавать новые объекты передавая параметры прямо из HTML поля. Протестировано на релизах 8.3.12 и 8.3.15+

2 стартмани

31.01.2020    6356    burni4    16    

Краткое руководство по внесению изменений в конфигурацию

Практика программирования v8 1cv8.cf Абонемент ($m)

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

1 стартмани

13.01.2020    18554    sapervodichka    41    

Разбираемся с web-kit в 1С, на примере интеграции TinyMCE в управляемую форму в УТ 11.4. Допиливаем обмен с сайтом в УТ 11.4

Обмен данными 1С Интеграция Адаптация типовых решений v8 v8::УФ УТ11 Абонемент ($m)

Многие уже знают, что в релизе платформы 8.3.14.1565, браузер Internet Explorer был заменен на Web-Kit, это на самом деле большой шаг вперед, но я уверен, многим, как и мне, пока не совсем понятно, что к чему. Возник опыт использования web-kit в 1С, вызова JS из 1С и вызова 1С из JS. Давайте вместе попробуем понять, чем одно отличается от другого, и заодно сделаем, что-нибудь полезное. Да и наверняка многим придется переписывать свои подобные поделки после обновления на новую платформу, так что надеюсь мой опыт окажется полезным.

2 стартмани

08.12.2019    8738    Бэнни    25    

Бесплатная проверка контрагентов в ФНС (общий модуль с алгоритмом). На примере выводим статус в список справочника контрагентов Промо

Практика программирования v8 1cv8.cf Абонемент ($m)

Если вам интересно проверить контрагенте в ФНС, вам поможет данная публикация. Весь алгоритм работы строится на основе данных, полученных с сервиса http://npchk.nalog.ru совершенно бесплатно.

1 стартмани

01.02.2018    35642    rpgshnik    49    

Массовое изменение режима поддержки объектов конфигурации

Структура метаданных v8 1cv8.cf Абонемент ($m)

Что делать, если при сравнении/объединении конфигураций нужно изменить режим поддержки для большого количества объектов? Штатного механизма для выполнения подобной задачи в Платформе нет. Изменять режим для всей конфигурации? Описывается способ, позволяющий выполнить изменение режима только для нужных объектов.

05.11.2019    4295    VKislitsin    6    

"Живые" картинки со Snap.SVG

Практика программирования WEB Работа с интерфейсом v8 Абонемент ($m)

В статье рассмотрен пример использования http-сервисов для визуализации данных

1 стартмани

24.10.2019    14006    blackhole321    7    

Интеграция 1С с сайтом (магазином) WordPress (WooCommerce) с помощью Rest API сайта. Часть 1. Авторизация

WEB v8 Абонемент ($m)

Интеграция 1С с сайтом (магазином) WordPress (WooCommerce) с помощью функционала Rest API предоставляемого платформой (CMS) WordPress (WooCommerce). Без дополнительных приложений на PHP/вставьте сюда любой другой язык программирования/.

1 стартмани

12.10.2019    32193    osivv    33    

БСП: Дополнительная обработка (Регламенты), примеры от простого к сложному Промо

Практика программирования БСП (Библиотека стандартных подсистем) v8 1cv8.cf Абонемент ($m)

Очень много попадается странных решений, которые можно решить через БСП:Дополнительные отчеты и обработки. Я бы вообще БСП из-за этой подсистемы переименовал в «Большое Спасибо Программистам». Поработаем с подсистемой в части написания регламентных заданий.

1 стартмани

10.05.2018    46562    dsdred    43    

RLS - дубли условий в запросах к СУБД

Практика программирования Роли и права v8 v8::Права 1cv8.cf Абонемент ($m)

"Подводные камни", возникающие при бездумном копировании ролей с ограничениями RLS, как это отражается на производительности, разбор примера и инструмент для анализа.

1 стартмани

07.10.2019    8771    geron4    4    

Вебхук. Путь Телеграма

Внешние источники данных Интеграция v8 Абонемент ($m)

Долгое (на самом деле нет) и нелегкое путешествие телеграма к неведомым (из за РКН) конфигурациям 1С. Памятка себе.

1 стартмани

03.10.2019    19346    platonov.e    26    

Многопоточная обработка данных на примере перепроведения документов

Обработка документов Практика программирования v8 ERP2 УТ11 КА2 Абонемент ($m)

Дальнейшее развитие темы фоновой обработки данных - проведение документов в потоках. Настройка параметров и запуск основного процесса (менеджера потоков). Разбивка документов для проведения на не связанные друг с другом наборы и запуск дополнительных фоновых заданий для отдельных потоков. Отслеживание выполнения каждого потока в родительском сеансе.

1 стартмани

17.09.2019    9882    ids79    46    

Некоторая работа с данными через COM Промо

Практика программирования v8 Абонемент ($m)

В статье приведены примеры работы с Платформой 8.X через COM (точнее, через объект COMConnector). Примеры кода были использованы при реализации прикладных задач в процессе трудовой деятельности.

2 стартмани

05.12.2012    58219    wowik    32    

Описание формата внутреннего представления данных 1С в контексте обмена данными

Практика программирования Внешние источники данных v8 v8::УФ 1cv8.cf Абонемент ($m)

Фирма 1С не рекомендует использовать внутреннее представление данных для любых целей, которые отличны от обмена с 1С:Предприятием 7.7. Но сама возможность заглянуть на "внутреннюю кухню" платформы с помощью функций ЗначениеВСтрокуВнутр(), ЗначениеВФайл(), ЗначениеИзСтрокиВнутр() и ЗначениеИзФайла(), дала возможность сообществу программистов 1С разработать новые приемы разработки и анализа. Так, именно на использовании внутреннего представления был построен алгоритм "быстрого массива", который позволяет практически мгновенно создать массив в памяти на основании строки с разделителями. С помощью разбора внутреннего представления можно "на лету" программным кодом выполнить анализ обычной формы и даже сделать редактор графической схемы. Во внутреннем формате сохраняют свои данные между сеансами различные популярные внешние обработки. А еще это возможность сделать быстрый обмен с внешними системами.

1 стартмани

06.09.2019    20129    Dementor    30    

1С и PowerShell - обновление из хранилища

Администрирование данных 1С Инструментарий разработчика v8 Абонемент ($m)

Пример скрипта, упрощающего работу.

1 стартмани

29.08.2019    9107    Jokemas    31    

Удобный просмотр результата запроса с большим количеством временных таблиц

Практика программирования v8 Абонемент ($m)

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

1 стартмани

27.08.2019    11312    ids79    22    

Работа со схемой запроса Промо

Инструментарий разработчика Практика программирования v8 v8::Запросы Абонемент ($m)

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

1 стартмани

24.04.2018    45560    kalyaka    35    

Обмен большими данными между клиентом и сервером

Внешние источники данных v8 Абонемент ($m)

В статье рассматривается вопрос передачи больших объемов данных, превышающих теоретический лимит сеансовых данных (4Гб за вызов) (они же временное хранилище) как с клиента на сервер, так и в обратном направлении.

1 стартмани

27.08.2019    14170    logos    32    

Запуск фонового задания во внешней обработке. Отключение предупреждений защиты от опасных действий в фоновом задании

Практика программирования v8 1cv8.cf Абонемент ($m)

Как запустить фоновое задание из модуля внешней обработки используя БСП. Как отключить безопасный режим и сообщения защиты от опасных действий независимо от профиля безопасности пользователя в фоновом задании во внешней обработке.

2 стартмани

24.08.2019    12181    BenGunn    22    

Изменяющееся контекстное меню в 1С 8.3

Практика программирования Работа с интерфейсом Разработка v8 v8::УФ Абонемент ($m)

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

1 стартмани

06.08.2019    17278    signum2009    16    

Многопоточность. Универсальный «Менеджер потоков» (фреймворк) с отслеживанием зависимости объектов Промо

Практика программирования Математика и алгоритмы Универсальные функции Производительность и оптимизация (HighLoad) v8 1cv8.cf Россия Абонемент ($m)

Восстановление партий, расчет зарплаты, пакетное формирование документов или отчетов - теперь все это стало доступнее. * Есть желание повысить скорость работы медленных алгоритмов! Но... * Нет времени думать о реализации многопоточности? * о запуске и остановке потоков? * о поддержании потоков в рабочем состоянии? * о передаче данных в потоки и как получить ответ из потока? * об организации последовательности? Тогда ЭТО - то что надо!!!

26.05.2017    49702    DarkAn    86    

Использование HTTP-сервиса для создания "фронтенда" HTML/CSS/jQuery с кэшированием

WEB v8 1cv8.cf Абонемент ($m)

В статье описан способ создания "фронтенда" на HTML/CSS/jQuery и скрипт кеширования AJAX запросов на PHP.

1 стартмани

06.08.2019    14178    Sedaiko    25    

Менеджер потоков: реализация "любой" задачи в потоках

Производительность и оптимизация (HighLoad) Инструментарий разработчика v8 Абонемент ($m)

Менеджер потоков – один их новых инструментов, который упрощает работу разработчиков. Насколько легко с ним, на конференции Infostart Event 2018 Education показал начальник отдела автоматизации 1С Иван Филимонов компании «Трансстроймеханизация».

01.08.2019    10051    DarkAn    6    

Процедура ПриКомпоновкеРезультата

Практика программирования v8 1cv8.cf Абонемент ($m)

Коллекция кода

1 стартмани

26.07.2019    43684    vasilev2015    64    

Агрегатное суммирование строк в запросе – сложно, но не невозможно Промо

Математика и алгоритмы v8 Абонемент ($m)

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

1 стартмани

09.09.2013    80027    ildarovich    54    

10 способов получить модуль числа (а может, и больше)

Практика программирования Разработка v8 1cv8.cf Абонемент ($m)

Пишем функцию вычисления модуля числа. Сколько способов существует? Давайте посчитаем!

1 стартмани

11.07.2019    9559    sam441    29    

Ловец дедлоков СУБД

Производительность и оптимизация (HighLoad) Практика программирования Разработка v8 Россия Абонемент ($m)

Анализ простейшего дедлока СУБД в рабочей базе с использованием ЦУП (центра управления производительностью) и profiler MS SQL (Microsoft SQL Server). Эта статья будет полезна людям, изучающим вопросы оптимизации работы 1С, или тем, у кого возникают дедлоки в рабочей базе. UPD 09.07.2019 добавлено воспроизведение блокировки в случае установки управляемой блокировки перед чтением набора записей регистра сведений. UPD 10.07.2019 добавлена тестовая база с примером.

1 стартмани

08.07.2019    11514    azazana    79    

Мониторинг производительности и искусственный интеллект

Производительность и оптимизация (HighLoad) Практика программирования Разработка v8 Абонемент ($m)

Расскажем и покажем практически, как использовать искусственный интеллект на страже мониторинга производительности. У вас появится возможность создать собственного помощника Ларису, которая возьмет на себя вопросы по контролю и диагностике состояния обслуживаемой системы.

1 стартмани

01.07.2019    9597    ivanov660    28    

Новый запрос и новая таблица значений как функции Промо

Практика программирования v8 Абонемент ($m)

Предлагается две простые функции, использование которых уменьшает объем кода в конфигурациях на платформе «1С:Предприятие 8». Эти функции можно добавлять к своему общему модулю, что сделает процесс программирования более эффективным.

1 стартмани

27.11.2012    45520    ildarovich    46    

"Убер на складе": динамический расчет маршрутов с учетом реальных расстояний

Учет ТМЦ Практика программирования Учет ТМЦ v8 УУ Абонемент ($m)

Представляю методику и инструмент для динамического расчета маршрутов отбора на высоконагруженных складах для максимального повышения эффективности склада, ускорения проходимости и, как следствие, экономии денег. Это методика и обработка для интеграции в WMS решения. Тестировалось на 1С 8.3.14.1565.

3 стартмани

24.06.2019    17439    informa1555    17    

1С:Ассемблер. Немного летнего веселья!

Практика программирования Разработка v8 1cv8.cf Абонемент ($m)

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

1 стартмани

21.06.2019    30483    Evil Beaver    134    

Простые примеры сложных отчетов на СКД

Практика программирования v8 v8::СКД 1cv8.cf Абонемент ($m)

Подписи в отчете. Особенности соединения наборов: как соединить несоединяемое. Остатки на дату и обороты по месяцам в одном отчете. Курс валюты на каждую дату без группировок и соединений в запросе. Отчет с произвольными колонками и с произвольной последовательностью. "Неадекватный отчет".

1 стартмани

12.06.2019    31427    Hatson    31    

Уровни, глубина, прародители, циклы и аналоги запросом Промо

Практика программирования v8 1cv8.cf Абонемент ($m)

В продолжение публикации «Транзитивное замыкание запросом» [http://infostart.ru/public/158512/] добавлены другие варианты использования того же приема. Приведены запросы для быстрого определения уровней всех элементов справочника, максимальной глубины справочника, прародителей произвольных элементов справочника, запрос для быстрого определения циклов (на примере справочника спецификаций «1С:Управление производственным предприятием») и определения множеств аналогов номенклатуры (также на примере конфигурации «1С:Управление производственным предприятием»).

1 стартмани

13.11.2012    113987    ildarovich    98    

Работа с графической схемой в объектной модели DOM

Универсальные функции v8 v8::УФ Абонемент ($m)

Пример кода для работы с графической схемой в объектной модели DOM, платформа 8.3.12.

1 стартмани

04.06.2019    8096    botokash    19    

XDTO для чайников

Обмен через XML v8 1cv8.cf Абонемент ($m)

Пример использования XDTO пакетов для выгрузки документа с табличной частью.

1 стартмани

29.05.2019    32328    HAMMER_59    39    

Отладка правил обмена КД2 для подсистемы БСП Обмен данными

Перенос данных из 1C8 в 1C8 v8 1cv8.cf Абонемент ($m)

Уже давно нельзя отлаживать правила обмена при помощи внешних файлов. Попробуем исправить это.

1 стартмани

27.05.2019    12566    fenixnow    8    

Создание внешней печатной формы в формате документа Word

Практика программирования Разработка v8 1cv8.cf Абонемент ($m)

В статье написано, как создать внешнюю печатную форму (для конфигураций с БСП) в формате Word.

1 стартмани

17.05.2019    15991    ВикторП    21