Коллеги, приветствую.

Есть определенный интерес поизучать классы Select, Insert, Update, EntitySchemaQuery и подробнее
разобраться, как с ними работать.

Чтобы это было проще и быстрее мне бы хотелось иметь возможность работать с ними из MS Visual Studio.

Например, запрос из статьи Использование EntitySchemaQuery для построения запросов к базе данных:

// Создание экземпляра запроса, добавление в запрос колонок и источника данных.
Select selectQuery = new Select(UserConnection)
                    .Column("Id")
                    .Column("Name")
                    .From("Contact");
// Выполнение запроса к базе данных и получение результирующего набора данных.
using (DBExecutor dbExecutor = UserConnection.EnsureDBConnection())
{
    using (IDataReader reader = selectQuery.ExecuteReader(dbExecutor))
    {
        while (reader.Read())
        {
            // Обработка результатов запроса.
        }
    }
}

Собственно, вопрос в конфигурировании класса UserConnection, как его использовать "вне платформы", не в режиме отладки?

Например, в методе Main:

static void Main(string[] args)
{
}

В элементе "Задание- сценарий" я могу получить его таким образом:

var userConnection = Get("UserConnection");

В Web- сервисе WCF:

var userConnection = (UserConnection)HttpContext.Current.Session["UserConnection"];

Был бы весьма признателен за информацию.

Спасибо.

--
С уважением, Алексей Быков.

Нравится

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

Здравствуйте.
Если сайт у вас развернуть on-site, то есть, поднят на своем IIS сервере:
Тогда у вас есть возможность написать свой, к примеру, веб-сервис, или даже скрипт-сценарий, и пользуясь статьей по отладке,
http://academy.terrasoft.ru/documents/docs/technic/SDK/7.6.0/ServerCode…
ставить точки останова, и изучать поведение серверного ESQ.
Здесь главное, что бы код был частью системы, поэтому его и нужно писать как часть системы (веб-сервис, бизнес-процесс, и.т.д.), а не сторонние dll,exe,итд.
С автономной программы этого сделать не получится.

Если же сайт on-demand, то есть, развернут как http://имя-сайта.bpmonline.com/
То такой возможности у Вас нет, и со стороннего процесса (программы), Вы никак не обратитесь к ядру системы.
Из сторонних программ написанных Вами в Visual Studio, Вы можете разве что обратится к уже написанным веб-сервисам в рамках структуры сайта, либо же по протоколу OData,
http://academy.terrasoft.ua/documents/docs/technic/SDK/7.4.1/WorkWithBp… что, конечно же, совсем не запросы ESQ.

Александр, спасибо большое за ответ!

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

Коллеги, приветствую.

Есть некоторый запрос, который возвращает информацию по должнику и последней активности, проявленной в отношении него:

SELECT
        TOP 1  
        ContactId as id,
        AC.CreatedOn as activityDate,
        UP.CreatedOn as paymentDate

FROM
        Activity as AC INNER JOIN UsrPayments as UP
                ON AC.ContactId = UP.UsrDebtorId
WHERE
        UP.CreatedOn  > AC.CreatedOn

ORDER BY AC.id ASC;

И есть некоторый код на C#, где с помощью класса Select проделывается то же, но считываются записи по всем активностям:

var selectNewPayments = (Select)new Select(userConnection)
                .Column("t1", "ContactId")
                .Column("t1", "CreatedOn")
                .Column("t2", "CreatedOn")
                .From("Activity").As("t1")
                .Join(JoinType.Inner, "UsrPayments").As("t2")
                .On("t1", "ContactId").IsEqual("t2", "UsrDebtorId")
                .Where("t1", "CreatedOn")
                .IsLess("t2", "CreatedOn") as Select;

Как прочитать только первую запись?

Был бы весьма признателен за информацию.

Спасибо.

--
С уважением, Алексей Быков.

Нравится

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

Нашел, собственно -

var selectNewPayments = (Select)new Select(userConnection)
   .Top(1)
   .Column("t1", "ContactId") 
   .Column("t1", "CreatedOn")
   .Column("t2", "CreatedOn")
   .From("Activity").As("t1")
   .Join(JoinType.Inner, "UsrPayments").As("t2")
   .On("t1", "ContactId").IsEqual("t2", "UsrDebtorId")
   .Where("t1", "CreatedOn").IsLess("t2", "CreatedOn").OrderByAsc("t1", "id") as Select;

