Problems using datacontext.merge(T Entity)

Hi forum followers,

In my application there is an edit screen to register a payment composed by different invoices, as you can see on the next image:
Cobrament

When the user accepts the form, the application has to change the state and inform the payment date and the payment_id for every invoice.

Those actions are done on the edit screen controller:

@Subscribe
    private void onBeforeCommitChanges(BeforeCommitChangesEvent event) {
        //al collection container a través del botó Add, no caldria executar aquest codi.
        //Obtenir el cobrament del editor screen:
        Cobrament vCobrament = this.cobramentDc.getItemOrNull();
        if (vCobrament != null) {
            //Obtenció de les factures de proveïdor del collection container del editor screen:
            List<FacturaProv> facProvs = new ArrayList<FacturaProv>();
            facProvs = facts_provDc.getMutableItems();
            //Inicialització de variables pel bucle de factures:
            BigDecimal imptotcom = BigDecimal.ZERO;
            int cent = 100;
            BigDecimal divisor = new BigDecimal(cent);
            //Recorregut de les factures de proveïdor
              for (FacturaProv facPrv : facProvs) {
                  //Inicialització de variables per cada iteració:
                  BigDecimal amount = BigDecimal.ZERO;
                  //Obtenció de l'índex del Collection Container d'aquesta factura:
                  int idx = facts_provDc.getItemIndex(facPrv);
                  //Acumulem l'import total de comissió de totes les factures.
                  imptotcom = imptotcom.add(facPrv.getImportComissio());
                  //Fem un merge de la factura en el data context per a que faci la traça de si aquesta entitat ha sofert modificions per
                  //a que s'inclogui en el commit:
                  FacturaProv facPrvTrack = dataContext.merge(facPrv);
                  //Un cop l'entitat està en el tracking, es modifica l'atribut i el data context traçarà el canvi
                  facPrvTrack.setCobrament(vCobrament);
                  facPrvTrack.setDataCobrament(vCobrament.getData());
                  facPrvTrack.setEstat(FacProvEstatEnum.Cobrada) ;
                  //Tornem a col·locar l'entitat modificada en el collection container
                  //facts_provDc.getMutableItems().set(idx,facPrvTrack);
                  facts_provDc.replaceItem(facPrvTrack);
            }
            //Assignem els atributs de lectura Import Total Comissió i Import Diferència del cobrament:
            BigDecimal impdif = BigDecimal.ZERO;
            if (vCobrament.getImportCobrat() != null){
                impdif = vCobrament.getImportCobrat();
            }
            impdif = impdif.subtract(imptotcom);
            vCobrament.setImportTotComFac(imptotcom);
            vCobrament.setImportDifer(impdif);
            if (impdif.compareTo(BigDecimal.ZERO) != 0){
                notifications.create().withCaption("L'import cobrat (" + vCobrament.getImportCobrat().toString() + ") no coincideix amb la suma dels imports de comissió de les factures ("+ imptotcom.toString() + ")").show();
            }
        }
        // commit and resume action
        event.resume();
    }

The problem I encountered was with the datacontext.merge instruction. In every iteration, the datacontext.merge replace the state of the previous entity as you can see on the debugger session:
Pantalla%201

After pressing F8:

Pantalla%202

What am I doing wrong?. I understood the datacontext.merge(T Entity) instruction only affects the entity passed by parameter.

Thank you in advance

Hi Xavier,

This is not exactly true. A quote from the docs:

When you merge an entity, the whole object graph with the root in this entity will be merged. I.e. all referenced entities (including collections) will become tracked.

Could you reproduce your problem on a small test project? We are keen to fix possible bugs or usability issues, but we need a reproducible scenario.
Also, what CUBA version are you using?

Regards,
Konstantin

Hi Konstantin,

I’m debugging to discover in which point the datacontext.merge change the value of one attribute in the entity that was merged on the previous iteration and if it this change is coherent.

For another hand, I’ll try to create a test project, but it isn’t so easy because this project has dependencies with another one.

I’m using last version of Cuba Platform, 7.1.3, and last version of Cuba Studio, 12.3.

Debugging the code, I discovered that the problems with datacontext.merge are produced with an entity created by Cuba Platform and Cuba Studio v 6.x. I don’t know if this aspect could affect.

In a few days, I expect to have a test project.

Best regards,

I think this is a related issue. If not, feel free to fork this to a new thread. I’m attempting to create a more mobile friendly experience to replace the regular Table component. The idea is to have a vertical list of GroupBoxes, each with the details of an entity. But it seems that in moving away from the ListComponent interface, I’m losing some automated data management. I also think it has something to do with the merge() method.

Attached is an example project. Steps to reproduce:

  1. Create a new Master instance and save it. (This issue does not show when the Master is new)
  2. Edit the same Master and create a detail. Save.
  3. Edit that save detail, without committing the Master, putting some new value in the Notes field.
  4. Open the editor for that detail again. The changes made in the first edit do not appear when opened a second time.

I’ve traced the EditedEntity of the EditScreen and it seems to go into setupEditedEntity correctly and returns with the reverted value. It seems to be merge() that is causing it.

