Вопрос

Доброго времени суток, коллеги. При интеграции 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
ALTER TRIGGER [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();
        return true;

если вы полнить инсерт из sql менеджера то в результах я вижу, что хранимка вернула значение.

каким оброзом можно запихнуть возвращаемое значение в переменную и использовать ее дальше в BPM ?
Спасибо что дочитали :)

У меня такой же вопрос

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

если в скрипт кнопки добавить

Page.TextEdit14.SetValue(insert);

то в TextEdit14 пишется 1 (единица), но передоваемое значение не равно 1.

Попробуйте указанный код, вместо Insert

var selectSQL = new CustomQuery(UserConnection, sqlText);
using (var dbExecutor = UserConnection.EnsureDBConnection()) {
	using(var dr = selectSQL.ExecuteReader(dbExecutor)) {
		while (dr.Read()) {
			PreviousMonth = dr.GetValue(0).ToString();
		}
	}
}

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

может кому не сложно написать коды вызова хранимки:

в хранимку передаем значение из поля TextEdit5.
вернуть значение нужно в TextEdit14

почитал немного тут http://www.community.terrasoft.ru/blogs/7708

вот, что наваял:

var U1=Page.TextEdit5.Value;
//var NAP_NUM = null;
var dataValueTypeManager = (DataValueTypeManager)UserConnection.AppManagerProvider.GetManager("DataValueTypeManager");
var proc = new StoredProcedure(UserConnection, "BNS_to_DMS");
StoredProcedure.WithParameter(U1);
StoredProcedure.WithOutputParameter("NAP_NUM", dataValueTypeManager.GetInstanceByName("Text"));
using (DBExecutor dbExecutor = UserConnection.EnsureDBConnection()) {
        dbExecutor.StartTransaction(System.Data.IsolationLevel.ReadUncommitted);
        Page.DataSource.ActiveRow.Save();
        StoredProcedure.Execute(dbExecutor);}
SetCloseStatus(1);
//Connector.DBEngine.ExecuteCustomSQL(SQLText);
//var insert = new Insert(UserConnection).Into("napr").Set("USLUGA_1", Column.Parameter(U1)).Execute();
 	Page.TextEdit14.SetValue(NAP_NUM);
	return true;

но валится много ошибок при компиляции.

PS: извиняйте что туплю, я не программист

код хранимки

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);
	return true;

можно хотябы пример похожий (в примере с поиском дублей ничего не понятно)

PS: когда все допилю обещаю написать полный мануал по вызову хранимой процедуры на связанном сервере firebird 2.0

Добрый день. А почему Вы не используете тот пример, что я вам скинул? Им тоже можна "дергать" хранимые процедуры и получать значения

"Олейников Владимир Владимирович" написал:

Добрый день. А почему Вы не используете тот пример, что я вам скинул? Им тоже можна "дергать" хранимые процедуры и получать значения


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

там все очень просто. UserConnection - Переменная которая хранит в себе параметры подключения.
sqlText - сюда можно запихнуть любой SQL запрос, в том числе и вызов хранимой процедуры.

while (dr.Read()) {
PreviousMonth = dr.GetValue(0).ToString();
}

"dr" - сохраняет в себе структуру возвращенных данных

"Олейников Владимир Владимирович" написал:

там все очень просто. UserConnection - Переменная которая хранит в себе параметры подключения.
sqlText - сюда можно запихнуть любой SQL запрос, в том числе и вызов хранимой процедуры.

while (dr.Read()) {
PreviousMonth = dr.GetValue(0).ToString();
}

"dr" - сохраняет в себе структуру возвращенных данных


спасибо, будем пробовать

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

забал сказать что хранимка возвращает не просто число, а текст, по сути номер направления ( пример 15326/2015)

"Олейников Владимир Владимирович" написал:

там все очень просто. UserConnection - Переменная которая хранит в себе параметры подключения.
sqlText - сюда можно запихнуть любой SQL запрос, в том числе и вызов хранимой процедуры.

while (dr.Read()) {
PreviousMonth = dr.GetValue(0).ToString();
}

"dr" - сохраняет в себе структуру возвращенных данных

Владимир, ругается на PreviousMonth,

The name 'PreviousMonth' does not exist in the current context
DMSEditPage.cs
2918

PreviousMonth - это локальная переменная, которая создается и туда записывается значение. Можете вместо этого кода вписать
следующий код
Page.TextEdit14.SetValue(dr.GetValue(0).ToString());
только вместо "0" подставте нужный индекс. Посмотрите какой он по счету в выборке в запросе

"Олейников Владимир Владимирович" написал:

