Время создания
Filters

Is the below a valid multithreading pattern in Creatio when requiring to use a System User Connection, given that the script task (in my case) has access to a UserConnection?

var appConnection = (AppConnection)UserConnection.AppConnection;
Terrasoft.Core.Tasks.Parallel.ForEach(batchedCustomersToUpdate, new Terrasoft.Core.Tasks.ParallelOptions { MaxDegreeOfParallelism = maxParallelism }, (batch, state, currentBatch) => {
	var uc = appConnection.SystemUserConnection;
	
	foreach(var customer in batch) {
		var contact = uc.EntitySchemaManager
			.GetInstanceByName("Contact")
			.CreateEntity(uc);
		
		if (contact.FetchFromDB(customer.customerId)) {
			contact.SetColumnValue("UsrColumnName", customer.columnNameValue);
			contact.SetColumnValue("UsrDateColumnName", uc.CurrentUser.GetCurrentDateTime());
			contact.Save(false);
		}
	}
});

From what I can tell, I think appConnection.SystemUserConnection gets a new SystemUserConnection instance each time it is called, but that's only from having a brief look at the decompiled code based on the method names, so I might be wrong.

I cannot find any examples online of correct multi-threaded usage of User Connections in a similar way, just warnings that a single instance should not be used in multiple threads due to them not being threadsafe. The only documentation I could find was for a fire-and-forget method of running parallel background operations, but in my case I needed the launching code to know when all child processes had finished so it wasn't a great solution for me: https://academy.creatio.com/guides/dev/development-on-creatio-platform/back-end-development/data-operations-back-end/execute-operations-in-the-background/overview

The above code in a script task does appear to run fine & perform the expected updates, but I wanted to check to see if it might cause unexpected intermittent issues?

Like 0

Like

3 comments
Best reply

Short answer: yes, the pattern you posted is valid, and it is essentially the same approach the platform core uses internally. It should not cause intermittent issues on MSSQL or PostgreSQL. A few clarifications and recommendations below.

1. SystemUserConnection returns a shared instance, not a new one per call.
Your reading of the decompiled code is slightly off here. With default settings, AppConnection.SystemUserConnection lazily creates a single SystemUserConnection and returns that same object on every access. So all your parallel branches are working with one connection object.

2. That is fine, because SystemUserConnection is designed for concurrent use.
The class overrides how it obtains a DBExecutor. When the database engine does not use MARS (which is the default for both MSSQL and PostgreSQL in Creatio), the executor is cached per managed thread. Each Parallel.ForEach worker therefore gets its own DBExecutor and its own database connection, and Entity.Save starts and commits its transaction on that thread's executor only. The connection's internal caches are thread-safe collections. The core itself runs Parallel.ForEach loops that create entities on the shared SystemUserConnection, fetch them, and save them, exactly as your script task does.

3. Where the "not thread-safe" warning comes from.
It applies to a regular UserConnection, such as the one your script task is executing under. For a regular connection, the executor lookup will reuse any executor that currently has an open transaction, regardless of which thread owns it. Two threads calling Entity.Save on the same regular UserConnection can end up on the same executor mid-transaction. So the rule is: inside the parallel body, use only the system user connection, and never touch the process's own UserConnection from the worker threads. Your code already follows this.

4. Recommendations.

  • Keep using Terrasoft.Core.Tasks.Parallel rather than System.Threading.Tasks.Parallel. The Creatio wrapper initialises and resets the platform's logical call context for each branch, which you already have.
  • Keep creating Entity, Select, and EntitySchemaQuery objects inside the loop body, one per iteration. Do not share them between branches. Again, your code is already correct here.
  • Each worker holds a database connection while its batch runs. Keep MaxDegreeOfParallelism modest and well below the connection pool size, since regular user requests share the same pool.
  • Exceptions thrown inside branches are returned as an AggregateException from ForEach. Wrap the call if you want to log individual batch failures instead of failing the whole process.
  • Optionally, resolve appConnection.SystemUserConnection once before the loop and capture it. It makes no functional difference since the same instance is returned, but it reads more clearly.

5. On the background operations documentation.
You are right that Task.StartNewWithUserConnection is fire-and-forget and does not let the caller wait for completion. For a case where the launching code needs to know when all work is finished, Terrasoft.Core.Tasks.Parallel.ForEach with the system user connection, as you have it, is an appropriate choice.

