Всем доброго времени суток!

Нужна консультация.

У нас возникла необходимость обрабатывать большие коллекции Entity(изменять значение поля и обновлять) при этом что бы был задействован событийный слой.

Решили данным способом:

var bc = new BlockingCollection<Entity>();
// Наполнение коллекции
.....
Parallel.ForEach(bc, new ParallelOptions {
   MaxDegreeOfParallelism = Environment.ProcessorCount
}, entity => entity.UpdateInDB(false));

Вопрос в следующем будем ли мы ловить локи при использовании UserConnection entity, не создавая нового?

Подобные случаи с локами были на версии 7.8.


 

Нравится

1 комментарий

Алексей, здравствуйте!

Однозначно ответить на вопрос про блокировки нет возможности, они как могут возникать, так и нет. Но работать с данными в несколько потоков не рекомендуется.

Показать все комментарии

Привет.

Как использовать события таблицы Журнала изменений(ЖИ) как начальных событий для запуска БП?



Известно что это идут таблицы которые не имеют своего в Entity ORM, с названием - "Sys[TableName]Log" и специальным атрибутом в метаданных таблицы - "TS.EntitySchema.Kind=TrackChangesInDB;".



Тут два пути как я вижу: 

1. "Как-то" сделать  Entity из уже существующей таблицы ЖИ в БД. Но как? 

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

Нравится

2 комментария

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

Vyacheslav Lipatkin,

не считаю хорошей идеей на уровне триггеров как-то задействовать бизнес слой. Лучше всего наверное создание журнала кастомного как у Campaign.

Показать все комментарии

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

 

Нравится

7 комментариев
var userConnection = Get&lt;UserConnection&gt;("UserConnection");
var esq = new EntitySchemaQuery(userConnection.EntitySchemaManager, &lt;SchemaName&gt;);
esq.AddAllSchemaColumns();
esq.Filters.Add(esq.CreateFilterWithParameters(FilterComparisonType.Equal, &lt;FieldName&gt;, fieldValue));
var enitites = esq.GetEntityCollection(userConnection);
ver entity = enitites.FirtsOrDefault();

Примерно так.

Дамиан, так делать неправильно, в момент GetEntityCollection всё равно будут получены все записи, соответствующие условиям.

Правильнее будет перед этим дописать:

esq.RowCount = 1;

Аналогично и с колонками, оставить только нужные.

Дмитрий, а почему такие крайности, от 20000 к 1? Что именно Вы получаете и что потом с этими данными делаете? Может, подобрать оптимальный размер порции для обработки за раз?

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

Александр, спасибо за уточнение. Нашёл еще один подход.

public Entity GetEntity(UserConnection userConnection)
{
    var entitySchema = userConnection.EntitySchemaManager.GetInstanceByName(name);
    var esq = new EntitySchemaQuery(entitySchema);
    esq.AddAllSchemaColumns();
    return esq.GetEntity(userConnection, primaryColumnValue);
}

 

Тут всё зависит, сколько реально записей отфильтруется по такому условию. Если всегда 0 или 1, то можно и первым, и вторым Вашим способом. А если есть риск получить 100500 результатов, то лучше перестраховаться, ограничив количество в запросе.

Зверев Александр,

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

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


Соответственно он не сможет дальше писать ничего , если при записи лога взял пулом 20 тыс записей разом

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

Зверев Александр,

Спасибо за строчку с лимитом на выборку записей.

esq.RowCount = 1; // 20_000 - максимальный лимит

 

Показать все комментарии

Добрый вечер! Не знаю как такое возможно, чтобы ESQ.GetEntity() возвращал другую строку, при заданном первичном ключе в параметре функции. На скрине видно, что Id который задан и тот который вернул запрос совсем разные, а именно эта запись которую он взял идёт первой в таблице, что я не так делаю или в чём может ошибка? 

Прикрепленные файлы

Нравится

1 комментарий

Здравствуйте.

Мне такую же ошибку не удалось воспроизвести - GetEntity отрабатывает правильно.

Попробуйте явно указать id, вот так: esq.GetEntity(UserConnection, new Guid("e76f59d2-c8ca-4bc1-80fd-0cdf02ab92dc"));

Ещё отловить бы запрос, который уходит в БД, возможно он некорректно формируется.

 

Показать все комментарии

При параллельном сохранении Entity.Save() с использованием параллельных потоков Parallel.ForEach получаю ошибку:

System.Data.SqlClient.SqlException: A trigger returned a resultset and/or was running with SET NOCOUNT OFF while another outstanding result set was active.