PreviousMonth - это локальная переменная, которая создается и туда записывается значение. Можете вместо этого кода вписать
следующий код
Page.TextEdit14.SetValue(dr.GetValue(0).ToString());
только вместо "0" подставте нужный индекс. Посмотрите какой он по счету в выборке в запросе


Владимир, попробовал подставить Page.TextEdit14.SetValue(dr.GetValue(0).ToString());

скрипт отрабатывает но в 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();
 
}
 
 
	return true;

Профайлер перехватывает выполнение и показывает что в @NAP_NUM вернулось нужное значение

Попробуйте в дебаге посмотреть что вам возвращает переменная "dr" и все увидите

var dataValueTypeManager = (DataValueTypeManager)UserConnection.AppManagerProvider.GetManager("DataValueTypeManager");
var proc = new StoredProcedure(UserConnection, "BNS_to_DMS");
StoredProcedure.WithParameter(U1);
StoredProcedure.WithOutputParameter("NAP_NUM", dataValueTypeManager.GetInstanceByName("Text"));
using (DBExecutor dbExecutor = UserConnection.EnsureDBConnection()) {
        dbExecutor.StartTransaction(System.Data.IsolationLevel.ReadUncommitted);
        Page.DataSource.ActiveRow.Save();
        StoredProcedure.Execute(dbExecutor);}
SetCloseStatus(1);
//Connector.DBEngine.ExecuteCustomSQL(SQLText);
//var insert = new Insert(UserConnection).Into("napr").Set("USLUGA_1", Column.Parameter(U1)).Execute();
        Page.TextEdit14.SetValue(NAP_NUM);
        return true;

Вместо этого кода напишите мой

"Олейников Владимир Владимирович" написал:

var dataValueTypeManager = (DataValueTypeManager)UserConnection.AppManagerProvider.GetManager("DataValueTypeManager");
var proc = new StoredProcedure(UserConnection, "BNS_to_DMS");
StoredProcedure.WithParameter(U1);
StoredProcedure.WithOutputParameter("NAP_NUM", dataValueTypeManager.GetInstanceByName("Text"));
using (DBExecutor dbExecutor = UserConnection.EnsureDBConnection()) {
        dbExecutor.StartTransaction(System.Data.IsolationLevel.ReadUncommitted);
        Page.DataSource.ActiveRow.Save();
        StoredProcedure.Execute(dbExecutor);}
SetCloseStatus(1);
//Connector.DBEngine.ExecuteCustomSQL(SQLText);
//var insert = new Insert(UserConnection).Into("napr").Set("USLUGA_1", Column.Parameter(U1)).Execute();
        Page.TextEdit14.SetValue(NAP_NUM);
        return true;

Вместо этого кода напишите мой


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

вот что выдает дебагер

var sqlText = string.Format("ProcName {0},{1}", "value1", "value2")

Объявляем переменную, в которой хранится вызов хранимой процедуры с переметрами "value1", "value2"

var selectSQL = new CustomQuery(UserConnection, sqlText);
using (var dbExecutor = UserConnection.EnsureDBConnection()) {
        using(var dr = selectSQL.ExecuteReader(dbExecutor)) {
                while (dr.Read()) {
                        PreviousMonth = dr.GetValue(0).ToString();
                }
        }
}

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

"Олейников Владимир Владимирович" написал:

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


спасибо за ответ, вот что вышло,

var U1=Page.TextEdit5.Value;
var Proc = string.Format("BPM_TO_DMS {0},{1}", "U1", "NAP_NUM");
	var selectSQL = new CustomQuery(UserConnection, Proc);
using (var dbExecutor = UserConnection.EnsureDBConnection()) {
        using(var dr = selectSQL.ExecuteReader(dbExecutor)) {
                while (dr.Read()) {
                        Page.TextEdit14.SetValue(dr.GetValue(1).ToString());
                }
        }
}
return true;

но при выполнении ничего не происходит, дебагер выдал все переменные по null, профайлер вы дал просто строку "BPM_TO_DMS U1,NAP_NUM" куда копать?

var Proc = string.Format("BPM_TO_DMS {0},{1}", "U1", "NAP_NUM");

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

"Олейников Владимир Владимирович" написал:

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


Владимир, если написать таким образом то просит объявить переменную NAP_NUM, если ее объявить то просит записать туда какаое-нить значение. Если передать туда 2 значения то процедура не срабатывает т.к. в процедуре ожидается 1 параметр входящий и 1 исходящий, а таким образом мы передаем туда 2 входящих. Может подскажите каким образом обявить NAP_NUM исходящим параметром? Спасибо.

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