Short answer: yes, the pattern you posted is valid, and it is essentially the same approach the platform core uses internally. It should not cause intermittent issues on MSSQL or PostgreSQL. A few clarifications and recommendations below.

1. SystemUserConnection returns a shared instance, not a new one per call.
Your reading of the decompiled code is slightly off here. With default settings, AppConnection.SystemUserConnection lazily creates a single SystemUserConnection and returns that same object on every access. So all your parallel branches are working with one connection object.

2. That is fine, because SystemUserConnection is designed for concurrent use.
The class overrides how it obtains a DBExecutor. When the database engine does not use MARS (which is the default for both MSSQL and PostgreSQL in Creatio), the executor is cached per managed thread. Each Parallel.ForEach worker therefore gets its own DBExecutor and its own database connection, and Entity.Save starts and commits its transaction on that thread's executor only. The connection's internal caches are thread-safe collections. The core itself runs Parallel.ForEach loops that create entities on the shared SystemUserConnection, fetch them, and save them, exactly as your script task does.

3. Where the "not thread-safe" warning comes from.
It applies to a regular UserConnection, such as the one your script task is executing under. For a regular connection, the executor lookup will reuse any executor that currently has an open transaction, regardless of which thread owns it. Two threads calling Entity.Save on the same regular UserConnection can end up on the same executor mid-transaction. So the rule is: inside the parallel body, use only the system user connection, and never touch the process's own UserConnection from the worker threads. Your code already follows this.

4. Recommendations.

  • Keep using Terrasoft.Core.Tasks.Parallel rather than System.Threading.Tasks.Parallel. The Creatio wrapper initialises and resets the platform's logical call context for each branch, which you already have.
  • Keep creating Entity, Select, and EntitySchemaQuery objects inside the loop body, one per iteration. Do not share them between branches. Again, your code is already correct here.
  • Each worker holds a database connection while its batch runs. Keep MaxDegreeOfParallelism modest and well below the connection pool size, since regular user requests share the same pool.
  • Exceptions thrown inside branches are returned as an AggregateException from ForEach. Wrap the call if you want to log individual batch failures instead of failing the whole process.
  • Optionally, resolve appConnection.SystemUserConnection once before the loop and capture it. It makes no functional difference since the same instance is returned, but it reads more clearly.

5. On the background operations documentation.
You are right that Task.StartNewWithUserConnection is fire-and-forget and does not let the caller wait for completion. For a case where the launching code needs to know when all work is finished, Terrasoft.Core.Tasks.Parallel.ForEach with the system user connection, as you have it, is an appropriate choice.

Thanks Oleg, very useful to have this information. Is there any way of safely using a non-System User Connection inside patterns such as the Parallel.ForEach so that any data changes appear as being performed by the instigating user, or is that not possible? I don't need it for this use case, but it's definitely something I've thought about in the past.

Harvey Adcock,

Good question. Yes, it is possible, and there are two ways to do it depending on how far "appears as being performed by the instigating user" needs to go.

Option 1: keep the system connection, set the audit columns explicitly.
If all you need is for ModifiedBy (and CreatedBy for inserts) to show the real user, you do not need a user connection at all. Entity.Save only fills the history columns when they have not been explicitly changed to a non-null value. So this works with your existing pattern:

var initiatorContactId = UserConnection.CurrentUser.ContactId; // captured before the loop

// inside the parallel body, on the system connection
contact.SetColumnValue("UsrColumnName", customer.columnNameValue);
contact.SetColumnValue("ModifiedById", initiatorContactId);
contact.Save(false);

This is the simplest and cheapest approach. Its limitation is that everything else still runs as the system user: access-rights checks, default record rights on inserts, and anything downstream that reads UserConnection.CurrentUser (business processes, event listeners, change log) will see Supervisor.

Option 2: one dedicated UserConnection per worker, logged in as the instigating user.
If you need the work to genuinely run in that user's context, create a separate connection per parallel branch. This is public API and is what the platform's own background-task infrastructure does internally:

string userName = UserConnection.CurrentUser.Name;
TimeZoneInfo timeZone = UserConnection.CurrentUser.TimeZone;
var appConnection = UserConnection.AppConnection;

