Wednesday, January 29, 2014

The power of BPath

This one is for the CRM developers out there. A very specific topic this time: BPath. What is it? BPath is a query language from which you can access data and objects in the Business Object Layer by specifying commands in a character string. It doesn't allow for things you cannot do otherwise but it certainly allows you to do things in a much more compact and code-efficient way.

The basics

The first examples are based on the following data model containing two Business Objects, one representing an account and the other one the related phone information. Check transaction GENIL_MODEL_BROWSER to look up all entity definitions and relations including the names of attribute-structures. More on the Business Object Layer and useful tools can be found here.

Let's start with some simple BPath examples:

  "Read a single telephone number of an account
  data lv_phone_ref type ref to crmst_telephone_buil-telephone.
  lv_phone_ref ?= lr_builheader->get_properties_by_bpath(
    `BuilIndependantPhoneRel/@TELEPHONE` ).
  if lv_phone_ref is bound.
    write / lv_phone_ref->*.

  "Read all phone attributes

  data ls_phone_ref type ref to crmst_telephone_buil.
  ls_phone_ref ?= lr_builheader->get_properties_by_bpath(
    `BuilIndependantPhoneRel/*` ).
  if ls_phone_ref is bound.
    write: / ls_phone_ref->*-telephone, ls_phone_ref->*-country.

  "Read all attributes of all existing phone entities

  types tt_phone type standard table of crmst_telephone_buil with default key.
  data lt_phone_ref type ref to tt_phone.
  lt_phone_ref ?= lr_builheader->get_properties_by_bpath(
    `BuilIndependantPhoneRel/*$` ).
  if lt_phone_ref is bound.
    loop at lt_phone_ref->* assigning <ls_phone>.
      write: / <ls_phone>-telephone, <ls_phone>-country.

The GET_PROPERTIES_BY_BPATH-method in the above examples is used to read one or more properties of the entity BuilIndependantPhone which is accessed by following relation BuilIndependantPhoneRel from source entity BuilHeader. The specified return type is a very generic one: REF TO DATA. The actual return type depends on whether you request a:
  • single attribute (@TELEPHONE)
  • structure with all attributes (*) 
  • table with all attributes from several entities (*$)
  • table with a single attribute from several entities (@TELEPHONE$)

Multiple relations

One of the powers of BPath is the ability to follow several relations in one go:

  "Follow several relations
  data lr_btstatus type ref to cl_crm_bol_entity.
  lr_btstatus = lr_btorder->get_related_entities_by_bpath(
    `BTOrderHeader/BTHeaderStatusSet/BTStatusHCurrent/*$` ).

Notice that a different method is used this time: GET_RELATED_ENTITIES_BY_BPATH. This method returns a collection of entities. Remember to use the $-character to retrieve all entities otherwise the collection will never contain more than 1 entry! Also note that you have to specify the names of the relations in order to retrieve all relevant entities.


Another great aspect of BPath is the possibility to apply filters in a simple manner. Let me demonstrate by giving another example:

  "Get the standard telephone entity from the standard address
  data lr_phone type ref to cl_crm_bol_entity.
  lr_phone = lr_builheader->get_related_entities_by_bpath(
     BuilStandardAddressPhoneRel[@STD_NO="X"]/*` )->get_first( ).

This time only telephone entities are returned for which the attribute STD_NO has the specified value and from the resulting collection the first match is directly stored as an entity. This is powerful stuff! You can retrieve directly the entity you need without using any collections or iterators. And although the collection may be empty, it is always instantiated making it save to directly request the first one. 

  "It's possible to filter on any relation along the path
  data lr_phones type ref to if_bol_entity_col.
  lr_phones = lr_builheader->get_related_entities_by_bpath(
     BuilAddressPhoneRel[@VALIDTODATE>=#20140101#]/*$` ).

Note that string values are written between double quotes and dates between #.

Logical AND/OR

Use a "&" for a logical AND and the "|" for a logical OR. 

  "Retrieve the current roles
    `BuilRolesRel[(@VALID_FROM<=Today())&(@VALID_TO>=Today())]/@PARTNERROLE$` ).

  "Check whether an account has role A or B 

  check lr_builheader->get_properties_by_bpath(
    `BuilRolesRel[(@PARTNERROLE="A")|(@PARTNERROLE="B")]/*` ) is bound.

You might run into limitations when using more than 2 conditions. See also BPath: Filter by multiple criteria. Workaround is using additional parentheses like this:


And much more

Since BPath operates on a string containing the BPath commands it combines perfectly with the new possibilities of string templates. When used together they enable compact yet advanced queries:

  "Check whether there is a relation with a specific partner
  check lr_builheader->get_properties_by_bpath(
    |BuilRelationshipRel[@PARTNER1="{ lv_partner }"]/*| ) is bound.

Of course much more is possible with BPath. This blog mostly covers the ways in which I have used it. Check the blog series from Jürgen Gatter to learn all the details of BPath.