var Proc = string.Format("BPM_TO_DMS {0},{1} ...", "U1", "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 параметр? спасибо за помощь

В конце хранимки сделать выборку нужного вам параметра

DECLARE @d INT = 0
-- Какие-то действия с переменной
SELECT  @d
Войдите или зарегистрируйтесь, чтобы комментировать
Вопрос

3.4.0 XRM
Ситуация следующая:

При сохранении карточки (добавление\обновление) надо было создавать и обновлять, если уже было создано, заранее неизвестное количество операций (одна или две или три - в зависимости от значения поля). Я решил эти так:
сделал триггер After Update на tbl_Task, который удаляет все связанные с задачей (и "созданные автоматически" - поле в tbl_Cashflow) операции, а на AfterPost датасета задач добавил функцию добавления операций.
Все работает нормально, но есть 1-10% записей для которых операции отсутствуют. То ли не создаются, то ли удаляются - т.е. такое ощущение, что сначала отрабатывает AfterPost датасета - создает операции, а потом отрабатывает триггер (из-за каких-нибудь гипотетических тормозов сервера) - удаляет операции.
Нагрузка на сервер маленькая, если не минимальная.

Вопросы:
1) Возможна ли ситуация когда триггер After Update отрабатывает после AfterPost датасета?
2) если да, то есть ли еще варианты, как это решить, кроме как создавать доп. поля в задаче для записи ИД созданных операций или обновления\удаления записей по ним

У меня такой же вопрос

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

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

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();
}

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

А что внутри FillCashFlowDatasetValuesByTask?
Похоже что туда надо смотреть, т.е. триггер отработал а FillCashFlowDatasetValuesByTask нет.

если прочитаете, то вот ))
если коротко - там просто заполняются значения, все действия происходят вне функции

function FillCashFlowDatasetValuesByTask(CashflowDataset, TaskDataset, HeadPhones, TaskCode) {
	var ClauseID = GetCashflowClauseIDByCode(TaskCode);
	var ExpenseTypeID;
	if ((TaskCode == 'Bus') /*&& IsEmptyGUID(TaskDataset.ValAsGUID('OpportunityID'))*/) {
		ExpenseTypeID = GetCashflowExpenseTypeIDByCode(TaskCode);
	} else 
	/*if (TaskDataset.ValAsBool('OrderByGuide')){
		ExpenseTypeID = GetCashflowExpenseTypeIDByCode('drugoe');
	} else*/ {
		ExpenseTypeID = GetCashflowExpenseTypeIDByCode('Tour');
	}
	if (TaskDataset.ValAsBool('IsCash') || TaskCode == 'Bus' || HeadPhones) {
		CashflowDataset.ValAsGUID('CashAccountID') = '{4FAFDE39-D5EB-4309-BAFD-9C3DA1FCF0EB}'; // наличная касса
	} else {
		CashflowDataset.ValAsGUID('CashAccountID') = '{ADEC1E41-17EE-4B02-B04E-D677DEA48D39}'; // расчетный счет
	}
 
	if (TaskDataset.ValAsGUID('OpportunityID')) {
		CashflowDataset.ValAsGUID('OpportunityID') = TaskDataset.ValAsGUID('OpportunityID');
	}CashflowDataset.ValAsGUID('StatusID') = '{FDEA47BE-53FE-4730-BF4F-4F44C3B5D61A}'; // выполнена
	if (!HeadPhones) {
		CashflowDataset.ValAsStr('Subject') = TaskDataset.ValAsStr('Title');
	} else {
		CashflowDataset.ValAsStr('Subject') = TaskDataset.ValAsStr('Title') + ' наушники';
	}
	//var IsTop = GetIsUserInGroup(Connector.CurrentUser.Name, 'УЧРЕДИТЕЛИ');
	CashflowDataset.ValAsBool('UseAsCashflow') = false;
	CashflowDataset.ValAsBool('UseAsPandL') = true;
	CashflowDataset.ValAsBool('AutoCreated') = true;
	CashflowDataset.ValAsBool('ForFlow') = true;
	CashflowDataset.ValAsGUID('ClauseID') = ClauseID;
	CashflowDataset.ValAsGUID('ExpenseTypeID') = ExpenseTypeID; // группы
	CashflowDataset.ValAsGUID('CurrencyID') = TaskDataset.ValAsGUID('CurrencyID');
	if (!HeadPhones) {
		CashflowDataset.ValAsFloat('Amount') = TaskDataset.ValAsFloat('FullAmount');
	} else {
		CashflowDataset.ValAsFloat('Amount') = TaskDataset.ValAsFloat('HeadphonesAmount');
	}
	CashflowDataset.ValAsGUID('PayerID') = Connector.CurrentUser.AccountID;
	CashflowDataset.ValAsFloat('AutocalcAmount') = true;
	if (!HeadPhones) {
		CashflowDataset.Values('RecipientID') //= CashflowDataset.Values('DebtorCreditorID') 
			= TaskDataset.Values('SupplierID');
	} else {
		CashflowDataset.Values('RecipientID') //= CashflowDataset.Values('DebtorCreditorID') 
			= TaskDataset.Values('Supplier2ID');
	}
	//SetBasicPriceInDataset(CashflowDataset, 'BasicAmount', 'Amount', 'CurrencyID', 'CurrencyRate');		
	CashflowDataset.ValAsGUID('TaskID') = TaskDataset.ValAsGUID('ID');
	CashflowDataset.DisableEvents();
	CashflowDataset.ValAsDateTime('ActualDate') = CashflowDataset.ValAsDateTime('EstimatedDate') = TaskDataset.ValAsDateTime('StartDate');
	CashflowDataset.EnableEvents();
	return CashflowDataset;
}

