При добавлении пользователя через интерфейс идёт обращение к веб-сервису AdministrationService и его методу UpdateOrCreateUser. В схеме AdministrationServiceUsers виден его код:
То есть видно, что вставка идёт через механизмы ESQ (значит, события отработают), но в объект VwSysAdminUnit, а не SysAdminUnit. Если посмотреть в коде встроенного БП на VwSysAdminUnit в пакетах Base, Translation и WebitelCollaborations, там как раз успешно добавляют свои обработчики на вставку, сохранение и другие события:
То есть нужно будет либо в Custom сделать своё переопределение схемы со встроенным БП, либо сделать отдельный БП, запускающийся по сигналу вставки туда:
В любом случае нужно учитывать, что в этой view видны и пользователи, и роли.
Доброго времени суток, коллеги. При интеграции BPM с внешней БД возникла необходимость вставлять даннные из карточки BPM в таблицу на связанном сервере (Firebird 2.0). Для работы с таблицами связанного сервера содали представления. С селектами из этих представлений никаких проблем не было. Но вот с инсертами и апдейтами возникла проблемка - после инсерта(апдейта или делита) транзакции подвисали минут на 5 и выполнить очередной запрос к этому представлению было невозможно. Нашли выход из этой ситуевины:
1. Создали на связанном сервере хранимую процедуру (пишет данные в таблицу и возвращает одно значение).
2. На sql сервере в представлении создали триггер который срабатывает на инсерт, пинает хранимку на связанном сервере и передает параметры в эту хранимку.
3. В карточку BPM добавили кнопку и повесили на нее скрипт которые инсертит данные из полей карточки в представление.
После данные манипуляций все работает отлично, данные инсертятся, транзакции не подвисают.
Но вот возник вопросик. Как я написал в первом пункте, хранимка возвращает одно значение и мы не можем приложить ума как его перехватить чтобы вернуть это значение в текстовое поле в карточку BPM. Ниже код хранимки, триггера и скрипта инсерта.
Код хранимки.
BEGIN
INSERT INTO NAPRAVLENIE(NAPRAVLENIE.NAPRAVLENIE_NUM,NAPRAVLENIE.USLUGA_1) VALUES (:NUM,:U1);
NEW_OUTPARAM=gen_id(BP_GEN,1);
SUSPEND;
END
Код триггера.
USE[BPMonline]
GO /****** Object: Trigger [dbo].[IOI_napr] Script Date: 03/11/2015 17:56:44 ******/ SET ANSI_NULLS ON
GO SET QUOTED_IDENTIFIER ON
GO ALTERTRIGGER[dbo].[IOI_napr]ON[dbo].[napr]
INSTEAD OF INSERT AS
BEGIN
declare @U1 varchar(250)
declare @NUM varchar (250)
declare @NAP_NUM varchar (250) SET @U1=(SELECT i.USLUGA_1 FROM inserted i) SET @NUM=(SELECT i.NAPRAVLENIE_NUM FROM inserted i)
execute ('select * from BP_nap_ins('''+@NUM+''','''+@U1+''')') at DMS
END;
Код скрипта инсерта кнопки.
var U1=Page.TextEdit5.Value;// для теста передаем только 1 значение
var insert =new Insert(UserConnection).Into("napr").Set("USLUGA_1", Column.Parameter(U1)).Execute(); returntrue;
если вы полнить инсерт из sql менеджера то в результах я вижу, что хранимка вернула значение.
каким оброзом можно запихнуть возвращаемое значение в переменную и использовать ее дальше в BPM ?
Спасибо что дочитали :)
Спасибо за ответ, пошли немного другим путем. Запихнули текст триггера еще в одну хранимку, и через OPENQUERY пнули хранимку на связанном сервере. Таким макаром удалось вернуть значение в переменную. Сейчас бытаюсь разобраться как вызвать хранимку из формы.
может кому не сложно написать коды вызова хранимки:
в хранимку передаем значение из поля TextEdit5.
вернуть значение нужно в TextEdit14
USE BPMonline;
IF OBJECT_ID ('dbo.BPM_to_DMS', 'P') IS NOT NULL
DROP PROCEDURE dbo.BPM_to_DMS;
GO
CREATE PROCEDURE dbo.BPM_to_DMS
@U1 varchar(250),
@NAP_NUM varchar (250) OUTPUT
--@U2 varchar (250)
AS
SET NOCOUNT ON;
set @NAP_NUM =(select * from OPENQUERY (dms,'select * from BP_nap_ins(''+@U1+'')'))
GO
итак, удалось запустить хранимку, передать ей значение в U1, но никак не могу разобраться как вернуть из нее значение в TextEdit14. необходимое значение возвращается в NAP_NUM.
код вызова хранимки:
var U1=Page.TextEdit5.Value;//var NAP_NUM;
var dataValueTypeManager =(DataValueTypeManager)UserConnection.AppManagerProvider.GetManager("DataValueTypeManager");
var storedProcedure =new StoredProcedure(UserConnection, "BPM_to_DMS");
storedProcedure.WithParameter(U1);
storedProcedure.WithOutputParameter("NAP_NUM", dataValueTypeManager.GetInstanceByName("Text"));using(DBExecutor dbExecutor = UserConnection.EnsureDBConnection()){
dbExecutor.StartTransaction(System.Data.IsolationLevel.ReadUncommitted);
storedProcedure.Execute(dbExecutor);
dbExecutor.CommitTransaction();}//Page.TextEdit14.SetValue(NAP_NUM);returntrue;
можно хотябы пример похожий (в примере с поиском дублей ничего не понятно)
PS: когда все допилю обещаю написать полный мануал по вызову хранимой процедуры на связанном сервере firebird 2.0
Добрый день. А почему Вы не используете тот пример, что я вам скинул? Им тоже можна "дергать" хранимые процедуры и получать значения
извиняюсь, но я не понял что вы в своем примере делаете и как его можно применить к моей хранимке, если вам не слложно, не могли бы вы написать свой пример с коментами или подогнать егео под мою хранимку?
там все очень просто. UserConnection - Переменная которая хранит в себе параметры подключения.
sqlText - сюда можно запихнуть любой SQL запрос, в том числе и вызов хранимой процедуры.
while (dr.Read()) {
PreviousMonth = dr.GetValue(0).ToString();
}
"dr" - сохраняет в себе структуру возвращенных данных
PreviousMonth - это локальная переменная, которая создается и туда записывается значение. Можете вместо этого кода вписать
следующий код
Page.TextEdit14.SetValue(dr.GetValue(0).ToString());
только вместо "0" подставте нужный индекс. Посмотрите какой он по счету в выборке в запросе
скрипт отрабатывает но в TextEdit14 ничего не возвращает(пробовал разные индексы(0 1 2), а вообще запись по счету первая).
вот текст скрипта, вашим методом я так и не разобралмя как вызвать хранимку с передачей в нее параметров.
var U1=Page.TextEdit5.Value;//var result = 5;
var dataValueTypeManager =(DataValueTypeManager)UserConnection.AppManagerProvider.GetManager("DataValueTypeManager");
var storedProcedure =new StoredProcedure(UserConnection, "BPM_to_DMS");
storedProcedure.WithParameter(U1);
storedProcedure.WithOutputParameter("NAP_NUM", dataValueTypeManager.GetInstanceByName("Text"));using(var dbExecutor = UserConnection.EnsureDBConnection()){
dbExecutor.StartTransaction(System.Data.IsolationLevel.ReadUncommitted);// storedProcedure.Execute(dbExecutor);{using(var dr =storedProcedure.ExecuteReader(dbExecutor)){while(dr.Read()){
Page.TextEdit14.SetValue(dr.GetValue(0).ToString());}}}
dbExecutor.CommitTransaction();}returntrue;
Профайлер перехватывает выполнение и показывает что в @NAP_NUM вернулось нужное значение
Вы не коректно написали, UI Надо писать без кавычек, это имя переменной, в которой хранится значение передаваемой в хранимку переменной.
Владимир, если написать таким образом то просит объявить переменную NAP_NUM, если ее объявить то просит записать туда какаое-нить значение. Если передать туда 2 значения то процедура не срабатывает т.к. в процедуре ожидается 1 параметр входящий и 1 исходящий, а таким образом мы передаем туда 2 входящих. Может подскажите каким образом обявить NAP_NUM исходящим параметром? Спасибо.
В качестве параметров я привел пример. Вы можете их вообще туда не передавать, или передать сколько угодна. Вы передайте, те переметры, что Вам надо
var Proc = string.Format("BPM_TO_DMS {0},{1} ...", "U1", "NAP_NUM", ...);
Владимир, это я понял, но мне нужно передать в хранимку 1 параметр, и хранимка должна вернуть 1 параметр. для этого в хранимке объявлен переменная NAP_NUM OUTPUT (в которую возвращается результат работы хранимки). елси вызывать хранимку через var proc =new StoredProcedure то там можно указать какой из параметров входящий а какой выходящий. Может вы подскажете каким образом в вашем скрипте указать что NAP_NUM это OUTPUT параметр? спасибо за помощь
При сохранении карточки (добавление\обновление) надо было создавать и обновлять, если уже было создано, заранее неизвестное количество операций (одна или две или три - в зависимости от значения поля). Я решил эти так:
сделал триггер After Update на tbl_Task, который удаляет все связанные с задачей (и "созданные автоматически" - поле в tbl_Cashflow) операции, а на AfterPost датасета задач добавил функцию добавления операций.
Все работает нормально, но есть 1-10% записей для которых операции отсутствуют. То ли не создаются, то ли удаляются - т.е. такое ощущение, что сначала отрабатывает AfterPost датасета - создает операции, а потом отрабатывает триггер (из-за каких-нибудь гипотетических тормозов сервера) - удаляет операции.
Нагрузка на сервер маленькая, если не минимальная.
Вопросы:
1) Возможна ли ситуация когда триггер After Update отрабатывает после AfterPost датасета?
2) если да, то есть ли еще варианты, как это решить, кроме как создавать доп. поля в задаче для записи ИД созданных операций или обновления\удаления записей по ним
Там еще такой важный момент был, что задачу мог изменять, например, директор и тогда операции создавались от его имени и с урезанными для менеджеров правами - в результате чего менеджеры их не видели и у них создавались дубликаты этих операций. Именно поэтому я перенес логику на БД
1. Такого быть не может. Скорее всего ошибка в самом триггере (в запросе на удаление). По умолчанию триггер работает в рамках одной транзакции, т.е. или он отрабатывает полностью или не отрабатывает вообще (вываливается с ошибкой). Смотрите в тело триггера.
"Евгений Либин" написал:триггер работает в рамках одной транзакции
да, действительно, что это я :lol:
однако в триггере "ничего такого":
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER TRIGGER [dbo].[tr_tbl_Task_AU] ON [dbo].[tbl_Task]
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
declare @TaskID uniqueidentifier
select @TaskID =[INSERTED].[ID]
from [INSERTED]
DELETE FROM [dbo].[tbl_Cashflow]
WHERE([tbl_Cashflow].[TaskID]= @TaskID AND
[tbl_Cashflow].[AutoCreated]=1)
END
в карточке редактирования тоже все предельно просто:
function dlDataOnDatasetAfterPost(Dataset){
CopyContactInTasksIfNeed(Dataset);//ProcessSendMailMessageForTask(Dataset); //отключено
CreateContactsInTask(Self, Dataset);
CreateCashflowByTask(dlData.Dataset);// вот это}
Не знаю, почему там используется даталинк - наверно, просто как следствие копипаста). Вряд ли это могло вызвать эту проблему, исправил.
в самой функции, если убрать все лишнее и заполнение значений тоже все довольно просто:
function CreateCashflowByTask(TaskDataset){// на апдейт задачи стоит tr_tbl_Task_AU (удаляет старые операции)
var TaskCode = GetTaskTypeCodeByID(TaskDataset.ValAsGUID('TypeID'));
var CashflowDataset = Services.GetNewItemByUSI('ds_Cashflow');// сюда наверно лучше поставить GetSingleItemByCode()
CashflowDataset.DataFields.ItemsByName('DebtorCreditorID').IsRequired=false;
CashflowDataset.Append();
FillCashFlowDatasetValuesByTask(CashflowDataset, TaskDataset, false, TaskCode);
CashflowDataset.Post();
CashflowDataset.Close();}
В результате, по-прежнему не понимаю, почему может так происходить.
Причем воспроизвести сохранение задачи без формирования операции у меня пока не получилось. Не знаю, что и делать - не сидеть же мне и не клепать записи задач пока не получу такой случай...
"Евгений Либин" написал:А где CashflowDataset.Open(); ?
мне его читать не надо - есть только Append() в CreateCashflowByTask
"SQK" написал:Если набор данных не находится в состоянии добавления записи (значение свойства IDataset::State не равно "dstInsert"), и у текущего пользователя есть права на добавление записи (значение свойства IDataset::CanInsert равно "True"), то вызывает событие IDatasetEvents::OnDatasetBeforeAppend. Иначе метод завершает работу.
Здравствуйте, Дмитрий!
Помогли ли советы Евгения по открытию DataSet'а?
В данном случае также возможны следующие вариант, что операции создаются, но не привязываются к записям.
Попробуйте включить логирование и при возникновении проблемы проверьте, добавлялись ли записи вообще.
"Андрей Каспаревич" написал:Помогли ли советы Евгения по открытию DataSet'а?
Пока не знаю. Я дописал открытие с фильтрацией. Через какое-то время посмотрим, будет ли повторяться ситуация.
"Андрей Каспаревич" написал:операции создаются, но не привязываются к записям
почему? Не вижу причин, почему ИД задач и продаж могли бы не подставляться в скрипте. Кроме того я бы видел разницу между результатами запросов в SQL и в Террасофт
"Андрей Каспаревич" написал:
Попробуйте включить логирование и при возникновении проблемы проверьте, добавлялись ли записи вообще.
Так и сделал))
Через 2 недели - месяц отпишусь, что все ок, либо раньше - что нет
"Андросов Дмитрий" написал:
Борисов Михаил Евгеньевич пишет:
А делать DELETE FROM [dbo].[tbl_Cashflow] можно под всеми пользователями?
триггеру можно)
Не понял, как? Тригер срабатывает в рамках транзакции где пошел update. Он будет обращаться к таблицам от имени того пользователя от которого пошел update.
"Андросов Дмитрий" написал:
Борисов Михаил Евгеньевич пишет:
Он будет обращаться к таблицам от имени того пользователя от которого пошел update
вот только пользователи работают с представлениями (и их триггерами), а я сделал на таблицу
У меня только один вопрос: от имени какого пользователя открыта транзакция в тригере таблицы? Что возращает suser_name(): supervisor, vasay или еще что то? Я подозреваю, что vasay и прав у него ровно столько сколько дали через grant.
На сколько я понял - проблем с удалением записей нет. Вопрос только с созданием новых. Если речь идет о контексте выполнения триггера, то нужно для начала уточнить версию СУБД. Разные СУБД и даже разные версии СУБД по разному используют (не используют) контекст.
Проблема была в том, что пользователи использовали массовое редактирование записей (делается через датасет), а создание операций повешено на скрипт карточки....
В процессе администрирования базы данных возникла необходимость определить причину возникновения ошибки. Определенный объём информации импортируется в базу данных, с которым далее пользователи работают. В процессе заполнения определенного набора полей автоматически высчитывалась итоговая сумма в поле «Итого». Но в определённый промежуток времени использования продукта начали появляться ошибки, связанные с несоответствием значения поля «Итого» сумме полей из которых оно вычисляется («Сумма покупки», «Наценка», «Сбор» и т.д.). Так как ошибку не получалось явно повторить, необходимо было разработать механизм для решения данной проблемы.
Естественно самой реальной и первой причиной возникновения такой ошибки приходила идея о сбоях в работе событий полей окна редактирования (то есть значения в полях изменялись, а события данных полей(-я) не срабатывали).
В основу решения было положено создание двух таблиц в базе данных для ведения логов, что происходят с записью набора данных. Первая таблица WindowLog, а вторая TriggerLog.
Первая таблица WindowLog включает в себя поля «Дата создания»(CreatedOn), «Идентификатор записи» (RecordID), «Ответственный» (WindowsUser), «Имя поля породившего событие»(FieldName), «Итого» и поля из которых оно вычисляется («Сумма покупки», «Наценка», «Сбор» и т.д.). Для наполнения таблицы было использованы события невизуального компонента окна dlData: dlDataOnDatasetDataChange, dlDataOnDatasetBeforePost и dlDataOnDatasetAfterPost. В скрипте в событиях была создана функция, которая формировала SQL запрос к таблице WindowLog базы данных с фиксацией информации по указанным полям на момент срабатывания события.
Вторая таблица TriggerLog включает в себя поля «Дата создания»(CreatedOn), «Идентификатор записи» (RecordID), «Состояние» (до изменения записи и после), «SystemUser», «Итого» и поля из которых оно вычисляется («Сумма покупки», «Наценка», «Сбор» и т.д.). Для заполнения данной таблицы был создан триггер на инструкцию UPDATE проблемной таблицы с двумя запросами вставки значений в таблицу. В одном запросе вставлялись значения до изменений, а во втором после.
Запрос №1:
INSERTINTO TriggerLog (*набор полей*) SELECT(*набор полей*) FROM deleted
Запрос №2:
INSERTINTO TriggerLog (*набор полей*) SELECT(*набор полей*) FROM inserted
Результатом использования данного решения на основе анализа таблицы WindowLog было установлено, что срабатывают все события окна редактирования, влияющие на вычисление значения поля «Итого». В процессе использования окна редактирования и после сохранения записи значения поля «Итого» были корректны.
Проанализировав записи в таблице TriggerLog было установлено, что в результате выполнения инструкции UPDATE было внесено некорректное значение. Сопоставив даты создания записей в таблице TriggerLog и WindowLog было установлено, что инструкция UPDATE была вызвана не в результате манипуляций с окном редактирования, а иным источником. На основании поля «SystemUser» таблицы TriggerLog было установлено что изменения были внесены с помощью импортера данных.
Таблицу TriggerLog возможно расширить, добавив в нее поля, которые помогут ускорить процесс обнаружение источника изменений записи базы данных. Список дополнительных полей может выгладять следующим образом: ApplicationName, LoginName, HostName.
PS: Принимаю предложения на доработку вашей конфигурации!!! Для более детальной информации можно связаться по следующему e-mail адресу: providnui@ukr.net !!!
В случае возникновения дополнительных вопрос по теме могу поделиться более детальной информацией.
Столкнулся с интересной ситуацией при переводе проектного решения с версии 3.2 на версию 3.3. Карточка проекта не закрывалась при нажатии на кнопку OK. Запись в базе при этом сохранялась.
В результате отладки выяснил, что в 3.3 базовая карточка редактирования была доработана и теперь, если метод Post набора данных карточки возвращает 0, карточка не закрывается.
Метод Post набора данных возвращает количество строк, которые были затронуты при выполнении запроса. В моем случае, этот метод возвращал 0, несмотря на то, что запись сохранялась.
Более внимательный взгляд на таблицу проектов показал, что для нее есть несколько триггеров After Update, созданных в рамках проектного решения. Собственно, результат выполнения этих триггеров и возвращается методом Post в конечном итоге.
Чтобы все отрабатывало как нужно, и метод Post возвращал именно количество записей непосредственно затронутых запросом, в триггерах следует включать опцию NOCOUNT.
Для этого в каждом триггере первой строкой необходимо записать