Как установить NOCOUNT ON для объекта Entity при сохранении?

Нравится

12 комментариев

Проблема возникает из-за того, что в каком-то триггере в базе данных для таблицы, связанной со схемой Entity не установлен SET NOCOUNT ON.

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

У меня, например, такая проблема воспроизводилась при попытке удалить бизнес-процесс из конфигураци и возникала для объекта Contact. Для этой таблицы в БД есть триггер завязанный на изменение (UPDATE), но в нем не было установлено SET NOCOUNT ON.

Возможно, что у Вас также проблема именно в этом триггере.

Суть ошибки я понимаю. Вопрос в другом имеем ли мы право вносить правки в системные триггер.

 

Добрый день, Игорь.

Это зависит от того, о каком триггере речь. Генерировался ли он автоматически ядром, или создан специально для этой таблицы разработчиками вручную.

В первом случае менять нежелательно, во втором — нужно будет быть осторожным при установке обновлений, если в пакете будет SQL-запрос обновления стандартного триггера.

Узнать, что это за триггер, можно, наблюдая запросы в профайлере.

Мотков Илья,

Илья, Добрый день.

проблема в базовых триггерах таблиц Contact и Account

Это стандартная логика, связанная с поиском дублей и полем-изображением. Из трёх триггеров на событии вставки и изменения работает только один, обновляющий изображение.

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

Коновалов Игорь пишет:

Суть ошибки я понимаю. Вопрос в другом имеем ли мы право вносить правки в системные триггер.

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

Если это ошибка, тогда хотелось бы получить от поддержки Terrasoft решение.

Решением будет удаление некорректных сторонних доработок, которые ломают стандартную логику триггеров раздела. В конфигурации вообще использование Parallel.ForEach минимально, только один раз при работе с сервисом рассылок.

Илья, вопрос к триггерам таблицы

- Контрагент:

TRILqybnWrGVlAZ250EZZORaS6GAIU

TRILqybnWrGVlAZ250EZZORaS6GJAD

TRAccountID

- Контакт:

TRContactID

TRILE9Mk5tdkf4ii5Xu52IJW7JlLAD



Все они являются базовыми и все содержат строку

SET NOCOUNT ON

Игорь, это стандартные триггеры. Без крайней необходимости их менять не стоит. Адаптируйте свой код, чтобы он не вызывал сбоев.

Мотков Илья,

Я обрабатываю большой объем данных. Без применения

Parallel.ForEach данная выгрузка займет несколько дней.

Базовые триггеры прерывают данный процесс.



 

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

Показать все комментарии

Здравствуйте.

Место событий Entity, событие Saved, для которого был организован событийный подпроцесс. Последний со скриптом и сообщением которое запускает этот скрипт по событию. 

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

Нравится

5 комментариев
Лучший ответ

Добавьте еще подписку на событие, перед сохранением.

Так же делаете подписку, скрипт.

В параметрах создайте нужные переменные нужных типов.

После чего в самом скрипте "перед сохранением", делайте так:

ContactOld = Entity.GetTypedOldColumnValue<Guid>("Contact");

Затем, в вашем скрипте "После сохранения" делайте сверку