на всякий случай вот полный текст функций
createcashflowbytask.txt

На первый взгляд всё ОК.
Проверьте 2 пункта
1. Посмотрите напрямую из базы

 Select * from tbl_CashFlow Where TaskID = 'Нужный ID задачи'

2. Если вышеуказанный запрос вернет данные, то посмотрите на sq_CashFlow возможно где-то inner join или в where доп. условия.

"Евгений Либин" написал:Проверьте 2 пункта

1. нет
2. нет(

А где CashflowDataset.Open(); ?

"Евгений Либин" написал:А где CashflowDataset.Open(); ?

мне его читать не надо - есть только Append() в CreateCashflowByTask
"SQK" написал:Если набор данных не находится в состоянии добавления записи (значение свойства IDataset::State не равно "dstInsert"), и у текущего пользователя есть права на добавление записи (значение свойства IDataset::CanInsert равно "True"), то вызывает событие IDatasetEvents::OnDatasetBeforeAppend. Иначе метод завершает работу.

Я бы рекомендовал всегда перед добавление делать открытие датасета, тем более есть такая строка

CashflowDataset.DataFields.ItemsByName('DebtorCreditorID').IsRequired = false;

.
Для ускорения можно делать фильтр по любому левому ID

ApplyDatasetFilter(CashflowDataset, 'ID', GUID_NULL, true);

Обращения по ID моментальное, набор данных будет пустой и датасет будет проинициализирован.

Спасибо, Евгений, воспользуюсь Вашими советами, хотя и не понимаю, для чего мне открывать датасет (на Before\After Open ничего нет)

Тут смысл не в отработке событий, а в инициализации самого датасета.

Здравствуйте, Дмитрий!
Помогли ли советы Евгения по открытию DataSet'а?
В данном случае также возможны следующие вариант, что операции создаются, но не привязываются к записям.
Попробуйте включить логирование и при возникновении проблемы проверьте, добавлялись ли записи вообще.

"Андрей Каспаревич" написал:Помогли ли советы Евгения по открытию DataSet'а?

Пока не знаю. Я дописал открытие с фильтрацией. Через какое-то время посмотрим, будет ли повторяться ситуация.

"Андрей Каспаревич" написал:операции создаются, но не привязываются к записям

почему? Не вижу причин, почему ИД задач и продаж могли бы не подставляться в скрипте. Кроме того я бы видел разницу между результатами запросов в SQL и в Террасофт

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

Так и сделал))

Через 2 недели - месяц отпишусь, что все ок, либо раньше - что нет

А делать DELETE FROM [dbo].[tbl_Cashflow] можно под всеми пользователями?

"Борисов Михаил Евгеньевич" написал:

А делать DELETE FROM [dbo].[tbl_Cashflow] можно под всеми пользователями?


триггеру можно)

"Андросов Дмитрий" написал:
Борисов Михаил Евгеньевич пишет:

А делать DELETE FROM [dbo].[tbl_Cashflow] можно под всеми пользователями?

триггеру можно)


Не понял, как? Тригер срабатывает в рамках транзакции где пошел update. Он будет обращаться к таблицам от имени того пользователя от которого пошел update.

"Борисов Михаил Евгеньевич" написал:Он будет обращаться к таблицам от имени того пользователя от которого пошел update

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

"Андросов Дмитрий" написал:
Борисов Михаил Евгеньевич пишет:

Он будет обращаться к таблицам от имени того пользователя от которого пошел update

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


