Skip to main content

DataElement CRUD

CRUD stands for Create, Read, Update and Delete.

Each DataElement is expanded to provide these functionalities. Here we will explain how every function is implemented, and how you can customize them.


The create and modify methods take a single parameter:

public CrudsResult<DataRef> create(StarshipDetails details) {}
public CrudsResult<DataRef> modify(StarshipDetails details) {}

In case of the create method, the details parameter describes the data that should be created. The method will attempt to add a new record to the Starship table in the database. If it works, the method will return a DataRef, which can be used to link back to the created record.

In case of the modify method, the record will be retrieved based on the id in the details object and updated with the fields in details. It also returns a DataRef, pointing to the instance.

Details projection

Each DataElement has a details projection. This projection contains every field, except for the calculated fields. It is the default presentation used in most backend logic.


A DataRef is an object to encapsulate a reference to a DataElement instance. It usually contains the database id of the target instance.


Instead of throwing an exception when a validation fails or something goes wrong, methods return a CrudsResult or a variation of this in order to represent the error state.

When the result is a success, the CrudsResult wraps a return-value, which can be retrieved with the getValue()-method.

CrudsResult<DataRef> success = CrudsResult.success(DataRef.withId(0L));
success.isSuccess(); // returns true
DataRef dataRef = success.getValue();

When the result is an error, the CrudsResult can be defined with a number of error messages.

CrudsResult<DataRef> error = CrudsResult.error();
success.isSuccess(); // returns false
success.getValue(); // throws an exception



It is possible to retrieve data for a single instance with the getProjection method. The getDetails method is a shorthand for fetching the details projection in particular.

CrudsResult<T> getProjection(ProjectionRef projectionRef) {}
CrudsResult<StarshipDetails> getDetails(DataRef dataRef) {}


A ProjectionRef defines the target DataRef and the required projection.

new ProjectionRef("details", dataRef)

getProjection Result

The result class of the getProjection method depends on what projection is requested. However, this information is not included in the method signature. You will have to assign it to the correct class.

// Correctly cast to StarshipDetails
CrudsResult<StarshipDetails> result = agent.getProjection(new ProjectionRef("details", dataRef));
// Incorrectly cast, which can cause ClassCastExceptions when retrieving the value
CrudsResult<StarshipInfo> result = agent.getProjection(new ProjectionRef("details", dataRef));
Invalid DataRefs

Before Expanders version 5.18.0, the default behaviour of the getProjection() method was to return a success with an empty object when an invalid DataRef is provided. This could lead to unexpected results.


The find method allows you to search instances.

SearchResult<T> find(SearchDetails<S> searchDetails){}

For each Finder defined in the model, a details class is generated. These classes form the basis for the find method:

StarshipFindByLicensePlateEqDetails finder = new StarshipFindByLicensePlateEqDetails();


To search the database, a finder should be defined and wrapped with a SearchDetails object.

The SearchDetails object defines the projection in which the results should be returned and the paging. The paging defines how many results the query should return (default is 10).

StarshipFindByTypeEqDetails finder = new StarshipFindByTypeEqDetails();
finder.setType("ARC-170 starfighter");

SearchDetails<StarshipFindByTypeEqDetails> searchDetails = new SearchDetails<>(finder);
SearchResult<StarshipDetails> searchResult = find(searchDetails);

There are also some shorthands to define the SearchDetails object:

SearchDetails.fetchAllDetails(finder); // No limit, details projection
SearchDetails.fetchNDetails(finder, n); // Max n results, details projection
SearchDetails.fetchAllDataRef(finder); // No limit, details projection
SearchDetails.fetchNDataRef(finder, n); // Max n results, DataRef projection
SearchDetails.doCountOnly(finder); // No results, for if only the number of items is needed


The find method returns a SearchResult object. Like the CrudsResult, it is a way to return either a failed state or a success state. If a success, it can return the results and a total number of items that satisfy the query.

if (searchResult.isError()){

List<StarshipDetails> results = searchResult.getResults();
int totalNumberOfItems = searchResult.getTotalNumberOfItems();
Option<CarDetails> first = searchResult.getFirst();
Result Count

If the paging is defined to only return a limited amount of results (say 7), then the getTotalNumberOfItems-method still returns the real number of items even if it exceeds this limit (e.g. 100).

Also, if no items in the database satisfy the query, this is not considered an error state. The SearchResult will be a success with an empty result list and a total number of items of 0. The correct way of checking if the resultset is non-empty is:

boolean hasResult = searchResult.getTotalNumberOfItems() > 0;


boolean hasResult = searchResult.getFirst().isDefined();
Technical Finders

Sometimes it is useful to create a technical finder -- a finder used only in a backend to encapsulate a query, but without any UI/user-facing part.

For such cases, you can add finderOption noViewLayer. This will exclude the finder from appearing in the view layer.


Finally, the delete function allows you to delete a single instance.

public CrudsResult<Void> delete(DataRef target) {}


cruds.bulk.delete ApplicationApplicationInstance

Allows the user to select and delete multiple instances at once using ctrl+click, shift+click or double+click.

It also introduces a new method to remove a collection of instances:

public CrudsResult<Void> deleteMultiple(Collection<DataRef> targets);
cruds.softDelete Field

In case you want to be able to remove instances, but with the ability to recover those instances, it may be interesting to add a field with this option.

It overrides the default delete functionality to instead use this field as a field to filter out deleted instances. The deleted instances will still be present in the database, but not visible.



In the Logic Layer, you will often come across the ParameterContext class. Objects of this class wrap the actual parameter, which can be accessed with the getValue() method.

In addition to the value, it also contains a Context object. This object contains context information and can be extended with additional context.

E.g. the UserContext is always present:

Context context = parameterContext.getContext();
UserContext userContext = context.getContext(UserContext.class).getValue();

In case you are not sure whether the context information is ready, you can check the value first:

Option<ProcessingContext> processingContext = context.getContext(ProcessingContext.class);
if (processingContext.isDefined()) {

You can also extend the context, but note that the Context class is immutable and should be reassigned:

context = context.extend(new SpaceTravelContext());

To wrap a value in a ParameterContext, use one of the method on Context or ParameterContext: