Skip to main content
Solved

Custom Action Creates Duplicate Key Value


Forum|alt.badge.img+8
  • Captain II
  • 396 replies

Hi Guys,

I am working with 23R2.

I have encountered an error where my custom action, in an extension of the MoveEntry graph, gives me the error:

Cannot insert duplicate key row in object 'dbo.PressGlueLogHeader' with unique index 'PressGlueLogHeader_NoteID'. The duplicate key value is (b74bff6a-8e54-ef11-af2d-b81ea4b981b6, 2).
The statement has been terminated.

Below is my MoveEntry Extension:

public class MoveEntryQCExt : PXGraphExtension<MoveEntry>
{
    public static bool IsActive() => true;

    #region Actions
    public PXAction<AMBatch> StartQCChecks;
    [PXButton]
    [PXUIField(DisplayName = "Start QC Checks", MapEnableRights = PXCacheRights.Select, MapViewRights = PXCacheRights.Select)]
    protected virtual IEnumerable startQCChecks(PXAdapter adapter)
    {
        var moveEntry = PXGraph.CreateInstance<MoveEntry>();
        if(moveEntry == null)
        {
            return adapter.Get();
        }

        var qcEntry = PXGraph.CreateInstance<GCQualityControlEntry>();
        var move = Base.batch.Current;
        var record = qcEntry.QCCheck.Insert();
        record.NoteID = Guid.NewGuid();
        record.BatchNbr = move.BatNbr;
        record.DocType = move.DocType;
        record.Date = move.TranDate;
        qcEntry.QCCheck.Update(record);

        var details = qcEntry.Details.Insert();
        var tran = Base.transactions.Current;
        foreach (AMMTran lines in Base.transactions.Select())
        {
            details.AMProdOrdID = tran.ProdOrdID;  
            details.TranDate = tran.TranDate;
            qcEntry.Details.Update(details);
        }

        moveEntry.Actions.PressSave();
        qcEntry.Actions.PressSave();

        PXRedirectHelper.TryRedirect(qcEntry, PXRedirectHelper.WindowMode.New);

        return adapter.Get();
    }
    public PXAction<AMBatch> RecordPressGlueLogs;
    [PXButton]
    [PXUIField(DisplayName = "Record Press Glue Logs", MapEnableRights = PXCacheRights.Select, MapViewRights = PXCacheRights.Select)]
    protected virtual IEnumerable recordPressGlueLogs(PXAdapter adapter)
    {
        var moveEntry = PXGraph.CreateInstance<MoveEntry>();
        if(moveEntry == null)
        {
             return adapter.Get();
        }

        var logEntry = PXGraph.CreateInstance<GCPressGlueLogEntry>();
        var move = Base.batch.Current;
        var header = logEntry.Header.Insert();
        header.NoteID = Guid.NewGuid();
        header.BatchNbr = move.BatNbr;
        header.DocType = move.DocType;
        header.Date = move.TranDate;
        logEntry.Header.Update(header);

        logEntry.Actions.PressSave();
        moveEntry.Actions.PressSave();

        PXRedirectHelper.TryRedirect(logEntry, PXRedirectHelper.WindowMode.New);

        return adapter.Get();
    }
    #endregion

    #region Events
    protected virtual void _(Events.RowSelected<AMBatch> e, PXRowSelected b)
    {
        AMBatch row = e.Row;
        if (row == null) return;

        if (row.Status == "R")
        {
            StartQCChecks.SetEnabled(true);
            RecordPressGlueLogs.SetEnabled(true);
        }
    }
    #endregion
}

Both actions cause the same issue, the issue persists with and without the header/record.NoteID = Guid.NewGuid(); line.

 

Is there something I should do differently when extending the MoveEntry graph?

Does anyone know/have a clue about why this could be happening?

Any suggestions would be greatly appreciated!

Best answer by andriitkachenko

HI @aiwan 

Please check your DAC keys.

Sounds like your code creates several copies of the same record, which causes key clashes when inserting into the database.

