Триггер и скрипт датасета - кто первый? :)

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:

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