You can avoid large intermediate collections when an OCL expression creates or updates objects in a loop, especially when you only need the side effect and not a collect result.
Why this pattern becomes slow
collect evaluates an expression for every item in an input collection and returns a collection containing those expression results. Use it when you need those returned values.
A problem occurs when the expression both changes a collection and returns that growing collection. Consider an action expression such as:
Sequence{0..7000}->collect(a | vCollectionDetails.add(Detail.Create))Here, vCollectionDetails has type Collection(Detail). The intended work is to create Detail objects and add them to vCollectionDetails.
However, collect also retains the result of every evaluation. If vCollectionDetails.add(Detail.Create) returns the collection after the addition, the collected result contains progressively larger collections:
- the collection after the first
Detailis added; - the collection after the second
Detailis added; - the collection after the third
Detailis added; - and so on until the last iteration.
The result is a collection of collections. For an input of 7,000 items, this can retain approximately 7000 * 7000 / 2 object references in intermediate results. That allocation and copying work is unrelated to the intended result and can make the expression extremely slow.
Choose the correct loop operator
Use foreach when you need to perform work for each item but do not need a returned collection. Use collect when the returned collection is part of the result you need.
| Your goal | Use | Example result |
|---|---|---|
| Create or update objects for every input item; discard the per-item return value. | foreach
|
No collected result is built. |
| Transform every input item into a value that you will use afterwards. | collect
|
A collection of the transformed values. |
You must keep collect, but the expression currently returns a large object or collection.
|
collect with a small return value
|
A collection of small values, such as integers. |
Preferred solution: use foreach
When your only purpose is to populate vCollectionDetails, replace collect with foreach:
Sequence{0..7000}->foreach(a | vCollectionDetails.add(Detail.Create))This expresses the intent directly: perform the add operation once for each sequence item and do not build a collection from the expression's return values.
Alternative: return a small value from collect
If you need to retain collect for the surrounding expression, make the inner expression return a small scalar value rather than the growing collection:
Sequence{0..7000}->collect(a | vCollectionDetails.add(Detail.Create); 0)The semicolon evaluates the add operation and then returns 0. The resulting collection contains one zero for each input item, rather than a series of increasingly large Collection(Detail) values.
For example, if the input sequence has 7,000 items, the result is a collection of 7,000 zeros. The created Detail objects are still added to vCollectionDetails, but collect no longer retains the growing collection after each addition.
Diagnose an existing slow expression
- Find uses of
->collect(...)in actions or ViewModel expressions that are slow. - Inspect the last value returned by the inner expression. In
vCollectionDetails.add(Detail.Create), the returned value can be the collection being changed. - Ask whether the caller uses the result of
collect. - If the result is not needed, replace
collectwithforeach. - If the result must remain, end the inner expression with a small value that represents the required result, such as
0in the example above. - Re-test with a realistic number of input items. A pattern that appears acceptable with a few items can become expensive as the returned collections grow.
Keep transformation and mutation separate
Use collect for a transformation whose results you need. For example, this collects customer names:
Customer.allInstances->collect(c | c.Name)Use foreach for repeated work whose return value you do not need. This distinction prevents an accidental, large intermediate result from becoming part of an action.
When the slow expression is in a ViewModel, also review Documentation:QueryPlan and Documentation:Efficient ViewModel fetch to ensure that the input collection is not unnecessarily large. Documentation:Delayed Fetch addresses when ViewModel data is fetched; it does not remove the cost of constructing an unwanted collect result.
