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:

  1. 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.

  2. 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 of OrderLine instances belonging to it. The Order instance is located in Datasource; the OrderLine collection – in nested CollectionDatasource, which is created using the Order.lines attribute. If user changes some attribute of Order and creates a new instance, OrderLine. Then, when a screen is committed to DataManager, two instances – changed Order and new OrderLine – will be sent simultaneously. After that, they will together be merged into one persistent context and saved into the database on the transaction commit. The OrderLine instance is also contained in the Order.lines collection, but if it’s not passed into persistent context independently, the cascade merging between Order and OrderLines 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 the DsContext 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 the version 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.

  3. Declares two listeners: BeforeCommitListener and AfterCommitListener. 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 from DataManager can be obtained after commit in the AfterCommitListener 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 to CommitContext in BeforeCommitListener.

    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);
        }
    }