View RSS Feed

Development Team Blog

Inherited Constraints

Rate this Entry
Today we set out to answer the age old question of:

Should a child be judged by the sins of their parent?

In my previous article Using Expressions and Properties in Constraints we talked about how constraints are inherited. In addition to any constraints that may be applied directly in a main Data Dictionary Object (DDO) that main DDO will also acquire the constraints from its parent DDOs. If this is not clear, you might want to review that article before proceeding.

We will now discuss how a DDO can take advantage of inherited constraints and how when desired a DDO can take advantage of disabling this inheritance.

Consider the following requirement. We want to change our order entry view so that you can only select active customers for an order. We can do this by constraining on Customer.Status as follows:
Code:
  Object Vendor_DD is a Vendor_DataDictionary
  End_Object
   
  Object Invt_DD is a Invt_DataDictionary
      Set DDO_Server to Vendor_DD
  End_Object
   
  Object Customer_DD is a Customer_DataDictionary
      Procedure OnConstrain
          Constrain Customer.Status eq "Y"
      End_Procedure
  End_Object
   
  Object SalesP_DD is a Salesp_DataDictionary
  End_Object    // SalesP_DD
   
  Object OrderHea_DD is a OrderHea_DataDictionary
      Set DDO_Server to Customer_DD
      Set DDO_Server to SalesP_DD
  End_Object    // OrderHea_DD
   
  Object OrderDtl_DD is a OrderDtl_DataDictionary
      Set DDO_Server to OrderHea_DD
      Set DDO_Server to Invt_DD
      Set Constrain_File to OrderHea.File_Number
  End_Object    // OrderDtl_DD
   
  Set Main_DD to OrderHea_DD
  Set Server to OrderHea_DD
Try adding this to Order.vw in the Visual DataFlex Order Entry sample workspace. You will get the behavior you want for new orders. Create a new order and attempt to select customer 8. Do this by typing 8 and pressing the Find GE key (F9) or the Find GE Toolbar button (=). You will see that customer 8 is skipped and customer 9 is selected. Customer 8 is filtered (Customer.Status="N") and the next customer is found. No surprises here, this is exactly what youíd expect. Now try finding some orders. Clear your view, navigate to the order number form and start pressing the Find GT (F8) key or the Find GT button. Watch the order numbers as they go by. You will see that order number 108 is skipped. It turns out that order number 108ís customer is customer 8. Since the order header DDO inherits constraints from its parent Customer DD and the customer constraint fails, this order is filtered. (Note that in this example you should not use the prompt button to find records. The lookup objects have their own DDO structures so you will not see this filtering).

Is this filtering of orders based on a customer constraint desired? Maybe it is and maybe itís not. Letís pick another example where this is clearly less desirable. Assume you've been told to only allow the creation of order-detail lines if the unit price of the inventory item is greater than $100. Weíd do that as follows:
Code:
  Object Vendor_DD is a Vendor_DataDictionary
  End_Object 
   
  Object Invt_DD is a Invt_DataDictionary
      Set DDO_Server to Vendor_DD
      Procedure OnConstrain
          Constrain Invt.Unit_Price gt 100
      End_Procedure
  End_Object 
   
  Object Customer_DD is a Customer_DataDictionary
      Procedure OnConstrain
          Constrain Customer.Status eq "Y"
      End_Procedure
  End_Object 
   
  Object SalesP_DD is a Salesp_DataDictionary
  End_Object 
   
  Object OrderHea_DD is a OrderHea_DataDictionary
      Set DDO_Server to Customer_DD
      Set DDO_Server to SalesP_DD
  End_Object 
   
  Object OrderDtl_DD is a OrderDtl_DataDictionary
      Set DDO_Server to OrderHea_DD
      Set DDO_Server to Invt_DD
      Set Constrain_File to OrderHea.File_Number
  End_Object
If you run this view you will see that existing orders no longer display all of the detail records. Any detail record that has a parent inventory item with a unit cost that is less than $100 is filtered. This means that the detail items displayed will not add up to the displayed order total. While internally everything is fine and this is doing exactly what it should, you probably donít want this behavior.