You can test that theory by o looking at the contents of the Cache before Actions.PressSave(); is called on the breakpoint.

One quick thing - you don’t need to do .Update(header); on your newly inserted entity, all changes you’ve made are already recorded in the Cache. If there are issues with keys, it might assume the other entity is provided in the .Update(). And .Update() is configured to Insert the record instead if it is not found in the Cache.

View original
Did this topic help you find an answer to your question?

15 replies

Forum|alt.badge.img+8
  • Author
  • Captain II
  • 396 replies
  • August 7, 2024

Forgot to mention, the new record works fine with user entry.


andriitkachenko
Jr Varsity I
Forum|alt.badge.img+6

HI @aiwan 

Please check your DAC keys.

Sounds like your code creates several copies of the same record, which causes key clashes when inserting into the database.

You can test that theory by o looking at the contents of the Cache before Actions.PressSave(); is called on the breakpoint.

One quick thing - you don’t need to do .Update(header); on your newly inserted entity, all changes you’ve made are already recorded in the Cache. If there are issues with keys, it might assume the other entity is provided in the .Update(). And .Update() is configured to Insert the record instead if it is not found in the Cache.


Forum|alt.badge.img+8
  • Author
  • Captain II
  • 396 replies
  • August 7, 2024

Hi @andriitkachenko,

Thank you for your advice.

I added the below to my code and it seemed to work:

while (true)
{
    try
    {
        qcEntry.Actions.PressSave();
        break;  
    }
    catch (PXException ex) when (ex.Message.Contains("duplicate key row"))
    {
        // Generate a new NoteID and try again
        record.NoteID = Guid.NewGuid();
        qcEntry.QCCheck.Update(record);
    }
}

However, I will look at the cache contents and see if there are clashes.

I will also remove .Update() and see what that does.

 


andriitkachenko
Jr Varsity I
Forum|alt.badge.img+6

The chance that Guild.NewGuid() generated 2 same Guids in one method are close to 0, and if that happens consistently for you it tells me something’s wrong with the process.

Your solution helps, but:

  1. You don’t seem to save the updated record, meaning it gets lost (it might also tell Acumatica that there are unsaved changes, which would force a pop-up each time you’re trying to move to another record/screen or close the page)
  2. The 2nd record is still there, while it looks from the code snippets that you expect only 1 record to exist. This might cause table bloat and ghost records to exist, making it hard to work with

darylbowman
Captain II
Forum|alt.badge.img+13

You shouldn't need to specify a value for the NoteID at all. Set it to null and Acumatica will find one when it is inserted.

It doesn't look like anything is being changed in the 'moveEntry' graph. Why are you saving it?

Is there an index on the NoteID field in your table? Is it set to be unique?


Forum|alt.badge.img+8
  • Author
  • Captain II
  • 396 replies
  • August 7, 2024

Hi @darylbowman & @andriitkachenko 

 

Removing .Update() solved the issue.

 

I changed one of the actions to not include it resulting in the below:

public PXAction<AMBatch> RecordPressGlueLogs;
[PXButton]
[PXUIField(DisplayName = "Record Press Glue Logs", MapEnableRights = PXCacheRights.Select, MapViewRights = PXCacheRights.Select)]
protected virtual IEnumerable recordPressGlueLogs(PXAdapter adapter)
{
    var moveEntry = PXGraph.CreateInstance<MoveEntry>();
    if(moveEntry == null)
    {
         return adapter.Get();
    }

    var logEntry = PXGraph.CreateInstance<GCPressGlueLogEntry>();
    var move = Base.batch.Current;
    var header = logEntry.Header.Insert();
    header.BatchNbr = move.BatNbr;
    header.DocType = move.DocType;
    header.Date = move.TranDate;


    logEntry.Actions.PressSave();
    moveEntry.Actions.PressSave();

    PXRedirectHelper.TryRedirect(logEntry, PXRedirectHelper.WindowMode.New);

    return adapter.Get();
}