Terrasoft.Core.Tasks.Parallel.ForEach(batches, options, batch => {
	var uc = new UserConnection(appConnection) {
		SessionId = Guid.NewGuid().ToString()
	};
	try {
		uc.Initialize();
		uc.Login(userName, timeZone, needRegisterSessionStart: false);
		foreach (var customer in batch) {
			var contact = uc.EntitySchemaManager.GetInstanceByName("Contact").CreateEntity(uc);
			if (contact.FetchFromDB(customer.customerId)) {
				contact.SetColumnValue("UsrColumnName", customer.columnNameValue);
				contact.Save(false);
			}
		}
	} finally {
		uc.Close(SessionEndMethod.Logout, needRegisterSessionEnd: false);
	}
});

Notes on this approach:

  • One connection per branch, not per record. Login reads the user's profile, culture and settings from the database and runs a licence check, so it is not free. Your batching already gives you one branch per batch, which is the right granularity.
  • The user must be active and licensed, otherwise Login throws. That is the same rule as for an interactive login.
  • Access rights are enforced for that user, which is usually what you want in this scenario, but it means saves can fail where the system connection would have succeeded.
  • needRegisterSessionStart: false and needRegisterSessionEnd: false stop the connection from appearing as a separate login in the user session log. Drop them if you want those sessions recorded.
  • Always close the connection in a finally block. Each one owns its own database executor and connection, and Close also clears its session-level caches.
  • Keep parallelism modest. Each branch holds a database connection from the shared pool for the duration of its batch.
  • The single-argument Login overload performs no password check. It is intended for trusted server-side code that has already authenticated the user, so never feed it a user name that comes from client input.

What not to do. Do not pass the process's own UserConnection into the worker threads. A regular UserConnection reuses any database executor that currently has an open transaction, regardless of which thread started it, so two parallel Entity.Save calls can end up interleaved on one executor. That is the real source of the "not thread-safe" warnings you found. A connection that is created and used by a single worker thread does not have this problem.

In short: for audit columns alone, set ModifiedById on the system connection. For true user context, create a fresh UserConnection per branch and close it when done.

Show all comments

Hi All, question please, is it possible in Creatio to :
1. Change Text color field automatically from business process

2. Or make font from normal to Bold automatically from business process

If it can't, maybe any method to make it happen?

I googling, found this :

I just want to confirm, or maybe you have workaround to do this?

The scenario is :

I have 4 data model, and each from the column is the same field.

I want to compare all, and if we find the different value between them, it will change the color to red, OR if not possible, the workaround is make the font Bold.

So the user can aware, and fast directly notice the different.

Thank you.

Like 0

Like

1 comments

Hi,

A business process only writes data. It cannot change color or font weight on the page. But the page can change its own styling based on data, so the pattern is: the process writes a "mismatch" flag, and the page reads that flag to style the field.

Step 1. Store the comparison result in a column

On the object shown on the page, add one column per compared field, for example UsrDobMismatch (Boolean). Optionally add a text column UsrDobColor that holds either auto or a hex color like #d32f2f.

The business process reads the four records (Read data x4), compares them with a Formula element, and writes the result with Modify data. Trigger it on record save or on a schedule, whatever fits your flow.

Step 2. Style the page from that column

Option A, no code at all. In the Freedom UI page designer, add two copies of the value element for each field. Make one plain and one bold with red color (the Label element has color and thickness settings in the designer). Then add page business rules: show the red element when UsrDobMismatch is true, show the plain one when it is false.

Option B, small code change. Show the value as a crt.Label instead of an input and bind the styling properties to attributes. Label properties support $Attribute binding, so if the process fills the color column, no handler is needed:

{
  operation: "insert",
  name: "SurveyDobLabel",
  values: {
    type: "crt.Label",
    caption: "$PDS_UsrSurveyDob",
    labelType: "body",
    labelColor: "$PDS_UsrDobColor",        // "auto" or "#d32f2f"
    labelThickness: "$PDS_UsrDobThickness" // "default" or "semibold"
  },
  parentName: "SurveyColumn",
  propertyName: "items",
  index: 1
}

