Master-Detail Relationship Between Data with PXDBDefault and PXParent

To set up the master-detail relationship between data access classes, you have to add two attributes, PXDBDefault and PXParent, to the DAC fields of the detail class. You can specify these attributes directly in the DAC or within a graph (in the CacheAttached event handler).

PXDBDefault

The PXDBDefault attribute specifies the default value for a data field. You should use this attribute to insert the default value, which is the foreign key to the master DAC.

PXDBDefault works similarly to PXDefault and obtains its value from the Current property of the PXCache object that holds data records of the specified class. However, the PXDBDefault attribute is specially intended to insert the default value that is the key to the parent record. Unlike PXDefault, the PXDBDefault attribute supports the identity key field of the master DAC and inserts the actual default value after the parent record is saved to the database.

If you implement a master-detail relationship, you should use the PXDBDefault attribute to bind the detail data record fields (foreign key fields) to the master data record key fields. If the master data record is new and uses the identity key generated by the database, its key field will be set to a real value only when the master record is saved to the database. So if a detail data record is created before the master data record is saved for the first time, the detail data record field will be set to the temporary value of the master identity field. However, the PXDBDefault attribute will replace the temporary value with the actual one when the detail data record is saved to the database.

As the following example code shows, in the SupplierProduct class, the PXDBDefault attribute obtains the default value for its SupplierID key field from the Supplier.SupplierID field of the current master record.

//SupplierProduct.SupplierID
...
[PXDBDefault(typeof(Supplier.supplierID))]
public virtual int? SupplierID
{...
}

PXParent

The PXParent attribute specifies the master-detail relationship between classes.

Note: If you calculate aggregate values by using the PXFormula or PXUnboundFormula attribute for the master DAC, you also have to add PXParent to one of the fields of the detail DAC.

We recommend that you add the PXParent attribute to the first foreign key field of the child DAC (although it is possible to add the attribute to any field). Because the attribute specifies the master-detail relationship between classes, it enables cascading deletion of the child records once a parent record is deleted.

The parent data record is defined by the BQL Select<> statement specified in the attribute. Typically, the query includes a Where<> clause that adds conditions for the parent’s key fields to equal the child’s key fields. In this case, to specify the values of the key fields of the child data record, you use the Current parameter.

In the following code example, PXParent specifies the current Supplier record as the parent for the SupplierProduct record. The PXParent attribute is added on the SupplierProduct.SupplierID field in the SupplierProduct DAC.

//SupplierProduct.SupplierID
...
[PXParent(
    typeof(SelectFrom<Supplier>.
               Where<Supplier.supplierID.IsEqual<SupplierProduct.supplierID.FromCurrent>>))]
public virtual int? SupplierID
{...
}

We recommend that you ensure that the PXParent declaration satisfies the following rules:

  • Instead of using the Select query, you should declare the foreign key class and use it in the PXParent constructor. An example is shown in the following code, which is a part of the SOPickingWorksheetLine DAC located in the PX.Objects.SO namespace.
    public static class FK
    {
        public class Worksheet : SOPickingWorksheet.PK.ForeignKeyOf<SOPickingWorksheetLine>.By<worksheetNbr> { }
        ...
    }
    
    #region WorksheetNbr
    [PXDBString(15, IsUnicode = true, IsKey = true, InputMask = "")]
    [PXDBDefault(typeof(SOPickingWorksheet.worksheetNbr))]
    [PXUIField(DisplayName = "Worksheet Nbr.", Visible = false, Enabled = false)]
    [PXParent(typeof(FK.Worksheet))]
    public virtual String WorksheetNbr { get; set; }
    public abstract class worksheetNbr : PX.Data.BQL.BqlString.Field<worksheetNbr> { }
    #endregion

    For details on declaring foreign key classes, see To Define a Foreign Key.

  • If you need to specify a DAC that is not related to the current DAC via the foreign key, you should specify it in the additionalCondition predicate of the PXParent attribute constructor.
  • If you cannot use the foreign key class, you should declare the Select query according to the following recommendations:
    • The Select query for the PXParent attribute should reference fields declared in the current DAC. If the query references fields declared in the base class, you should again declare the fields with the new keyword in the current class.
    • The Select query for the PXParent attribute should reference only DACs that have the child or parent role in the implemented relationship.

      If you need to use a DAC other than the child or parent in the Select query, you should specify it in the additionalCondition predicate of the PXParent attribute constructor.

    • In the Select query, you should not use unparametrized queries—that is, queries without a reference to the current value of a child DAC field in the cache (FromCurrent or Current<T> references). For details, see Parameters in Fluent BQL.

Selection of the Master-Detail Data

To select master-detail data, you define two data views in the graph (see the code below). In this code example, the master data view selects data records of the Supplier class, while the detail data view selects records of the SupplierProduct class. To select the details for a particular master record, you specify the master key field in the Current parameter of the data view type. In the Current parameter, MYOB Acumatica Framework inserts the value from the Current property of the PXCache object that works with the specified DAC.

The order of data views in the graph defines the order of the insertion, update, and deletion of data records in the database. The framework inserts and updates data records in the order in which the data views are defined, and deletes the records in the reverse order. Thus, you have to define the master data view before the detail data view to enabledetails to be saved and deleted correctly. (The master data record is the first to be inserted in the database and the detail data records are the first to be deleted from the database.)

// Retrieves master records
public SelectFrom<Supplier>.View Suppliers;
// Retrieves detail records by the Supplier.SupplierID of the current master record
public SelectFrom<SupplierProduct>.
    LeftJoin<Product>.On<Product.productID.IsEqual<SupplierProduct.productID>>.
    Where<SupplierProduct.supplierID.IsEqual<Supplier.supplierID.FromCurrent>>.View
    SupplierProducts;