Dear ABAPers,
Probably most of you are aware of persistent classes, meanwhile I don't know many developers who're actively using them.
Moreover, I just get a response from SAP:
I would propose to avoid the usage of
persisten classes in projects where the underlying structure of the
persistent class is not stable. Generating new persitent classes
is easy, but maintenance of excisting persitent classes could led in
high effort. For this reason I would propose to take the future
maintenace effort into account when you decide to use or not to use
persistent classes.
After spending 2 years on standard TR-TM solution support, that is persistent classes based, I don't think it's such a bad thing.
What I want to reach with my blog is to present my personal ideas about how we can make usage of persistent clasess more attractive.
Let's speak today about query service:
So every persistent class has embedded interface if_os_ca_persistency. Details are here:
Query Service Components - ABAP - Object Services - SAP Library
This interface has very interesting method: get_persistent_by_query
In fact once generating a class for a table we automtically have query service for it - that sounds promising. Let's go to example:
Here is a code from a standard DEMO_QUERY_SERVICE program.
agent = ca_spfli_persistent=>agent. TRY. query_manager = cl_os_system=>get_query_manager( ). query = query_manager->create_query( i_filter = `AIRPFROM = PAR1 AND AIRPTO = PAR2` ). connections = agent->if_os_ca_persistency~get_persistent_by_query( i_query = query i_par1 = airpfrom i_par2 = airpto ). LOOP AT connections ASSIGNING FIELD-SYMBOL(<connection>). connection = CAST #( <connection> ). result-carrid = connection->get_carrid( ). result-connid = connection->get_connid( ). APPEND result TO results. ENDLOOP. cl_demo_output=>display( results ). CATCH cx_root INTO exc. cl_demo_output=>display( exc->get_text( ) ). ENDTRY.
What we can see here is that SAP developers suggest us to use a generic request from a string.
Which critical things I see in this example:
- we have only 3 parameters
- there is no reference to DDIC structure that means where-used-list will not work for this statement
- select-options and ranges are not supported
So after that I decided that if we transform the same example into the code like this we can make things simplier:
REPORT ZDEMO_QUERY_SERVICE. tables: spfli. parameters: p_from type spfli-airpfrom, p_to type spfli-airpto. select-options: so_carid for spfli-carrid. start-of-selection. types: begin of query_ts, airpfrom type spfli-airpfrom, airpto type spfli-airpto, carrid type range of spfli-carrid, end of query_ts. data(connections) = zcl_os_api=>select_by_query( exporting io_agent = ca_spfli_persistent=>agent " Class-Specific Persistence Interface is_selection = value query_ts( airpfrom = p_from airpto = p_to carrid = so_carid[] ) ).
As you can see from the example I represented query not like a single string, but as local variable of structure type where fields have same names as in source table. Moreover, to support multiple selection, you can define parameter as a range (CARRID).
To perform range selection I decided to convert range to a set of OR statements ( SIGN = 'I' ) + set of AND statements (SIGN = 'E').
This simple class now let me easily generate simple classes for selection.
1) Generate persistent class
2) Define local variable for query
3) Call query with agent and query structure.
The provided class is just a prototype. If you wish - you can copy it and try to use it.
Please read the continue in next posts:
- Persistent classes: single get( ) method instead of multiple get_xxx() methods calls
- Persistent classes: hashing generic query requests
The most actual version of a class is here: ZCL_OS_API
Enjoy =)
class ZCL_OS_API definition public abstract final create public . public section. class-methods SELECT_BY_QUERY importing !IO_AGENT type ref to IF_OS_CA_PERSISTENCY !IS_SELECTION type ANY changing !CO_TYPE type ref to CL_ABAP_STRUCTDESCR optional returning value(RT_RESULT) type OSREFTAB . protected section. private section. types: begin of range_ts, sign type c length 1, option type c length 2, low type string, high type string, end of range_ts . class-methods GET_QUERY_RANGE_VALUE importing !IS_RANGE type RANGE_TS !IO_EXPR type ref to IF_OS_QUERY_EXPR_FACTORY !IV_NAME type STRING returning value(RO_EXPR) type ref to IF_OS_QUERY_FILTER_EXPR . class-methods GET_QUERY_RANGE importing !IT_RANGE type TABLE !IV_NAME type STRING !IO_EXPR type ref to IF_OS_QUERY_EXPR_FACTORY returning value(RO_EXPR) type ref to IF_OS_QUERY_FILTER_EXPR . type-pools ABAP . class-methods IS_RANGE importing !IO_TYPE type ref to CL_ABAP_TABLEDESCR returning value(RV_RANGE) type ABAP_BOOL . class-methods GET_QUERY importing !IS_SELECTION type ANY changing !CO_TYPE type ref to CL_ABAP_STRUCTDESCR optional returning value(RO_QUERY) type ref to IF_OS_QUERY . ENDCLASS. CLASS ZCL_OS_API IMPLEMENTATION. method get_query. data(lo_query) = cl_os_query_manager=>get_query_manager( )->if_os_query_manager~create_query( ). data(lo_expr) = lo_query->get_expr_factory( ). if co_type is not bound. try. co_type = cast #( cl_abap_typedescr=>describe_by_data( is_selection ) ). catch cx_sy_move_cast_error. " ToDo: message return. endtry. endif. data: lt_and type table of ref to if_os_query_filter_expr. " for each selection criteria loop at co_type->get_included_view( * p_level = ) into data(ls_view). " parameter or range? case ls_view-type->kind. " parameter when ls_view-type->kind_elem. field-symbols: <lv_component> type any. unassign <lv_component>. assign component ls_view-name of structure is_selection to <lv_component>. check <lv_component> is assigned. try. " goes to and condition append lo_expr->create_operator_expr( i_attr1 = ls_view-name i_operator = 'EQ' i_val = conv #( <lv_component> ) ) to lt_and. catch cx_os_query_expr_fact_error. " endtry. when ls_view-type->kind_table. " check: is range? check is_range( cast #( ls_view-type ) ) eq abap_true. " must be not initial field-symbols: <lt_range> type table. assign component ls_view-name of structure is_selection to <lt_range>. check <lt_range> is assigned. check <lt_range> is not initial. " goes to and condition append get_query_range( iv_name = ls_view-name it_range = <lt_range> io_expr = lo_expr ) to lt_and. endcase. endloop. " build and conditions loop at lt_and into data(lo_and). if sy-tabix eq 1. data(lo_filter) = lt_and[ 1 ]. else. lo_filter = lo_expr->create_and_expr( exporting i_expr1 = lo_filter i_expr2 = lo_and ). endif. endloop. lo_query->set_filter_expr( lo_filter ). ro_query = lo_query. endmethod. method get_query_range. data: lt_and type table of ref to if_os_query_filter_expr, lt_or type table of ref to if_os_query_filter_expr. data: ls_range type range_ts. " .. for each range value loop at it_range assigning field-symbol(<ls_range>). move-corresponding exact <ls_range> to ls_range. " E = AND, I = OR case ls_range-sign. when 'E'. append io_expr->create_not_expr( get_query_range_value( is_range = ls_range io_expr = io_expr iv_name = iv_name ) ) to lt_and.. when 'I'. append get_query_range_value( is_range = ls_range io_expr = io_expr iv_name = iv_name ) to lt_or. endcase. endloop. " First of all combine all OR in to a single expression loop at lt_or into data(lo_or). if sy-tabix eq 1. data(lo_filter_or) = lt_or[ 1 ]. else. lo_filter_or = io_expr->create_or_expr( exporting i_expr1 = lo_filter_or i_expr2 = lo_or ). endif. endloop. " make all or statements as one of ANDs append lo_filter_or to lt_and. loop at lt_and into data(lo_and). if sy-tabix eq 1. ro_expr = lt_and[ 1 ]. else. ro_expr = io_expr->create_and_expr( exporting i_expr1 = ro_expr i_expr2 = lo_and ). endif. endloop. endmethod. method get_query_range_value. try. case is_range-option. " is operator when 'EQ' or 'NE' or 'LE' or 'LT' or 'GE' or 'GT' . ro_expr = io_expr->create_operator_expr( i_attr1 = iv_name i_operator = conv #( is_range-option ) i_val = is_range-low ). " is mask when 'CP'. data(lv_pattern) = is_range-low. replace all occurrences of '*' in lv_pattern with '%'. ro_expr = io_expr->create_like_expr( i_attr = iv_name i_pattern = lv_pattern * i_not = OSCON_FALSE ). " is mask with not when 'NP'. lv_pattern = is_range-low. replace all occurrences of '*' in lv_pattern with '%'. ro_expr = io_expr->create_like_expr( i_attr = iv_name i_pattern = lv_pattern i_not = oscon_true ). * when 'BT'. when others. " not supported endcase. catch cx_os_query_expr_fact_error. " endtry. endmethod. method IS_RANGE. CHECK io_type->table_kind eq io_type->tablekind_std AND io_type->key_defkind eq io_type->KEYDEFKIND_DEFAULT AND io_type->key eq value ABAP_KEYDESCR_TAB( ( name = 'SIGN') ( name = 'OPTION') ( name = 'LOW') ( name = 'HIGH') ). rv_range = abap_true. endmethod. method select_by_query. " check agent check io_agent is bound. try. " get result by using generated method rt_result = io_agent->get_persistent_by_query( exporting " create query by selection criteria i_query = get_query( exporting is_selection = is_selection " Must be structure changing co_type = co_type " Runtime Type Services ) " Query ). catch cx_os_object_not_found. " catch cx_os_query_error. " endtry. endmethod. ENDCLASS.