Такой запрос возможен???

select count(ID)

   from [dbo].[Case] AS TI

WHERE DATEPART(HOUR, DATEADD(hh, 6, TI.[UsrDueDate])) >=9  and DATEPART(HOUR, DATEADD(hh, 6, TI.[UsrDueDate])) <12

and FORMAT(DATEADD(hh, 6, TI.[UsrDueDate]), N'yyyy.MM.dd') = FORMAT(cast(GETDATE() +1 as date), N'yyyy.MM.dd')

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

Добрый день, коллеги!

Имеется примерно такой запрос:

var newSelect = new Select(CurrentUserConnection)
        .Column("Table1", "Column1").As("Column1")
        .From("Table1").As("Table1")
        .Where("Table1", "Column2").IsNotEqual(  ...  ) as Select;

И имеется некоторая коллекция значений, к примеру:

List myList = new List();

Вопрос в следующем: Каким образом можно в условие запроса передать список значений моей коллекции (будь то Array, ArrayList, List, Dictionary, Hashtable, SortedList или другое - значения не имеет)?

Условно говоря, мне нужно мою коллекцию представить в виде типа "Select", чтобы можно было использовать в месте отмеченном 3-мя точками, - это если следовать сигнатуре этих методов, согласно описанию из SDK (www.terrasoft.ru/bpmonlinesdk/). Но как это сделать? Прошу помощи!

Нравится

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

Виталий, для этого нужно использовать конструкцию .Not().In(массив_или_список_идентификаторов) .
Пример использования (схема EditSynchUserInSynchRole):

var ldapGroupsIds = new List<QueryParameter>();
	var ldapUserLoginAttribute = SysSettings.GetValue(UserConnection, "LDAPUserLoginAttribute").ToString();
	using (var ldapUtils = new LdapUtilities(UserConnection)) {
		var usersCollection = ldapUtils.GetUsersAttributesByFilter(ldapUserLoginAttribute + "=" + ldapEntry, new [] {"distinguishedName"});
		var userDn = string.Empty;
		foreach (SearchResultEntry user in usersCollection) {
			userDn = user.DistinguishedName;
			break;
		}
		var ldapGroupIdentityAttribute = SysSettings.GetValue(UserConnection, "LDAPGroupIdentityAttribute").ToString();
		var groupsCollection = ldapUtils.GetGroupsAttributesByFilter("member=" + userDn, new [] {ldapGroupIdentityAttribute});
		foreach(SearchResultEntry group in groupsCollection) {
			ldapGroupsIds.Add(new QueryParameter(ldapUtils.IdentityAttributeToString(group.Attributes[ldapGroupIdentityAttribute][0])));
		}
	}
 
	if (ldapGroupsIds.Count < 1) {
		return true;
	}
 
	var delete = new Delete(UserConnection).From("SysUserInRole").
	Where("SysUserId").IsEqual(Column.Parameter(userId)).
	And().Exists(new Select(UserConnection).
		Column("Id").From("SysAdminUnit").
		Where("SysUserInRole", "SysRoleId").IsEqual("SysAdminUnit", "Id").
		And("SynchronizeWithLDAP").IsEqual(Column.Parameter(true)).
		And("LDAPEntryId").Not().In(ldapGroupsIds));
	delete.Execute();

Если же передавать массив, то так (схема AdministrativeGrantedRightsGridPage):

	object[] objectParameters = new object[adminUnitCollection.Count];
	for (int i = 0; i < adminUnitCollection.Count; i++) {
		objectParameters[i] = adminUnitCollection[i];
	}
}
var entitySchemaManager = Page.UserConnection.EntitySchemaManager;
var rightsSchema = entitySchemaManager.GetInstanceByName("SysAdminUnitGrantedRight");
var select = new Select(Page.UserConnection)
		.Column(rightsSchema.Name, "Id")
		.Column(rightsSchema.Name, "GrantorSysAdminUnitId")
		.Column("Grantor", "Name").As("GrantorSysAdminUnitName")
	.From(rightsSchema.Name)
	.InnerJoin("SysAdminUnit").As("Grantor").On("Grantor", "Id").IsEqual(rightsSchema.Name, "GrantorSysAdminUnitId")
	.Where(rightsSchema.Name, "GranteeSysAdminUnitId").In(Column.Parameters(objectParameters))
	.OrderByAsc("Grantor", "Name") as Select;