If you prefer not to store styling values in the database, keep only the Boolean column and compute the color in a crt.HandleViewModelAttributeChangeRequest handler on the page. When the Boolean attribute changes, set a DobColor attribute to red or auto and bind labelColor to it.

One thing to watch: if the process runs after save, the page will show the new styling only after reload. If the users expect it instantly, do the comparison in the page handler instead of the process. The four values are already loaded on the page, so the handler can compare them directly and set the color attributes with no round trip.

Show all comments
Portfolio_Insights
Matrix
Grid_views
now_available
Syntech_Portfolio

We have released Syntech Portfolio Insights for Creatio 1.2.0, adding Matrix and Grid views for portfolio planning and analysis.

The new views allow users to visualize:

  • team and project capacity;
  • current workload;
  • resource allocation;
  • utilization by team, project, role, or time period.

Why we added the new views

Project lists are useful for accessing individual records, but portfolio decisions often require a broader perspective.

Portfolio managers need to understand not only the status of each project but also how resources and workloads are distributed across the organization.

Matrix and Grid views make it easier to compare planning data, identify capacity constraints, and analyze resource utilization without exporting information to separate spreadsheets.

Portfolio management inside Creatio

Syntech Portfolio Insights provides a centralized workspace for managing strategic initiatives, programs, projects, and investments.

In addition to the new Matrix and Grid views, the component includes:

  • Executive Portfolio Visibility - monitor initiatives, delivery status, risks, and investment allocation;
  • Portfolio Hierarchy View - navigate portfolios, programs, and projects using an interactive hierarchy;
  • Project Summary Preview - access progress, status, budget utilization, project manager information, and health indicators without opening the full record;
  • Contextual Team Communication - discuss portfolio updates, tasks, and project activities in one workspace;
  • No-Code Setup - configure the component inside Creatio and connect it to existing Portfolio, Program, and Project sections.

Release details

  • Version: 1.2.0
  • Release date: September 16, 2026
  • Compatibility: Creatio 8.3.0 and later
  • Deployment: cloud and on-site

Learn more and install the new version on the Marketplace:
https://syntech.digital/en/products/syntech-portfolio-insights-for-creatio 

We welcome your feedback on the new planning views.

Like 1

Like

Share

0 comments
Show all comments

Hello community,

the 10x version enable the list-base time selection for "Time" and "Date/Time" type fields.

Do you know how I can enable it?

 

Like 1

Like

1 comments

Hi,

I found, enable the feature "EnableDateTimeCalendarListPickerView"..

  1. Open the Feature toggling page
    Enter {CreatioURL}/0/Flags in the browser address bar, for example https://mycreatio.com/0/Flags. This is valid for Creatio 8.x and later.
    You need an administrator account.

  2. Find the feature
    Search the list for EnableDateTimeCalendarListPickerView (the Code column). The grid is long, so use the browser's find or the page filter.

  3. Switch it on
    Two scopes are available:

    1. State for current user — toggles the feature only for the account you're logged in with. Useful for testing. This rule takes higher priority than the rule applied to the user's group.

    2. Is enabled — the status for all users.
    3. Turn on the relevant switch and save. Front-end status changes require a browser page refresh to take effect; back-end changes do not.
  4.  If the feature isn't in the list
    A feature that hasn't been added to Creatio is treated as disabled. You can add it manually from the same page: create a new record, set Code to EnableDateTimeCalendarListPickerView, add a description, and save. Creatio will populate the Source property with "DbFeatureProvider" automatically. 
Show all comments
auto
filter
Studio_Creatio
8.0

Hi All, question please, so I want to auto filter (not static value) in List, filtered by field with data type Text. How to do that in Creatio, need advice please, thank you.

Capture :

I already tried, but I can not found the way. The only way I can found is Apply static filter from Properties, but it is not my expected. Capture :

I already tried with Business Process too, but the value is not my expected also, because it can not read specific filter result, the result is : showing all data.
Capture :

Need advice please, how to auto filter in List by parameter data type Text (not static filter) ?

Thank you.

Like 0

Like

1 comments

Hello!

The no-code designer cannot do this. The "Apply static filter" dialog only accepts constants or current-user macros, and a business process can populate data but cannot filter a list on a page. To filter a list by a Text field of the current record, you need a few lines of code in the page schema. This is the documented Creatio approach for dynamic list filters.

