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.
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;