Likely, it is me not fully grasping the magic that is CUBA but I figured you’d want to know in case it is a bug. If it isn’t a bug, please point me in the right direction.

Helps if I actually upload the test…

dataContextMergeTest.zip (90.1 KB)

Hi Weston,
Thank you for the reproducible test, we have found a couple of issues:

The first one is particularly important in your case: the replaceItem method, which is used to update the collection container with the edited entity, didn’t affect the collection property, which lead to inconsistent graph: the master entity still had a reference to the old object. So DataContext couldn’t merge the graph correctly when you edited the same Detail entity next time.

Both issues will be fixed in 7.1.5 and 7.2.0. After that, your code should be as simple as that:

editAction.withHandler(e -> {
    Detail localDetail = detailsDc.getItem(detail);
    Screen screen = screenBuilders.editor(Detail.class, this)
            .editEntity(localDetail)
            .withParentDataContext(dataContext)
            .withContainer(detailsDc)
            .build();
    screen.addAfterCloseListener(f -> {
        if (f.getCloseAction().equals(WINDOW_COMMIT_AND_CLOSE_ACTION)) {
            listBuilder(detailsDc.getItems());
        }
    });
    screen.show();
})

Regards,
Konstantin

1 Like

Thank you for the response. I’m glad it wasn’t just me. :slight_smile: I know 7.2 is in beta so that will happen in the near future. Any timeline for 7.1.5?

Probably next week.

Just tested this with 7.2. Flawless. Thank you.

Hi, Konstantin

Sorry for the delay.

I’ve just migrated the project to Cuba Platform v 7.2.2 but the problem persists.

Here you are a test project that includes:

  1. Step by step.pdf – A guide to reproduce the problem
  2. The Cuba Platform project folder zipped
  3. A MS SQL SERVER 2008 database full backup file

Step by step.pdf (770.4 KB)

Best regards,

Logistica BBDD TEST.7z (2.2 MB) TestBBDD.7z (198.5 KB)

Xavier

Hi Xavier,

Today we are going to release 7.2.3 with lots of bug fixes. Could you test your project on that version when it is out and confirm the issue still exists? It would save me some time.

Regards,
Konstantin

1 Like

Of course, Konstantin.

You’re doing a great work with this magnific tool.

Regards,
Xavier

Hi Konstantin,

Unfortunately, I’ve just upgraded the project to release 7.2.3 but the problem persists.

Attached to this post, you would find all the material needed to reproduce the problem:

Step by step.pdf – A guide to reproduce the problem
The Cuba Platform project folder zipped ('Logistica BBDD Test.7z' file)
A MS SQL SERVER 2008 database full backup file ('BBDD Test.7z' file)

Best regards,

Xavier

Sorry, I’ve forgotten to attach the files.

XavierBBDD Test.7z (198.4 KB) Logistica BBDD TEST.7z (2.2 MB) Step by step.pdf (770.4 KB)

Hi Xavier,
Thank you for testing and confirmation.
I will deal with the problem in the next few days.

Regards,
Konstantin

Hi Xavier,
I cannot run your project because it requires your custom add-on com.company.mestrelegacy.
Could you provide its project too?

Regards,
Konstantin

Thank you, Konstantin, for the interest in looking for the origin of the trouble.

Here you are the required project.

Regards,

XavierMestreLegacy.7z (82.7 KB)

Hi Xavier,
I finally run your project and fixed it.

It’s hard to explain the root of the problem (it’s related to how DataContext merges complex object graphs), but the solution is simple and saves you from writing extra obscure code: just put into DataContext everything right after loading or creating it. After that, don’t do any merge, replaceItem and so on.

In your case, merge FacturaProv instances and add tracked instances to the data container as soon as you set a value in the debitNoteField field:

@Subscribe("debitNoteField")
private void onDebitNoteFieldValueChange(HasValue.ValueChangeEvent<FacturaComi> event) {
// ...
            if (dbnoteAct != null) {
                facts_provDc.getMutableItems().clear();
                for (FacturaProv facturaProv : dbnoteAct.getFacturesProveidor()) {
                    facts_provDc.getMutableItems().add(dataContext.merge(facturaProv));
                }
            }

Then in onBeforeCommitChanges method just set required values to these instances, DataContext will save them:

@Subscribe
private void onBeforeCommitChanges(BeforeCommitChangesEvent event) {
// ... 
        for (FacturaProv facPrv : facProvs) {
            // no DataContext.merge anymore, this is all you need:
            facPrv.setCobrament(vCobrament);
            facPrv.setDataCobrament(vCobrament.getData());
            facPrv.setEstat(FacProvEstatEnum.Cobrada) ;

            // no facts_provDc.replaceItem()
        }

The rule of thumb is “data containers should contain merged instances”.

Regards,
Konstantin

Hi, Konstantin.

I’m very grateful for your attention, the analysis of the problem and the lesson on how to use DataContext in this kind of situations.

I’ve just tested your solution and it works fine with a simple and clear code.

Thanks for all.

Regards,

Xavier