Every list on a Freedom UI page has a data source attribute in viewModelConfig.attributes with a filterAttributes array. You add your own filter attribute there, then set that attribute to a sdk.FilterGroup in a handler. Whenever the attribute changes, the list reloads with the new filter.

Step 1. Add the SDK dependency

At the top of the page schema (UsrPreScreening_FormPage or whatever your form page is named):

define("UsrPreScreening_FormPage", /**SCHEMA_DEPS*/["@creatio-devkit/common"]/**SCHEMA_DEPS*/, function/**SCHEMA_ARGS*/(sdk)/**SCHEMA_ARGS*/ {

Step 2. Register a filter attribute on the list's data source

Find the data source for your Application list in viewModelConfig.attributes. It will look something like GridDetail_xxxx_DS or DataGrid_xxxx_DS. Add a new entry to its filterAttributes:

"GridDetail_Application_DS": {

    "isCollection": true,

    "modelConfig": {

        "path": "GridDetail_Application_DS",

        "filterAttributes": [

            { "name": "GridDetail_Application_PredefinedFilter", "loadOnChange": false },

            { "name": "GridDetail_Application_ActiveFilter", "loadOnChange": true },

            { "name": "UsrCustomerNameFilter", "loadOnChange": true }

        ]

    }

}

Keep the existing entries; only the last line is new. Also declare the attribute itself next to the others:

"UsrCustomerNameFilter": {}

 

Step 3. Build the filter in handlers

Add two handlers to the handlers array. The first sets the filter when the page opens. The second rebuilds it when the user edits Customer Name. Replace the attribute names with the real ones from your page (PDS_UsrCustomerName_xxxx and PDS_UsrApplicationNumber_xxxx), and the column names with the real Application object column codes.

handlers: /**SCHEMA_HANDLERS*/[
    {
        request: "crt.HandleViewModelInitRequest",
        handler: async (request, next) => {
            await next?.handle(request);
            await applyCustomerNameFilter(request.$context);
        }
    },
    {
        request: "crt.HandleViewModelAttributeChangeRequest",
        handler: async (request, next) => {
            if (request.attributeName === "PDS_UsrCustomerName_xxxx" && !request.silent) {
                await applyCustomerNameFilter(request.$context);
            }
            return next?.handle(request);
        }
    }
]/**SCHEMA_HANDLERS*/,

 

Then define the helper above the return { ... } of the schema module:

async function applyCustomerNameFilter(context) {
    const customerName = context.PDS_UsrCustomerName_xxxx;
    const appNumber = context.PDS_UsrApplicationNumber_xxxx;
    const filter = new sdk.FilterGroup();
    filter.logicalOperation = sdk.LogicalOperation.And;
    if (customerName) {
        await filter.addSchemaColumnFilterWithParameter(
            sdk.ComparisonType.Equal, "UsrCustomerName", customerName);
    } else {
        // no name yet: show nothing instead of everything
        await filter.addSchemaColumnFilterWithParameter(
            sdk.ComparisonType.Equal, "Id", "00000000-0000-0000-0000-000000000000");
    }
    if (appNumber) {
        await filter.addSchemaColumnFilterWithParameter(
            sdk.ComparisonType.NotEqual, "UsrApplicationNumber", appNumber);
    }
    // required workaround: the filter must be a plain object to trigger reload
    const plainFilter = Object.assign({}, filter);
    plainFilter.items = filter.items;
    context.UsrCustomerNameFilter = plainFilter;
}

 

The last three lines are not optional. The list only reloads when the attribute receives a new plain object, so copying the FilterGroup this way is the standard trick.

Notes

  • Use sdk.ComparisonType.Contain instead of Equal if you want partial matches on the name.
  • Column codes in addSchemaColumnFilterWithParameter are Application object column codes, not page attribute names.
  • If you also want Date of Birth or ID Card matching, add more conditions the same way.
  • The static filter you configured in the designer can stay or be removed. Your code filter combines with it through the PredefinedFilter attribute.

Alternative without code

If you can change the data model, make Customer a lookup object instead of a Text field and reference it from both the pre-screening record and the Application object. Then the standard list-to-page binding by lookup column filters automatically. That is the only no-code option, but it only works if a shared lookup makes sense for your data.

Show all comments