and this works as expected!

Thank you!

 

@darylbowman, I have just checked and there is no index on the table, should there be an index/how should i set one up.


Forum|alt.badge.img+8
  • Author
  • Captain II
  • 396 replies
  • August 7, 2024

@darylbowman I was getting the action working, I have added fields to the AMBatch which will display the Log entry no. and the qc check no.


darylbowman
Captain II
Forum|alt.badge.img+13

There's nothing wrong with how you had it, and it's how I would write it (to update before saving). However, upon insert, Acumatica should be assigning a NoteID and then immediately after, you're changing it to a new one. This effectively creates two records (I think). Have you tried removing that line?


Forum|alt.badge.img+8
  • Author
  • Captain II
  • 396 replies
  • August 7, 2024

@darylbowman with or without  the  record/header.NoteID = Guid.NewGuid(); it gave the error. It was my attempt at generating a unique one.


darylbowman
Captain II
Forum|alt.badge.img+13

If removing the Update() solves it, great. But I would say that it indicates an issue somewhere else.


Forum|alt.badge.img+8
  • Author
  • Captain II
  • 396 replies
  • August 7, 2024

You mentioned a Index on the NoteID field, my table doesnt have that, should i set this up?

Does it have to be clustered or can it be non-clustered?


andriitkachenko
Jr Varsity I
Forum|alt.badge.img+6

@aiwan each table has at least 1 index (clustered) if you’ve set the primary keys for your table properly.
There can only be 1 clustered index.

A non-clustered index is a specific-use-case tool, where you optimize a table for search by a specific set of fields. They can improve the speed of retrieval of records by the said set of fields, but inserting and updating speed suffers with each new index. So it’s an instrument to use with caution and only when the need arises.

I’d say you don’t need to bother about indexes right now. Again - please check your DAC declaration and table script. Outside of some niche cases, you should have the same set of keys both in the C# and SQL part (aside from CompanyID - as we don’t need to add it to the C# class, we can’t mark it with IsKey = true).

The fact that .Update() helped you to remove the issue reinforces my theory about keys not working as expected.

[PXDBGuid] has a logic to default the field to a new Guid, so I agree with Daryl, you don’t need to set this manually. But I don’t think that something breaks when you manually set it - it’s a redundant move, but not a code-breaking one.

If your DAC is meant to have a Guid as a key field, it should be defined like this in C#:

        #region YourGuid
        [PXDBGuid(IsKey = true)]
        [PXUIField(DisplayName = "Remove this attribute if you don't want to show it on the UI")]
        public Guid? YourGuid { get; set; }
        public abstract class yourGuid : BqlGuid.Field<yourGuid> { }
        #endregion YourGuid

And this should be your only Key field in the class.

In SQL, make sure your table has Primary Key - CompanyID + YourGuid


darylbowman
Captain II
Forum|alt.badge.img+13

Unclustered is fine. Use CompanyID and NoteID as the columns. Set IsUnique to Yes.


Forum|alt.badge.img+8
  • Author
  • Captain II
  • 396 replies
  • August 7, 2024

Thanks @darylbowman!


From:

        var record = qcEntry.QCCheck.Insert();
        record.NoteID = Guid.NewGuid();
        record.BatchNbr = move.BatNbr;
        record.DocType = move.DocType;
        record.Date = move.TranDate;
        qcEntry.QCCheck.Update(record);

To:

        var record = new WhateverTable(); // qcEntry.QCCheck.Insert();
        record.BatchNbr = move.BatNbr;
        record.DocType = move.DocType;
        //record.NoteID = Guid.NewGuid(); // Probably don't need, but can leave it in till the update works
        record.Date = move.TranDate;
        record = qcEntry.QCCheck.Update(record); // Could use Insert here

 


Reply


Cookie policy

We use cookies to enhance and personalize your experience. If you accept you agree to our full cookie policy. Learn more about our cookies.

 
Cookie settings