Хм... Честно говоря, если судить по информации из SDK (http://i.piccy.info/i9/fa961117bdc660b65920ae24393d4c3e/1452507140/1535…), то оператор "In()" не принимает произвольную коллекцию, потому и решил переспросить. Во всяком случае, вопрос закрыт. В очередной раз спасибо Вам, Александр!

Я тоже думал, что только массивы из Object, но оказалось, что не только массивы.
В первом примере обратите внимание, что коллекция из элементов именно типа QueryParameter.

А это действительно важно, использовать именно тип "QueryParameter"? Т.е., к примеру:

List<Guid> guids = new List<Guid>();

нельзя использовать? Или тот же тип "string"? В том же SDK есть только такой пример: http://i.piccy.info/i9/7c65675471d92405256ab8d1e300693f/1452512386/1789…

Если следовать этому описанию, то коллекция ГУИДов должна выглядеть так:

List<QueryParameter> guids = new List<QueryParameter>();
guids.Add(new QueryParameter((new Guid("...")).ToString()))

Верно ли это? Тогда я не смогу указать оператор "In()" для колонки, содержащую тип "Guid", т.к. я буду проверять наличие текущей записи в коллекции guids, а у нее QueryParameter содержит тип "string". Что-то тут нечисто... Подскажете, как быть?:confused:

Попробуйте сделать как в моём первом примере, там как раз сравнивается со списком из Guid, хоть и приводится к строке.

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

Доброго времени суток, коллеги.
Согласно этому в запросе в колонке с текстом SQL вроде как можно использовать конструкцию для автоматической замены таблицы на представление в зависимости от прав пользователя. Вопрос: в каких версиях Terrasoft это работает? На Terrasoft CRM 3.3.2.245 выдает ошибку "Оригинальное сообщение об ошибке: Incorrect syntax near ''. Incorrect syntax near the keyword 'SELECT'"

Нравится

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

Добрый день.

Начиная с версии 3.4.0

Ясно, спасибо.
А в 3.3.2 подобную задачу решить можно? Или сразу вместо имен таблиц вставлять имена представлений?

Можно создать отдельный сервис на основе представления и обращаться к нему.

Также можно


делать в колонке вызов хранимой функции с нужным условием.

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

Добрый день!
Подскажите, пожалуйста синтаксис запрос COUNT на C#

Например, такой запрос
var select =
new Select(userConnection)
.Column("SysUserInRole","SysUserId")
.From("SysUserInRole")
.Join(JoinType.Inner, "SysAdminUnit").On("SysUserInRole", "SysUserId").IsEqual("SysAdminUnit", "Id")
.Where("SysUserInRole", "SysRoleId").IsEqual(new QueryParameter(ownerGroup))
as Select;

Как здесь правильно прописать COUNT?

Нравится

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

.Column(Func.Count("TableName", "FieldName")).As("ColumnAlias")

что то вроде такого

Спасибо

Добрый день!
А подскажите, пожалуйста, как привести к целому типу?

к String, например, так
(String)dataReader.GetColumnValue("NameGroup")

к Guid, например, так
(Guid)dataReader.GetColumnValue("NameId")

а к целому типу как?

Int32.Parse()
к примеру:
Int32.Parse((String)dataReader.GetColumnValue("NameCount"))

а если NameCount - это и так число?

я, наверное, не так выразилась
- хочу обратиться, результату значения из запроса
dataReader.GetColumnValue("NameCount")
NameCount"- это число (кол-во записей)

Дарья, добрый день!

Попробуйте так:

dataReader.GetColumnValue<int>("NameCount")
Показать все комментарии

Как выполнить sql скрипт из кода bpm 5.4 ? (не нашел на комьюнити)

Нравится

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

Здравствуйте, Илья!
Ссылки Вам в помощь:
http://www.community.terrasoft.ru/developer/article/7043
http://www.terrasoft.ru/bpmonlinesdk/ (раздел "Примеры")
http://www.community.terrasoft.ru/search/node/esq (можно найти примеры)

Андрей, подскажите, а как получить доступ к странице: http://www.community.terrasoft.ru/developer/article/7043 ?

"D.T." написал:

Андрей, подскажите, а как получить доступ к странице: http://www.community.terrasoft.ru/developer/article/7043 ?


+1

Прошу прощения, дал не ту ссылку.
Вот та же статья:
http://www.community.terrasoft.ru/blogs/8977

В примерах http://www.terrasoft.ru/bpmonlinesdk/ не понятно откуда берется userConnection.
Примера объявления userConnection не нашел.

Обычно в процессе страницы или объекта есть глобальная переменная UserConnection, можно подставить её.

"Зверев Александр" написал:

Обычно в процессе страницы или объекта есть глобальная переменная UserConnection, можно подставить её.

Подскажите каким образом подставлять глобальные переменные.

Просто к ней обращаться.

Илья, UserConnection - это класс ядра, который включает функции и свойства соединения пользователя (уникальные данные пользователя, сессии, временная зона).

Пример использования:

UserConnection.CurrentUser.Id // текущий пользователь
UserConnection.CurrentUser.ContactId // Контакт текущего пользователя
UserConnection.CurrentUser.AccountId // Контрагент текущего пользователя 
UserConnection.CurrentUser.GetCurrentDateTime() // Текущие время в часовой зоне пользователя

Как написал Александр, чтобы использовать UserConnection, достаточно обратиться (без дополнительных объявлений).

У меня такой код:

var insert = new Insert(userConnection).Into("massmailingcontact")
        .Set("ContactId", Column.Parameter("C92EA9E7-CC76-4FA1-9CA4-5186AA32FC2A"))
		.Set("MassMailingId", Column.Parameter("8EC7B788-401E-40F0-9B82-05937B4399B0"))
		.Set("EmailAddress", Column.Parameter("cc-sib@yandex.ru"))
        .Set("EmailId", Column.Parameter("6EEC951D-C89A-42A2-9DE4-AFCA74A72B97"));

и вот такую ошибку в итоге получаю http://joxi.ru/knMUU_3JTJBHE4UZoAc

Надо с большой буквы U.

Спасибо по букве U. Теперь не ругается при публикации, но запрос не выполняется (в таблице не появляется записей), использую такой код:

var insert = new Insert(UserConnection).Into("massmailingcontact")
        .Set("ContactId", Column.Parameter("C92EA9E7-CC76-4FA1-9CA4-5186AA32FC2A"))
		.Set("MassMailingId", Column.Parameter("8EC7B788-401E-40F0-9B82-05937B4399B0"))
		.Set("EmailAddress", Column.Parameter("cc-sib@yandex.ru"))
        .Set("EmailId", Column.Parameter("6EEC951D-C89A-42A2-9DE4-AFCA74A72B97"));
 
return true;

а SQL запрос выполняется

insert into massmailingcontact ("ContactId","MassMailingId","EmailAddress","EmailId") VALUES ('C92EA9E7-CC76-4FA1-9CA4-5186AA32FC2A','8EC7B788-401E-40F0-9B82-05937B4399B0','cc-sib@yandex.ru','6EEC951D-C89A-42A2-9DE4-AFCA74A72B97') 

Надо в конце

insert.Execute();

или прямо:

...
.Set("EmailId", Column.Parameter("6EEC951D-C89A-42A2-9DE4-AFCA74A72B97")).Execute();

"Зверев Александр" написал:

Надо в конце

insert.Execute();

Спасибо, работает!

Еще вопрос вдогонку, как после Insert узнать id новой записи?

"Андрей Каспаревич" написал:

Здравствуйте, Илья!

Ссылки Вам в помощь:

http://www.community.terrasoft.ru/developer/article/7043

http://www.terrasoft.ru/bpmonlinesdk/ (раздел "Примеры")

http://www.community.terrasoft.ru/search/node/esq (можно найти примеры)

С уважением,

Каспаревич Андрей

Эксперт 3-й линии поддержки

В примерах www.terrasoft.ru/bpmonlinesdk/ "userConnection" с маленькой буквы написан и не хватает в примерах ...Execute(); Просьба добавить.

"Соколов Илья Андреевич" написал:Еще вопрос вдогонку, как после Insert узнать id новой записи?

Сначала сгенерировать функцией:

var NewRecordId = Guid.NewGuid();

а при вставке указать поле "Id" и это значение:

...
.Set("Id", Column.Parameter(NewRecordId))
...

"Соколов Илья Андреевич" написал:"userConnection" с маленькой буквы написан

Глобальный объект UserConnection доступен не везде. Если, например, пишем функцию в отдельной схеме вроде CommonUtilities, то его нужно передавать в функцию как параметр. При вызове функции будет с большой буквы, а параметр функции — с маленькой, чтобы не перепутать.

"Соколов Илья Андреевич" написал:В примерах www.terrasoft.ru/bpmonlinesdk/ "userConnection" с маленькой буквы написан и не хватает в примерах ...Execute(); Просьба добавить.

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

"Зверев Александр" написал:
Соколов Илья Андреевич пишет:

Еще вопрос вдогонку, как после Insert узнать id новой записи?

Сначала сгенерировать функцией:

var NewRecordId = Guid.NewGuid();

а при вставке указать поле "Id" и это значение:

...

.Set("Id", Column.Parameter(NewRecordId))

...

Использую код:

var NewRecordId = Guid.NewGuid();
var insert = new Insert(UserConnection).Into("ContactCommunication")
        .Set("Id", Column.Parameter(NewRecordId))
		.Set("Number", Column.Parameter("cc-sib@yandex.ru"));
 
var insert2 = new Insert(UserConnection).Into("massmailingcontact")
        .Set("ContactId", Column.Parameter("C92EA9E7-CC76-4FA1-9CA4-5186AA32FC2A"))
		.Set("MassMailingId", Column.Parameter("8EC7B788-401E-40F0-9B82-05937B4399B0"))
		.Set("EmailAddress", Column.Parameter("cc-sib@yandex.ru"))
        .Set("EmailId", Column.Parameter(NewRecordId));
insert2.Execute();

Получаю ошибку:

Date: 05.03.2014 13:19:34
Date (UTC): 05.03.2014 6:19:35
 
Exception Message: The INSERT statement conflicted with the FOREIGN KEY constraint "FK1al75M40naNJlg36z3fYVOHfw". The conflict occurred in database "BPMonline CRM", table "dbo.ContactCommunication", column 'Id'.
The statement has been terminated.
Exception Type: System.Data.SqlClient.SqlException
Exception Source: .Net SqlClient Data Provider
 
Exception Stack Trace:
   в System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
   в System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
   в System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose)
   в System.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady)
   в System.Data.SqlClient.SqlCommand.FinishExecuteReader(SqlDataReader ds, RunBehavior runBehavior, String resetOptionsString)
   в System.Data.SqlClient.SqlCommand.RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, Boolean async, Int32 timeout, Task& task, Boolean asyncWrite)
   в System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method, TaskCompletionSource`1 completion, Int32 timeout, Task& task, Boolean asyncWrite)
   в System.Data.SqlClient.SqlCommand.InternalExecuteNonQuery(TaskCompletionSource`1 completion, String methodName, Boolean sendToPipe, Int32 timeout, Boolean asyncWrite)
   в System.Data.SqlClient.SqlCommand.ExecuteNonQuery()
   в Terrasoft.Core.DB.DBExecutor.FailoverExecute[TResult](DbCommand command, Func`1 func)
   в Terrasoft.Core.DB.DBExecutor.Execute(String sqlText, QueryParameterCollection parameters)
   в Terrasoft.Core.DB.BaseInsert.Execute()
   в Terrasoft.WebApp.AccountsGridPageEventsProcessSchema`1.ScriptTask1Execute(ProcessExecutingContext context)
   в Terrasoft.Core.Process.ProcessScriptTask.InternalExecute(ProcessExecutingContext context)
   в Terrasoft.Core.Process.ProcessFlowElement.Execute(ProcessExecutingContext context)
   в Terrasoft.Core.Process.ProcessScriptTask.Execute(ProcessExecutingContext context, Func`2 internalExecute)
   в Terrasoft.WebApp.AccountsGridPageEventsProcessSchema`1.ProcessQueue(ProcessExecutingContext context)
   в Terrasoft.WebApp.AccountsGridPageEventsProcessSchema`1.OnExecuted(Object sender, ProcessActivityAfterEventArgs e)
   в Terrasoft.Core.Process.ProcessFlowElement.OnExecuted(ProcessActivityAfterEventArgs e)
   в Terrasoft.Core.Process.ProcessFlowElement.Execute(ProcessExecutingContext context)
   в Terrasoft.WebApp.AccountsGridPageEventsProcessSchema`1.ProcessQueue(ProcessExecutingContext context)
   в Terrasoft.WebApp.BaseGridPageEventsProcess`1.ThrowEvent(ProcessExecutingContext context, String message)
   в Terrasoft.WebApp.AccountsGridPageEventsProcessSchema`1.ThrowEvent(ProcessExecutingContext context, String message)
   в Terrasoft.UI.WebControls.PageSchemaUserControl.ThrowEvent(String message)
   в Terrasoft.WebApp.AccountsGridPageSchemaUserControl.ButtonKP1Click(Object sender, EventArgs e)
   в Terrasoft.UI.WebControls.Controls.ComponentAjaxEvent.OnEvent(Object sender, AjaxEventArgs e)
   в Terrasoft.UI.WebControls.Controls.Observable.FireAsyncEvent(String eventName, ParameterCollection extraParams)
   в Terrasoft.UI.WebControls.Controls.ScriptManager.RaisePostBackEvent(String eventArgument)
   в Terrasoft.UI.WebControls.Page.RaisePostBackEvent(IPostBackEventHandler sourceControl, String eventArgument)
   в System.Web.UI.Page.RaisePostBackEvent(NameValueCollection postData)
   в System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)
 
Form:
	submitAjaxEventConfig: {&quot;config&quot;:{&quot;viewStateMode&quot;:&quot;include&quot;,&quot;extraParams&quot;:{}}}
	__EVENTTARGET: ScriptManager
	__EVENTARGUMENT: PageContainer_AccountsModulePage_Grid_ButtonKP1|event|Click
	__VIEWSTATEFIELDCOUNT: 25
	__VIEWSTATE0: ...................................

Нужно было вызвать Execute для insert.
Кроме того, ContactCommunication — деталь раздела контактов и в новой записи нужно заполнить связь ContactId и другие стандартные поля.

"Зверев Александр" написал:

Нужно было вызвать Execute для insert.


спасибо добавляет и рассылка идет

А как выполнить из скрипта хранимую процедуру или функцию sql?

Екатерина, с примером вызова хранимой процедуры из скрипта Вы можете ознакомиться в процессе страницы BaseDuplicateMergeEditPage, а именно в OKButtonClickScriptTask:

…
var storedProcedure = new StoredProcedure(UserConnection, "tsp_MergeDuplicates");
storedProcedure.WithParameter(Column.Const(EntityPrimaryColumnValue));
storedProcedure.WithParameter(Column.Const(entitiesToMerge));
storedProcedure.WithParameter(Column.Const(Page.DataSource.Schema.UId));
storedProcedure.WithOutputParameter("return_value", dataValueTypeManager.GetInstanceByName("Integer"));
storedProcedure.WithOutputParameter("error_message", dataValueTypeManager.GetInstanceByName("Text"));
using (DBExecutor dbExecutor = UserConnection.EnsureDBConnection()) {
         dbExecutor.StartTransaction(System.Data.IsolationLevel.ReadUncommitted);
         Page.DataSource.ActiveRow.Save();
         storedProcedure.Execute(dbExecutor);
         if (storedProcedure.Parameters.Count > 0) {
                   int result = (int)storedProcedure.Parameters[0].Value;
                   string errorMessage = storedProcedure.Parameters[1].Value as string;
                   if (result != 0) {
                            dbExecutor.RollbackTransaction();
                            throw new Exception(errorMessage);
                   }
         }        
         dbExecutor.CommitTransaction();  
}
Показать все комментарии

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

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

В основу решения было положено создание двух таблиц в базе данных для ведения логов, что происходят с записью набора данных. Первая таблица 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 комментариев
Показать все комментарии

Здравствуйте! Требуется настроить планирование по работе менеджера. У менеджера есть задача обзвонить и назначить определенное количество встреч. Данная информация фиксируется через Задачи.
Как сделать настройку планирования, чтобы данная настройка отображалась в Разделе Планирования?
Пробовал сделать, но почему то не вышло.
Сделал запрос по задачам, создал настройку планирования - в поле показатель выбрал поле Количество,которые сформировано в запросе из Заголовка, выбрал функцию "Сумма".
Мне кажется, не совсем правильно выбрал показатель в настройке планирования.
При нажатии кнопки "Пересчитать факт" в разделе планирования выдает ошибку
Сообщение об ошибке: Ошибка открытия источника данных "". Оригинальное сообщение об ошибке: Operand data type nvarchar is invalid for sum operator
Как вывести в настройки планирования фактическое количество Задач в показатель? И чтобы по этому показателю можно было формировать плановые данные?

Спасибо.

Нравится

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

Все настроил. в Демо базе есть пример. Вопрос снимается

Добрый день. Подскажите, пож-ста, где можно глянуть пример? в какой версии TS?

Пример можно посмотреть в демо версии

Здравствуйте!
Настраиваю Планирование задач по примеру в демо версии. Смотрю количество задач по Ответственным. На детали Подробно для выбранного в списке ответственного отображается 4 задачи, при этом в колонке факт основной таблицы - 0,00.
Нажимаю "Пересчитать факт" ситуация сохраняется. В чем может быть причина?

Проблема решилась настройкой прав доступа.:smile:

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

Добрый день!

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

В частности, интересуют следующие вопросы:

1. Действительно ли в качестве основной таблицы можно использовать только "Средства связи контакта" или "Средства связи контрагента"?
2. Нужно ли делать фильтр по полю Код ("Код" содержит "EMAIL")?
3. Обязательно ли создавать дополнительные детали? Или можно вынести поле "ФИО" из обратной связи в основную деталь?
4. Нужно ли в свойствах запроса выбирать поле фильтрации?
5. Нужно ли в шаблоне сообщения для рассылки заполнять деталь "Получатели"?

Дело в том, что по всем пунктам у меня "Да", но рассылка не уходит (Состояние = Ошибка). При этом не рассылочная почта из Terrasoft'a уходит нормально.

Нравится

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

Если Вы когда-либо создавали запросы SelectQuery с CustomSQL-колонками, Вы наверняка обратили внимание на то, что запрос, прекрасно работающий под учетной записью администратора может оказаться нефункциональным под учетной записью пользователя, если в CustomSQLColumn используются таблицы, администрируемые по записям.

При этом обращение к тем же таблицам средствами дизайнера отчетов вполне успешно. В чем же секрет?

Дело в том, что если таблица администрируется по записям, дизайнер запросов при работе под пользователем автоматически подставляет вместо нее представление. А поскольку CustomSQL-колонки вставляются в запрос как есть, в результате у пользователя нет доступа к таблице, и есть доступ к предоставлению.

Схема работы:
selectquery

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

Есть еще один способ - сразу указать в CustomSQLColumn представление таблицы. Однако такой способ медленнее, кроме того, запрос станет нефункциональным, если Вы отключите администрирование по записям.

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

Нравится

Поделиться

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

"Alimova Anna" написал:

Есть еще один способ - сразу указать в CustomSQLColumn представление таблицы. Однако такой способ медленнее


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

Ну, а самое забавное начинается, когда таблица начинает администрироваться по полям =)

Максим, и что же начнется? :)

В случае добавления безобидной колонки Account.Name
И удалении прав на чтение этой колонки:

TSObjectLibrary.DBDataset: Ошибка открытия источника данных "ds_Account". 
Оригинальное сообщение об ошибке: The SELECT permission was denied on the column 'Name' of the object 'vw_Account", database 'XXX', schema 'dbo'

Это потому, что она ключевая? о_О

И еще: как это обойти?

Нет, это потому, что на нее доступ запрещен на уровне БД в самой таблице(и как следствие во вьюхе). Ядро в таком случае колонки заменяет на NULL, чтоб к ним вообще обращение не шло.
Пока мне известен один способ обхода - создавать отдельную функцию и использовать в CustomSQLColumn:

fn_GetAccountNameByID(Account.ID)
Показать все комментарии