dataManager.save() vs dataContext.commit()

Can you give some guidance on when it is better to use dataManager.save() vs dataContext.commit() ?

My application has a screen (FinTxferBrowse2) that extends MasterDetailScreen. I want to be able to select multiple rows and update the chosen attribute(column) to a given value for each selected row.

I have a function onSetBtnCick that will loop through each selected row and update the attribute to a specified value if the if the associated checkbox is enabled. See code below:

My first attempt was to use dataContext.commit(), but I kept getting unfetched attiribute errors on the second iteration on the line where I called dataContext.merge.

So I switched to using dataManager.save() and it works, but I am confused.
What is the purpose of dataContext if I can achieve the same thing with dataManager ?

Why would I get unfetched attribute errors on the second iteration of executing dataContext.merge ?

Ampata Issue 5
Ampata Branch Issue-5

I am using jMix 1.2 and PostgreSQL 14.

@Subscribe("setBtn")
public void onSetBtnClick(Button.ClickEvent event) {
    String logPrfx = "onSetBtnClick";
    logger.trace(logPrfx + " --> ");

    List<GenNode> thisFinTxfers = table.getSelected().stream().toList();
    if (thisFinTxfers == null || thisFinTxfers.isEmpty()) {
        logger.debug(logPrfx + " --- thisFinTxfer is null, likely because no records are selected.");
        notifications.create().withCaption("No records selected. Please select one or more record.").show();
        logger.trace(logPrfx + " <-- ");
        return;
    }

    thisFinTxfers.forEach(thisFinTxfer -> {
        GenNode thisTrackedFinTxfer = thisFinTxfer;
        //GenNode thisTrackedFinTxfer = dataContext.merge(thisFinTxfer);
        if (thisTrackedFinTxfer != null) {

            if (tmplt_finTxact1_EI1_RoleFieldChk.isChecked()){
                thisTrackedFinTxfer.setFinTxact1_EI1_Role(tmplt_finTxact1_EI1_RoleField.getValue());}

            LocalDateTime idTs1 = thisTrackedFinTxfer.getIdTs() != null ? thisTrackedFinTxfer.getIdTs().getTs1() : null;
            if (tmplt_beg1Ts1FieldChk.isChecked()) {
                idTs1 = tmplt_beg1Ts1Field.getValue();
                thisTrackedFinTxfer.getBeg1().setTs1(idTs1);
                updateIdTs(thisTrackedFinTxfer);
            }

            Integer idX = thisTrackedFinTxfer.getIdX();
            if (tmplt_idXFieldRdo.getValue() != null){
                // Set
                if (tmplt_idXFieldRdo.getValue() == 1){
                    idX = tmplt_idXField.getValue();
                    thisTrackedFinTxfer.setIdX(idX);
                }
                // Max
                else if (tmplt_idXFieldRdo.getValue() == 2
                        && idTs1 != null) {

                    idX = getIdXMax(idTs1);
                    if (idX == null) return;
                    thisTrackedFinTxfer.setIdX(idX);
                }
            }

            Integer idY = thisTrackedFinTxfer.getIdY();
            if (tmplt_idYFieldRdo.getValue() != null){
                // Set
                if (tmplt_idYFieldRdo.getValue() == 1){
                    idY = tmplt_idYField.getValue();
                    thisTrackedFinTxfer.setIdY(idY);
                }
                // Max
                else if (tmplt_idYFieldRdo.getValue() == 2
                        && idTs1 != null) {

                    idY = getIdYMax(idTs1, idX);
                    if (idY == null) return;
                    thisTrackedFinTxfer.setIdY(idY);
                }
            }

            Integer idZ = thisTrackedFinTxfer.getIdZ();
            if (tmplt_idZFieldRdo.getValue() != null) {
                // Set
                if (tmplt_idZFieldRdo.getValue() == 1) {
                    idZ = tmplt_idZField.getValue();
                    thisTrackedFinTxfer.setIdZ(idZ);
                }
                // Max
                else if (tmplt_idZFieldRdo.getValue() == 2
                        && idTs1 != null) {

                    idZ = getIdZMax(idTs1, idX, idY);
                    if (idZ == null) return;
                    thisTrackedFinTxfer.setIdZ(idZ);
                }
            }

            if (tmplt_finStmt1_IdFieldChk.isChecked()){
                thisTrackedFinTxfer.setFinStmt1_Id(tmplt_finStmt1_IdField.getValue());}

            if (tmplt_finStmtItm1_Desc1FieldChk.isChecked()){
                thisTrackedFinTxfer.setFinStmtItm1_Desc1(tmplt_finStmtItm1_Desc1Field.getValue());}

            if (tmplt_finStmtItm1_Desc2FieldChk.isChecked()){
                thisTrackedFinTxfer.setFinStmtItm1_Desc2(tmplt_finStmtItm1_Desc2Field.getValue());}

            if (tmplt_finStmtItm1_Desc3FieldChk.isChecked()){
                thisTrackedFinTxfer.setFinStmtItm1_Desc3(tmplt_finStmtItm1_Desc3Field.getValue());}

            if (tmplt_finStmtItm1_DescAmtFieldChk.isChecked()){
                thisTrackedFinTxfer.setFinStmtItm1_DescAmt(tmplt_finStmtItm1_DescAmtField.getValue());}

            if (tmplt_finStmtItm1_RefIdFieldChk.isChecked()){
                thisTrackedFinTxfer.setFinStmtItm1_RefId(tmplt_finStmtItm1_RefIdField.getValue());}


            if (tmplt_finAcct1_IdFieldChk.isChecked()){
                thisTrackedFinTxfer.setFinAcct1_Id(tmplt_finAcct1_IdField.getValue());}

            if (tmplt_finDept1_IdFieldChk.isChecked()){
                thisTrackedFinTxfer.setFinDept1_Id(tmplt_finDept1_IdField.getValue());}
            
            if (tmplt_amtDebtFieldChk.isChecked()){
                thisTrackedFinTxfer.setAmtDebt(tmplt_amtDebtField.getValue());}

            if (tmplt_amtCredFieldChk.isChecked()){
                thisTrackedFinTxfer.setAmtCred(tmplt_amtCredField.getValue());}

            thisTrackedFinTxfer.setId2Calc(thisTrackedFinTxfer.getId2CalcFrFields());
            thisTrackedFinTxfer.setId2(thisTrackedFinTxfer.getId2Calc());

            Integer finTxactOption = updateColItemCalcValsTxactOption.getValue();
            if (finTxactOption == null){
                finTxactOption = 0;}
            Integer finTxsetOption = updateColItemCalcValsTxsetOption.getValue();
            if (finTxsetOption == null){
                finTxsetOption = 0;}
            updateCalcVals(thisTrackedFinTxfer, finTxactOption, finTxsetOption);

            //logger.debug(logPrfx + " --- executing metadataTools.copy(thisTrackedFinTxfer,thisFinTxfer).");
            //metadataTools.copy(thisTrackedFinTxfer, thisFinTxfer);

            //logger.debug(logPrfx + " --- executing dataContext.commit().");
           //dataContext.commit();

            logger.debug(logPrfx + " --- executing dataManager.save(thisFinTxfer).");
            dataManager.save(thisFinTxfer);

        }
    });

    //call dataContext.commit to sync the UI with the changes to the database
    logger.debug(logPrfx + " --- executing dataContext.commit().");
    dataContext.commit();
    //logger.debug(logPrfx + " --- executing finTxfersDl.load().");
    //finTxfersDl.load();

    //Loop throught the items again to update the id2Dup attribute
    thisFinTxfers.forEach(thisFinTxfer -> {
        GenNode thisTrackedFinTxfer = thisFinTxfer;
        //GenNode thisTrackedFinTxfer = dataContext.merge(thisFinTxfer);
        if (thisTrackedFinTxfer != null) {

            Integer id2Dup = thisTrackedFinTxfer.getId2Dup();
            //thisTrackedFinTxfer = dataContext.merge(thisFinTxfer);
            updateId2Dup(thisTrackedFinTxfer);
            Integer id2Dup2 = thisTrackedFinTxfer.getId2Dup();

            //thisFinTxfer.setId2Dup(thisTrackedFinTxfer.getId2Dup());

            //logger.debug(logPrfx + " --- executing dataContext.commit().");
            //dataContext.commit();
            if (id2Dup != id2Dup2){
                logger.debug(logPrfx + " --- executing dataManager.save(thisFinTxfer).");
                dataManager.save(thisFinTxfer);
            }
        }
    });


    logger.debug(logPrfx + " --- executing dataContext.commit().");
    dataContext.commit();

    logger.debug(logPrfx + " --- executing finTxfersDl.load().");
    finTxfersDl.load();

    table.sort("id2", Table.SortDirection.ASCENDING);
    table.setSelected(thisFinTxfers);

    logger.trace(logPrfx + " <-- ");
}

DataContext.commit() makes sense when your entities are already in the context, and they can be changed from UI components edited by the user or from some programmatic actions. It’s the case of editor screens and edited instance in the form of MasterDetailScreen.

The entities displayed in the table of MasterDetailScreen are not merged into DataContext when loaded - for performance reasons and to not interfere with the edited instance. So it makes little sense to use DataContext in your case. However, it shouldn’t lead to any exceptions on merge. Unfortunately, I couldn’t reproduce the issue on your project because it’s quite complex and I don’t have any data in the database. I would appreciate if you could provide test data or a simpler example, but first please try to reproduce with the latest framework 1.3.x.