
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:
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:
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.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
User | Count |
---|---|
3 | |
1 | |
1 | |
1 | |
1 | |
1 | |
1 | |
1 |