if (ContactOld != Entity.GetTypedColumnValue<Guid>("Contact") { // }

Добавьте еще подписку на событие, перед сохранением.

Так же делаете подписку, скрипт.

В параметрах создайте нужные переменные нужных типов.

После чего в самом скрипте "перед сохранением", делайте так:

ContactOld = Entity.GetTypedOldColumnValue<Guid>("Contact");

Затем, в вашем скрипте "После сохранения" делайте сверку

if (ContactOld != Entity.GetTypedColumnValue<Guid>("Contact") { // }

Это можно сделать только в событийном процессе объекте перед сохранением записи

Entity.GetColumnOldValue("Name")

или

Entity.GetTypedOldColumnValue<String>("Name");

 

Добрый день!

На вход элемента скрипта обработки сохранения подается переменная ProcessExecutingContext context (https://monosnap.com/file/TP10NeOGRBkG4zXPFKxrVZM8Tvj4I6).

В ней есть public object ThrowEventArgs { get; set; }, которая приводится к объекту EntityAfterEventArgs. А в этом объекте уже есть массив измененных колонок: public EntityColumnValueCollection ModifiedColumnValues { get; set; }

Еще проще проверить что столбец изменен

var flag = Entity.GetChangedColumnValues().Any(col => col.Name == "Date");

Всем спасибо!

Показать все комментарии

 В чем может быть проблема?

Процесс валится с ошибкой https://yadi.sk/i/OEAV92qdZHfeIA

Вот сам процесс:

Изображение удалено.

Параметр:

Изображение удалено.

 

Нравится

8 комментариев

А если просто:

AccountListParameter = query.GetEntityCollection(userConnection);

 

Rochefort Trappistes,

Процесс так даже не опубликуется

эт почему не опубликуется? параметры это поля класса (процесс это класс), а блок c# это метод, который имеет доступ к полям класса, как на чтение так и на запись.

снимите галочку "Интерпритируемый" в блоке с c# кодом (в расширенных настройках)

 

Rochefort Trappistes,

нет такой галочки

три точки - расширенный режим, как-то так

Rochefort Trappistes,

значит убрали её. какая ошибка при компиляции, если сетить значение в параметр через равно, как я писал выше?

 Rochefort Trappistes, см тут:

Начиная с версии 7.12.3 все создаваемые бизнес-процессы в bpm’online являются интерпретируемыми. Для обращения к значениям параметра процесса следует использовать методы get и set.

Миннекаев Айдар, дело в том, что в версии 7.13 в системе отключена возможность создания компилируемых бизнес-процессов, что делает невозможным работу с параметром EntityCollection. Параметр такого типа оставлен в системе для совместимости со старыми бизнес-процессами, которые работают в компилируемом режиме, а для новых — возможность использования компилируемых скрипт-тасков в БП отключена.

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

Показать все комментарии

Коллеги всем доброго времени суток. Подскажите, кто нибудь работал с событийным слоем Entity.

Написал класс обработчик в качестве примера для обучения скомпилировал приложение упал сайт.

Вот пример класса обработчика:

 

using System;
using System.Web;
using Terrasoft.Core;
using Terrasoft.Core.Entities;
using Terrasoft.Core.Entities.Events;
 
 
namespace Terrasoft.Configuration
{
    [EntityEventListener(SchemaName = "Order")]
    public class OrderEntityEventListener : BaseEntityEventListener
    {
 
        private UserConnection _userConnection;
 
        private LogService _logService;
        public UserConnection UserConnection
        {
            get
            {
                if (_userConnection != null)
                {
                    return _userConnection;
                }
                _userConnection = HttpContext.Current.Session["UserConnection"] as UserConnection;
                if (_userConnection != null)
                {
                    return _userConnection;
                }
                return _userConnection;
            }
 
            set { _userConnection = value; }
        }        
 
        public LogService LogService { get => _logService; set => _logService = value; }
 
        public OrderEntityEventListener()
        {
            LogService = new LogService(UserConnection);
        }
 
        public override void OnInserting(object sender, EntityBeforeEventArgs e)
        {
            try
            {
                base.OnInserting(sender, e);
 
                var systemUserName = UserConnection.CurrentUser.ContactName;
                var systemUserId = UserConnection.CurrentUser.ContactId;
                LogService.RecInfo($"LOG:[OnInserting]:systemUserId: {systemUserId}; systemUserName:{systemUserName}");
            }
            catch (Exception exception)
            {
                LogService.RecInfo($"LOG:[OnInserting]:Exception: {exception.Message}; {exception.InnerException}");
            }
 
        }
    }
}

Подскажите, что тут не так? Если кто работал, скинте пожалуйста примеры.

Заранее благодарен

Нравится

1 комментарий
Лучший ответ

Добрый день, Алексей!

UserConnection необходимо получать из sender

Пример:

public override void OnSaved(object sender, EntityAfterEventArgs e) {

            base.OnSaved(sender, e);

            var entity = (Entity) sender;

            var userConnection = entity.UserConnection;

        }

Ошибка заключается в том, что слушатели изменений сущностей, работают не только при изменении значений через карточку (в данном случае есть HttpContext) но и в фоновом режиме (процессы, планировщик - без HttpContext).

Добрый день, Алексей!

UserConnection необходимо получать из sender

Пример:

public override void OnSaved(object sender, EntityAfterEventArgs e) {

            base.OnSaved(sender, e);

            var entity = (Entity) sender;

            var userConnection = entity.UserConnection;

        }

Ошибка заключается в том, что слушатели изменений сущностей, работают не только при изменении значений через карточку (в данном случае есть HttpContext) но и в фоновом режиме (процессы, планировщик - без HttpContext).

Показать все комментарии

Здравствуйте!
Возникла проблема при копировании записей.
Использую следующий конструктор:
var newEntity = new Entity (entity);
Запись корректно создается, однако события объекта не срабатывают, в чем может быть проблема?

Полный код приведен ниже:

private void copyRouteDetail(Guid TemplateId, Guid TransRequestId) {
        EntitySchema schema = UserConnection.EntitySchemaManager.GetInstanceByName("SxRouteDetailTransRequest");
        EntitySchemaQuery esq = new EntitySchemaQuery(schema);
        esq.AddAllSchemaColumns();
        esq.UseAdminRights = false;
        esq.Filters.Add(esq.CreateFilterWithParameters(FilterComparisonType.Equal,
                                                        "SxRouteOptions", TemplateId));
        EntityCollection entities = esq.GetEntityCollection(UserConnection);
         foreach (Entity entity in entities)
        {
                var newEntity = new Entity (entity);
                newEntity.SetColumnValue("SxRouteOptionsId", null);
                newEntity.SetColumnValue("SxTransRequestId", TransRequestId);
                newEntity.UseAdminRights = false;
                newEntity.Save(false);
        }
}

Нравится

8 комментариев

Здравствуйте, данный вопрос будет решён в рамках Вашего обращения №0300267

"Пащенко Александр Сергеевич" написал:Запись корректно создается, однако события объекта не срабатывают, в чем может быть проблема?

Добрый день Александр!!!

я бы рекомендовал внимательно проанализировать все бизнес-процессы выполняемые в сохраняемом Entity, внимательно проанализировать сначало Глазами, а потом внимательно проанализировать в Дебагере, что происходит в вашей базе данных при сохранение, и найти место где возникает исключение, при котором запись создается, но не выполняются события.

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

Михаил, все процессы процессы построены корректно и работают стабильно.
Процитирую ответ техподдержки:
"Данное поведение проявляется по причине того что, при использования данного конструктора класса Entity свойства создаваемого объекта отвечающие за вызов событий проставляются как false. К сожалению, изменить данные свойства после создания объекта нельзя.

Данную задачу можно решить следующим образом:

var userConnection = context.UserConnection;
var schema = UserConnection.EntitySchemaManager.GetInstanceByName("Account");
var newEntity = schema.CreateEntity(UserConnection);
newEntity.SetDefColumnValues();
//код установки значений колонок объекта
newEntity.Save();

Упростить задачу копирования значений колонок из одного объекта в другой можно используя метод public IEnumerable GetColumnValueNames(), который возвращает имена колонок объекта. Затем перебирая в foreach результат данного метода скопировать значения колонок."

При отладке, единственная разница, которую нашел - при использовании конструктора
var newEntity = new Entity(oldEntity)
у newEntity.Process=null,
у oleEntity.Process = не nulll, а ссылка на метод.

Михаил, все процессы процессы построены корректно и работают стабильно.
Процитирую ответ техподдержки:
"Данное поведение проявляется по причине того что, при использования данного конструктора класса Entity свойства создаваемого объекта отвечающие за вызов событий проставляются как false. К сожалению, изменить данные свойства после создания объекта нельзя.

Данную задачу можно решить следующим образом:

var userConnection = context.UserConnection;
var schema = UserConnection.EntitySchemaManager.GetInstanceByName("Account");
var newEntity = schema.CreateEntity(UserConnection);
newEntity.SetDefColumnValues();
//код установки значений колонок объекта
newEntity.Save();

Упростить задачу копирования значений колонок из одного объекта в другой можно используя метод public IEnumerable GetColumnValueNames(), который возвращает имена колонок объекта. Затем перебирая в foreach результат данного метода скопировать значения колонок."

При отладке, единственная разница, которую нашел - при использовании конструктора
var newEntity = new Entity(oldEntity)
у newEntity.Process=null,
у oleEntity.Process = не nulll, а ссылка на метод.

"Пащенко Александр Сергеевич" написал:Данную задачу можно решить следующим образом:

var userConnection = context.UserConnection;
var schema = UserConnection.EntitySchemaManager.GetInstanceByName("Account");
var newEntity = schema.CreateEntity(UserConnection);
newEntity.SetDefColumnValues();
//код установки значений колонок объекта
newEntity.Save();

Добрый день Александр!!!

так техническая поддержка вам предложила верный вариант решения вашей проблемы. Чем он вам не нравиться? что не устраивает?

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

Спасибо за отклик!

Александр день добрый!!!

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

Показать все комментарии

В процессе, в рамках одного "Задания-Сценария" идет цикл по строкам (продуктам).
По логике, в этом цикле идет запрос в другую сущность, и если ничего не возвращает, делает новую строку в той сущности.
Если находит, то делает апдейт найденной строки.

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

Нравится

1 комментарий

Ложная тревога, причина была в другом. save работает как надо

Показать все комментарии