Table of Contents

Collections frequently asked questions

Why isn't my embedded collection sent to the server in a POST or PUT call?

Starting from Neos version 2.3, omitting a collection in a PUT request means that the collection should remain unchanged (see the documentation).
To avoid sending unnecessary data to the server (and to reduce the risk of concurrency issues), the Neos client only sends a collection if it has been modified.

This behavior also applies to POST requests: if no elements have been added to a collection, it won't appear in the payload. Omitting it is equivalent to sending an empty array, which matches the behavior in Neos versions prior to 2.3.

  • If you don't see the collection in a POST, it means it's empty.
  • If you don't see the collection in a PUT, it means it hasn't been modified on the client (no addition, update, or removal).
Note

A collection may also be omitted if it hasn't been loaded yet. See later in this article for information on disabling auto-loading.

How to load a collection only when it becomes visible

Since Neos 2.3, you can disable the automatic loading of a non-embedded for reading collection and manually trigger the loading at the desired time.
By default, auto-loading occurs when the active row changes or when the data is loaded/refreshed.

To disable auto-loading, simply uncheck the Auto load option on the subview corresponding to the collection.
To load the data manually, call:

await SubViews.[SubViewName].EnsureDataLoadedAsync();

This call can be placed, for example, in the code of an action that displays the collection, or in the setter of a Computed bound to the selected-index of a tabs component.

Note

In manual mode, the collection will never load automatically. You'll likely need to write a Retrieved rule that calls EnsureDataLoadedAsync if the UI element displaying the collection is visible during a data refresh.

Refer to the TechnicalDemos cluster menu entry:
Lazy loading with Entity Framework > Customers (EF lazy loading) for an example in the edit screen.

Why is my manually loaded and embedded for writing collection automatically loaded after a save?

This behavior occurs because the POST and PUT APIs return all properties that are exposed in POST, PUT, or GET responses.
Even if the collection wasn't included in the request payload (because it wasn't loaded), the server still includes it in the response.
As a result, the collection appears to be automatically loaded on the client side after the save operation.

Can I lose data if I save while a manually loaded collection hasn't been loaded yet?

No, you won't lose data.

  • For an embedded for writing collection that is not loaded, it will not be sent to the server, and no changes will be applied to it (see documentation).
  • For a non-embedded for writing collection, if there are no items in the Added, Deleted, or Modified states, no changes will be sent either.

How to detect if a collection is loaded?

Use the IsLoaded property on the datasource to determine if a collection is loaded.
Here is an example that returns true if the collection is loaded or if the parent item is new:

OrderUI? parent = GetParentItem<OrderUI>();
return Datasource.IsLoaded || (parent != null && parent.IsNew());

Refer to the TechnicalDemos cluster menu entry:
Lazy loading with Entity Framework > Orders (EF lazy loading) for an example in the edit screen.

Warning

It is possible to call any method on a datasource even if it hasn't been loaded.
It is the developer's responsibility to check Datasource.IsLoaded when necessary.

How to save a collection and its header in a single transaction if the collection is not embedded?

To support this scenario, embed the collection in the entity view and uncheck both Exposed in GET and (most likely) Loaded on GET.
This way, the collection is non-embedded for read operations, but embedded for write operations.

How to enable filtering on a collection with minimal performance impact

Let's take the example of an Order entity with a collection of OrderDetails. On the screen listing Orders, we want to allow users to filter orders that contain at least one OrderDetail with a ProductName equal to Apple.

One possible solution would be to define an expression property ProductNames in the OrderListView (the entity view used by the listing screen), using an expression like:

string.Join(';', e.OrderDetails.Select(e => e.ProductName))

This expression works for display purposes, but the string.Join is executed on the client side (C#, not SQL). Attempting to apply a filter such as ProductNames contains Apple results in an error. This is currently a known EF Core limitation, referenced here.

A more effective solution is to embed the OrderDetails collection within the OrderListView, including only the ProductName property. This allows the filter to be applied directly on ProductName. This approach is documented here.

With this setup, you can successfully filter on product names containing Apple, but the generated SQL query will look like this:

SELECT e1."Id"..., e2."Id"..., s."Id"...
FROM (
    SELECT e."Id"...
    FROM "Order" AS e
    WHERE EXISTS (
        SELECT 1
        FROM "OrderDetail" AS e0
        WHERE e."Id" = e0."OrderId" AND UPPER(UNACCENT(e0."ProductName")) LIKE '%Apple%')
    ORDER BY ...
) AS e1
INNER JOIN "Customer" AS e2 ON e1."CustomerId" = e2."Id"
LEFT JOIN "OrderDetail" AS s ON e1."Id" = s."OrderId"
ORDER BY ...

While the EXISTS subquery is effective for filtering, the entire collection is still fetched and returned to the client. This is far from optimal. For example, if there are 50 orders with 20 details each, the query will return 1,000 rows, even though only 50 orders are needed. With multiple collections, the result can become disastrous due to cartesian explosion.

Furthermore, even without filtering on ProductName, the OrderDetail table is still read and returned to the client:

SELECT e1."Id"..., e2."Id"..., s."Id"...
FROM "Order" AS e1
INNER JOIN "Customer" AS e2 ON e1."CustomerId" = e2."Id"
LEFT JOIN "OrderDetail" AS s ON e1."Id" = s."OrderId"
ORDER BY ...

Performance optimization

To minimize performance impact, it's highly recommended to uncheck both Exposed in GET and Loaded on GET for the embedded collection in the entity view.

Doing this will result in queries like:

SELECT e1."Id"..., e2."Id"...
FROM (
    SELECT e."Id"...
    FROM "Order" AS e
    WHERE EXISTS (
        SELECT 1
        FROM "OrderDetail" AS e0
        WHERE e."Id" = e0."OrderId" AND UPPER(UNACCENT(e0."ProductName")) LIKE '%Apple%')
    ORDER BY ...
) AS e1
INNER JOIN "Customer" AS e2 ON e1."CustomerId" = e2."Id"
ORDER BY ...

There is no longer any join on OrderDetail for returning data to the client. And if no filter is applied on ProductName, the OrderDetail table won't even appear in the generated query:

SELECT e1."Id"..., e2."Id"...
FROM "Order" AS e1
INNER JOIN "Customer" AS e2 ON e1."CustomerId" = e2."Id"
ORDER BY ...
Warning

This optimization applies only to GET operations. On POST or PUT, the collection will always be loaded if it's present in the entity view.