У меня только один вопрос: от имени какого пользователя открыта транзакция в тригере таблицы? Что возращает suser_name(): supervisor, vasay или еще что то? Я подозреваю, что vasay и прав у него ровно столько сколько дали через grant.

На сколько я понял - проблем с удалением записей нет. Вопрос только с созданием новых. Если речь идет о контексте выполнения триггера, то нужно для начала уточнить версию СУБД. Разные СУБД и даже разные версии СУБД по разному используют (не используют) контекст.

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

зато заглавный вопрос прояснил :wink:

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

В процессе администрирования базы данных возникла необходимость определить причину возникновения ошибки. Определенный объём информации импортируется в базу данных, с которым далее пользователи работают. В процессе заполнения определенного набора полей автоматически высчитывалась итоговая сумма в поле «Итого». Но в определённый промежуток времени использования продукта начали появляться ошибки, связанные с несоответствием значения поля «Итого» сумме полей из которых оно вычисляется («Сумма покупки», «Наценка», «Сбор» и т.д.). Так как ошибку не получалось явно повторить, необходимо было разработать механизм для решения данной проблемы.

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

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

Первая таблица WindowLog включает в себя поля «Дата создания»(CreatedOn), «Идентификатор записи» (RecordID), «Ответственный» (WindowsUser), «Имя поля породившего событие»(FieldName), «Итого» и поля из которых оно вычисляется («Сумма покупки», «Наценка», «Сбор» и т.д.). Для наполнения таблицы было использованы события невизуального компонента окна dlData: dlDataOnDatasetDataChange, dlDataOnDatasetBeforePost и dlDataOnDatasetAfterPost. В скрипте в событиях была создана функция, которая формировала SQL запрос к таблице WindowLog базы данных с фиксацией информации по указанным полям на момент срабатывания события.

Запрос:

INSERT INTO WindowLog (*набор полей*)
SELECT (*набор полей*) -- Dataset('поле1'), Dataset('поле2'), Dataset('поле2')

Вторая таблица TriggerLog включает в себя поля «Дата создания»(CreatedOn), «Идентификатор записи» (RecordID), «Состояние» (до изменения записи и после), «SystemUser», «Итого» и поля из которых оно вычисляется («Сумма покупки», «Наценка», «Сбор» и т.д.). Для заполнения данной таблицы был создан триггер на инструкцию UPDATE проблемной таблицы с двумя запросами вставки значений в таблицу. В одном запросе вставлялись значения до изменений, а во втором после.

Запрос №1:

INSERT INTO TriggerLog (*набор полей*)       
SELECT (*набор полей*)
FROM deleted

Запрос №2:

INSERT INTO TriggerLog (*набор полей*)       
SELECT (*набор полей*)
FROM inserted

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

Проанализировав записи в таблице TriggerLog было установлено, что в результате выполнения инструкции UPDATE было внесено некорректное значение. Сопоставив даты создания записей в таблице TriggerLog и WindowLog было установлено, что инструкция UPDATE была вызвана не в результате манипуляций с окном редактирования, а иным источником. На основании поля «SystemUser» таблицы TriggerLog было установлено что изменения были внесены с помощью импортера данных.

Таблицу TriggerLog возможно расширить, добавив в нее поля, которые помогут ускорить процесс обнаружение источника изменений записи базы данных. Список дополнительных полей может выгладять следующим образом: ApplicationName, LoginName, HostName.

PS: Принимаю предложения на доработку вашей конфигурации!!! Для более детальной информации можно связаться по следующему e-mail адресу: providnui@ukr.net !!!

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

Всем удачи в этом не легком процессе!!!

У меня такой же вопрос

0 комментариев
Войдите или зарегистрируйтесь, чтобы комментировать
Публикация

Столкнулся с интересной ситуацией при переводе проектного решения с версии 3.2 на версию 3.3. Карточка проекта не закрывалась при нажатии на кнопку OK. Запись в базе при этом сохранялась.

В результате отладки выяснил, что в 3.3 базовая карточка редактирования была доработана и теперь, если метод Post набора данных карточки возвращает 0, карточка не закрывается.

Метод Post набора данных возвращает количество строк, которые были затронуты при выполнении запроса. В моем случае, этот метод возвращал 0, несмотря на то, что запись сохранялась.

Более внимательный взгляд на таблицу проектов показал, что для нее есть несколько триггеров After Update, созданных в рамках проектного решения. Собственно, результат выполнения этих триггеров и возвращается методом Post в конечном итоге.

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

Для этого в каждом триггере первой строкой необходимо записать

SET NOCOUNT ON

Поделиться

0 комментариев
Войдите или зарегистрируйтесь, чтобы комментировать