4.5.3.4. DsContext
All datasources that are created declaratively are registered in the DsContext
object which belongs to a screen. A reference to DsContext
can be obtained using the getDsContext()
method of a screen controller or via Controller Dependency Injection.
DsContext
is designed for the following tasks:
-
Organizes dependencies between datasources when navigation through a record set in one datasource (i.e. changing a "current" instance with the
setItem()
method) causes a related datasource to be updated. These dependencies allow you to organize master-detail relationships between visual components on screens.Dependencies between datasources are organized using query parameters with the
ds$
prefix. -
Collects all changed entity instances and sends them to Middleware in a single invocation of
DataManager.commit()
, i.e. to save them into the database in a single transaction.As an example, let’s assume that some screen allows a user to edit an instance of the
Order
entity and a collection ofOrderLine
instances belonging to it. TheOrder
instance is located inDatasource
; theOrderLine
collection – in nestedCollectionDatasource
, which is created using theOrder.lines
attribute. If user changes some attribute ofOrder
and creates a new instance,OrderLine
. Then, when a screen is committed to DataManager, two instances – changedOrder
and newOrderLine
– will be sent simultaneously. After that, they will together be merged into one persistent context and saved into the database on the transaction commit. TheOrderLine
instance is also contained in theOrder.lines
collection, but if it’s not passed into persistent context independently, the cascade merging betweenOrder
andOrderLines
at the ORM level should be set. Tight cascade relations at the ORM level sometimes cause unwanted consequences in unexpected places, so it is better to avoid them, as described in theDsContext
mechanism.As a result of committing the transaction,
DsContext
receives a set of saved instances from Middleware (in the case of optimistic locking they, at least, have an increased value of theversion
attribute), and sets these instances in datasources replacing old ones. It allows you to work with the latest instances immediately after committing without an extra datasource refresh that produces queries to Middleware and the database. -
Declares two listeners:
BeforeCommitListener
andAfterCommitListener
. They receive notifications before and after committing modified instances.BeforeCommitListener
enables to supplement a collection of entities sent to DataManager to save arbitrary entities in the same transaction. A collection of saved instances that are returned fromDataManager
can be obtained after commit in theAfterCommitListener
listener.This mechanism is required if some entities, with which a screen works, are not under control of datasources, but are created and changed directly in the controller code. For example, a visual component, FileUploadField, after uploading a file, creates a new entity instance,
FileDescriptor
, which can be saved together with other screen entities by adding toCommitContext
inBeforeCommitListener
.In the following example, a new instance of
Customer
will be sent to Middleware and saved to the database together with other modified screen entities when the screen is committed:protected Customer customer; protected void createNewCustomer() { customer = metadata.create(Customer.class); customer.setName("John Doe"); } @Override public void init(Map<String, Object> params) { getDsContext().addBeforeCommitListener(context -> { if (customer != null) context.getCommitInstances().add(customer); } }