In both of the above examples, we are having an issue because a parent constraint is inherited by the main DDO. We can stop this inheritance by setting the pbInheritConstraint property of the main DDO to false (when we refer to the "main DDO" we are talking about the DDO that performs the find). When you do this constraints are not inherited, which means that DDOs and their parent DDOs can apply different filters.
Code:
  Object Vendor_DD is a Vendor_DataDictionary
  End_Object
   
  Object Invt_DD is a Invt_DataDictionary
      Set DDO_Server to Vendor_DD
      Procedure OnConstrain
          Constrain Invt.Unit_Price gt 100
      End_Procedure
  End_Object
   
  Object Customer_DD is a Customer_DataDictionary
      Procedure OnConstrain
          Constrain Customer.Status eq "Y"
      End_Procedure
  End_Object
   
  Object SalesP_DD is a Salesp_DataDictionary
  End_Object
   
  Object OrderHea_DD is a OrderHea_DataDictionary
      Set DDO_Server to Customer_DD
      Set DDO_Server to SalesP_DD
      Set pbInheritConstraints to False
  End_Object
   
  Object OrderDtl_DD is a OrderDtl_DataDictionary
      Set DDO_Server to OrderHea_DD
      Set DDO_Server to Invt_DD
      Set Constrain_File to OrderHea.File_Number
      Set pbInheritConstraints to False
  End_Object
In our example all order-detail items will be displayed but you can now only add new detail items that have a inventory item unit cost above $100. What happens if you try to edit an order detail line? You can change the inventory item but you can only change it to an item with the appropriate unit cost. This new behavior feels right for order detail. With orders you can now find any existing order for any customer but you can only create new orders for active customers. In the case of this customer filter you may or may not want constraints to be inherited. This can be controlled on a DDO by DDO basis by setting pbInheritConstraints.

There is another, arguably more straight forward way, to inherit parent constraints. Rather than counting on the parent DD to supply the constraint you can add it directly to the main DDOís OnConstrain event. There is one catch to this. If you are applying a parent filter you must use the Constrain As construct. It will take the format of:
Code:
Constrain Main_Table_Name As (Parent constraint expression or whatever you want)
For example:
Code:
  Object OrderHea_DD is a OrderHea_DataDictionary
      Set DDO_Server to Customer_DD
      Set DDO_Server to SalesP_DD
      Set pbInheritConstraints to False
   
      Procedure OnConstrain
          Constrain OrderHea as (Customer.Status = "Y")
      End_Procedure
  End_Object
Here we are not inheriting constraints (so we donít care what the Customer DDO does) but we are creating a constraint on OrderHea that just happens to be based on information found in the parent.

As we know, Constrain As can be used for any type of expression. This makes the parent constraint just another type of Constrain As filter. If youíve read the article about Constraints and Performance you might be thinking that the use of Constrain As here is a bad idea because it cannot be optimized. Thatís true but it turns out it does not matter. When a constraint is inherited from a parent DDO, it cannot be optimized anyway. If you think about what we are doing, this makes sense. There is no way you can optimize the finding of your main DD record based on a filtered value of a related parent column. You are not using parentís index so there is no way to optimize it.

To summarize:

The default behavior of DDOs is to inherit their constraints from their parent DDOs. This can include the constraints that the parents might have inherited from their parent DDOs and so on. This normally works well but it can create conditions where the filtering of a record based on a parent DDO is not desired. Also, if you have very large DDO structures with many different constraints this inheritance can become difficult to manage.

Parent constraint inheritance can be disabled by setting pbInheritConstraints to false in your main DDO.

If inherited constraints are disabled you can still apply a parent filter by using Constrain As in the main DDO. There is no performance difference between using an inherited parent constraint and using a Constrain As in the main DDO.

Disabling inherited constraints with pbInheritConstraints and the use of parent filters with Constrain As are advanced features that can come in handy in the right situation.

Comments

  1. danwalsh46's Avatar
    I didn't know this property existed. Thanks, very useful.
  2. ivankaupa's Avatar
    Thank you John.

    You changed my DataFlex World! :-)