cancel
Showing results for 
Search instead for 
Did you mean: 

Object-Instance does not take changed attributes - database does

Former Member
0 Kudos

Hey Guys,

I am new to Workflow-Creation and just doing my first project with WF and ABAP OO.

In my WF I am working with an Account described in an instance of a persistent class, created by myself.

In some Workitems i call tasks (-> here: methods of the object) that change (persistent) attributes.

Going to the next Workitem, the change has not been taken by the instance of the object - the attributes are the same as before. BUT in the database-table, the persistent class is representing, the values HAVE changed.

So I would have to refresh or reinstanciate the instance of the class to load the attributes again, before reaching every Workitem.

I thought (and even read in SAP's Workflow Book) that Objects in WF are instanciented for every Workitem or Operation they are used - so the attributes should be equal to the values in the database without manually reinstancing them -shouldn't they?

What did I try to solve the Problem and have up-to-date values in my instance:

Checking Bindings

Checking LPOR and Find_by_lpor methods

testing the methods, that change attributes (outside of workflow)

looking for solutions in OSS

looking for solutions in SDN

Is anybody familiar with this situation and can offer a solution?

Thank you very much.

Background Information:

System: SAP ECC 6.0 / SAP_APPL Rel. 603 (SAPKH60302)#

PS: I hope I wrote this thread in the right way - it is my first one here. If not, tell me please.

Accepted Solutions (1)

Accepted Solutions (1)

Former Member
0 Kudos

Hi,

How did you populate the Attributes of the class? using constructor method right?

If i understood correctly, using constructor your attributes are populated. But its not changing for every instance. That is the issue right?

If so, for each instance you have to pass different input's. so that the attributes getting populated with different values right?

Please make sure that, for each instance is the same input is passed or different input's.

Also test the class with different instances in SE24 itself. If it is working fine in SE24, then there is some issue's with

workflow binding.

Thanks,

Viji.

Former Member
0 Kudos

Thank you for your answer Viji.

Just like I said before, my class is a persistent class. All attributes I use in this class are specified as persistent attributes - I do not use a constructor in the usual way.

If I cahnge an attribute with a Task in WF, the datatable changes, but the Instance does not.

If I test the class in SE24, everything works fine: by calling a method, that changes an attribute, the attribute changes in database AND Object-Instance.

In WF-Bindings I do not find a mistake...

Edited by: Wolfgang Dörner on Oct 14, 2010 10:56 AM

Former Member
0 Kudos

Hi Wolfgang,

I think the problem probably comes from the fact that you're using a persistent class. If you were using a normal class with the constructor method to populate your attributes it would work.

Why do you want a persistent class? I therory you should not need it, you just need a key to your class which persists in the wf containers and allows re-instatiation of the class throug the IF_WORKFLOW methods each time the class is used.

I am not quite sure persistent classes are supported by the workflow engine...

Cheers,

Patrick

Former Member
0 Kudos

Thanks Patrick,

the Problem is, that workflow does not save any attributes of the class by itself except the key - just like you said.

I use a persistent class, because the account I describe with this class is not directly in any other database-table. To select the attributes every time when the class is instanciated would be a horrible effort.

So if I would not use a persistent class, I had to make the attributes persistent manually by insert, update and select statements on the database-table I created fpr the persistent class. This cannot be what SAP wants, can it?

Adverting to the support of persistent classes i had a look at this book:

[Practical Workflow for SAP|http://www.sap-press.com/products/Practical-Workflow-for-SAP-%282nd-Edition%29.html ]

chapter 11.8.2 is about persistent classes. It says that their use is of no problems in generell - it only becomes complicated if there is no 1:1-relation between instance and database-table-line.

But here is a 1:1-relation.

Farther the book alludes to the class CL_BOOK_PPF. It is a WF-Demo-Class for WF. Unfortunately it is not used in any WF in our system. So can't take a look how to handele it, but I copied the LPOR and FIND_BY_LPOR-Methods.

Former Member
0 Kudos

Hey, thanks, you taught me something. I didn't know it was possible to use persistent classes in workflow (even if the 2nd edition of "the book" replaced the first one on my desk as soon as it was published

Did you try getting your attribute through a GET method? I guess this is worth a try. When you read an attribute directly you don't have a preceeding call of the state_read_access macro, so maybe this is the reason why it doesn't work?

Cheers,

Patrick

Former Member
0 Kudos

Hey Patrick,

I AM using a get-method. E.g.: when I created a Condition-Step, which is reading an attribute, I didn't choose account->attribute, but account->methods->get_attribute.

That does not work, but thanks for the idea.

But I have just discovered a strange behaviour. To describe it I have to tell a little more background information:

My WF is controled by a status-attribute in the tabel (and class of course). So when the workflow starts a loop starts. The next step is a multiple condition which reads the status attribut and goes an accordant way. After this way the status is changed by a task (->the instence-method "set_status") and the loop checks the status. If the status is not 100 it starts again at the multiple condition and so on....

Nearly every one of the steps is a background step. But If I create a manual step (regardless what it does) just after the loop started but before the multiple condition, the whole thing works.

Without this manual step I create an endless loop.

Perhaps this description helps to understand the problem a bit more.

Thanks again!

Former Member
0 Kudos

I bet that your task that changes the status is a synchronous one. If that's the case it is likely that the change is not actually updated into the db when the condition is evaluated at the next loop pass.

To solve this I see two possibilities:

- Make your background task that changes the status an asynchronous one, that finishes with an event "status_changed". You would have to code that event somewhere in the method(s) where you change the status. The RFC call of the event will happen only once the commit is effective, so that should guarantee you that the db is actually updated when the backround task ends and the next loop pass starts.

- That's a dirty one, but usually works: put a step after the status change with a dummy method to wait for one second or so.

Cheers,

Patrick

Former Member
0 Kudos

You win the bet!

I am going to try your approachs and post as soon as I have some conclusion.

So far:

Dank u voor uw ideeën!

(dutch for: Thank you for your ideas!)

Former Member
0 Kudos

Hey Patrick.

I just tried to implement this event-orientated thing. In my loop's first run it works. in the second run I use a copy of the first step that changes my status. But It does not work: The step waits for the event to be triggered, but nothing happens.

I tried to debug the whole thing and it seems like the WF is not even entering the method to change the status a second time. It does it only in the first run. But it is the same kind of task. And according to the WFProtocol it gets started in the second run, too.

I am at my wit's end. Can you help me? Perhaps this is my problem just from the beginning: The WF does not enter the method at all....

If you need further information to solve the problem simply ask....

thank you so far!

Former Member
0 Kudos

Hi Wolfgang,

Can you activate the event trace (SWELS) and check that your event is raised twice (i.e. ponce in the first loop, once in the second loop).

Thanks,

Patrick

Former Member
0 Kudos

Hey Patrick,

Event Lock is turned on and already checked: The Event is only thrown once.

Just like I said: If I set an external breakpoint in the method, that changes status and triggers the event and debug the WF I only stop once in this method NOT twice. So I think, he doesn't even call the method a second time - that is what makes me confused.

Former Member
0 Kudos

I can't really explain this in detail, but in summary since the workflow engine is making tRFC's external breakpoints won't always work.

The first thing to check to find out whether it really gets into the second step is the wf log. If the step has a status "ready" then it does not even start. If it is started then probably there is an error in your method, which occurs only in some situations (i.e. in the second loop pass and not in the first one). To solve this I'd suggest you implement an error handling as detailed as possible. You might also be missing a commit work somewhere in your status update method.

Side remark: the more I am thinking about this, the more I am wondering why you are trying to change statuses on an object several times in a row without any manual step inbetween. I.e. why are you going from A to B and then from B to C instead of A to C directly?

Cheers,

Patrick

Former Member
0 Kudos

Good to know that external bp do not always work

The status is "in process", but my method does not throw an exception (which is the case if anything goes wrong) or trigger the event. SWEL tells me the event was only triggered once.

And there is a "commit work an wait statement" in my method. Otherwise the status would not be changed in the 1st run 😛

The reason for handling this whole status-thing consists of two points:

1. I am a student and want to explore workflow with abap objects. So I try a lot and "play" with different patterns.

2. Going from A to C would be nice if there was'nt the possibility of exceptions If my wf crashes I cannot just start from the beginning or restart the current workitem. So if an exception happens, I cahnge my Status on kind of error-status and send a message to me and my colleagues. We can check what happens and set the status to start from a specific step in WF. I figured this out as the most efficient way to handle possible errors.

Do you understand my reasons?

Cheers and thanks!

Former Member
0 Kudos

Mmmh, if your task is "in process" there must be something wrong at runtime with your method. Did you check if there is any dump in ST22 with the WF-BATCH user? Are you sure you are catching all exceptions properly in your method?

In your case I'd vote for a lock issue, i.e. the object on which you want to change a status is still locked by the first status change.

Something else that might help you investigate the issue: did you try to restart your workflow with SWPC (this transaction will not select workflows with a background step that is stuck in process since less than x minutes/hours - I don't know the X valus but it must be something like one hour).

Cheers,

Patrick

Former Member
0 Kudos

Hey hey!

I think there are two possibilities: Either there is a runtime-problem with my method or of any reason the wf does not even start my method a second time.

ST22: No Problems visible (without any selection, except the date).

SWPC: Tried (had a wf-test from this morning) -> nothing happened.

Here is my method. If you see any mistakes, tell me, please:


method change_status.

* * Inform class agent and handle exceptions
  state_write_access.

  if ( i_wf_status <> wf_status ).

    former_wf_status = wf_status.

    wf_status        = i_wf_status.

*   * Inform class agent and handle exceptions
    state_changed.
    commit work.

    if sy-subrc = 0.
*     * raise event
      raise_evt_status_changed( ).
    else.
      raise exception type cx_bo_error.
    endif.
  endif.
endmethod.

raise_evt_status_changed:

 
method RAISE_EVT_STATUS_CHANGED.
data: l_por type SIBFLPOR.

l_por-catid = 'CL'.
l_por-typeid = 'ZCL_FI_SW_ECAIM_COST_ACC'.
l_por-instid = me->get_idoc_id( ).
      TRY.
          CALL METHOD cl_swf_evt_event=>raise
            EXPORTING
              im_objcateg = l_por-catid
              im_objtype  = l_por-typeid
              im_event    = 'STATUS_CHANGED'
              im_objkey   = l_por-instid.
        CATCH cx_swf_evt_exception.
      ENDTRY.

      commit work.
endmethod.

Thank you very much!!!

Regards!

pokrakam
Active Contributor
0 Kudos

Hi Wolfgang,

Interesting discussion. Just to go back to some of the earlier posts: Yes, persistent classes are workflow-friendly. However you do need to take care of how you implement the workflow engine's persistence layer - namely the LPOR/FIND_BY_LPOR methods and how they relate to your persistent class. This can get quite complex, and I suspect this is where your original problem stems from.

First off, are you using the persistent class directly in WF or via a wrapper class? How exactly are you instantiating your persistent class?

If direct, then you may need to store the class agent as a static attribute of your persitent class and use it in the FIND_BY_LPOR method so that it will always return the same instance of your persitent class. This should normally work automagically, but it sounds like you somehow manage to re-instantiate new instances for the same object, causing it to read old data before it's written.

> I thought (and even read in SAP's Workflow Book) that Objects in WF are instanciented for every Workitem or Operation they are used - so the attributes should be equal to the values in the database without manually reinstancing them -shouldn't they?

This is exactly where FIND_BY_LPOR is key: WF is quite unpredictable as to when and how often it tries to instantiate, thus FIND_BY_LPOR it should always return the same instance back to WF to ensure object consistency (only one instance is ever active), and performance. It sounds like this is where it's going wrong.

As a last resort, you can always create a wrapper class that manages the persistent class internally. I usually prefer this anyway, as I can control which attributes are exposed to WF - and thus what the user can see in the log.

Cheers,

Mike

Former Member
0 Kudos

Hey Mike!

Thank you for your post!

I do not use a wrapper class. I also read this pattern, but I thought in my situation it is like cracking a nut with a sledgehammer.

I'm aware of the difficulty implementing the LPOR-things, but not sure if I did everything right.

I would be happy if you could check my findbylpor:


method bi_persistent~find_by_lpor.
  data: idoc_id type edi_docnum.
  try.
      idoc_id = lpor-instid.
      result ?= zca_fi_sw_ecaim_cost_acc=>agent->if_os_ca_persistency~get_persistent_by_key( idoc_id ).
    catch cx_os_error cx_os_system_error.
  endtry.

endmethod.

and lpor:


method BI_PERSISTENT~LPOR.
  TRY.
      result-catid   = 'CL'.
      result-typeid  = 'ZCL_FI_SW_ECAIM_COST_ACC'.
      result-instid = idoc_id.
    CATCH cx_os_error cx_os_system_error.
  ENDTRY.
endmethod.

Or if you could tell me how to test this whole thing....

Thank you very much!

pokrakam
Active Contributor
0 Kudos

Hi Wolfgang,

That looks exactly right - apart from a very redundant TRY-ENDTRY block

Hmmm, very strange. Couple more questions: how do you set the data? Did you create a task for the SET_STATUS method?

It makes sense that it works when you switch to a dialog user, because it will re-read it from the DB. The fact that it writes it to the DB indicates that the attribute was successfully changed. The agent mechanism theoretcally ensures that one and only one instance for a given key is ever active in a user session. Are you sure there's no context switch? i.e. a dialog task followed by a dialog task performed by the same user in background, followed by a 'real' background task?

Using events is an idea, but if a wrapper class is a sledgehammer then asynchronous events in this scenario are a steamroller (only because this is exactly what persistent classes are supposed to do!). There may be some strange stuff happening with COMMIT's, but at this stage I would consider reporting this to OSS, because this is definitely not how persistent classes are supposed to behave.

Also worth checking if there are any OSS notes out there.

Cheers,

Mike

Former Member
0 Kudos

Wolfgang,

May be the way your peristent class is instanciated can be improved (can't really help you with that, I've never tried this before). But the fact that it works in the first loop and not in the second one makes me still think you have a lock issue. I see you have two commit work statement, one in the change_status method just after the status change and one in the raise_evt_status_changed method. I'd try to remove these two commit works and have just one in the change_status method after the call to raise_evt_status_changed.

Cheers,

Patrick

Former Member
0 Kudos

Hey Mike!

how do you set the data? Did you create a task for the SET_STATUS method?

I created a task for the change_status method i've shown just some posts ago.

The fact that it writes it to the DB indicates that the attribute was successfully changed.

Funny thing - good you wrote that: I checked that point again just yet: It does not change the status in the 2. run of the loop anymore. Any cahnge in code I've made in the last versions (and there were a lot!) has changed the behaviour of the databse. but at least it is consistent now

asynchronous events in this scenario are a steamroller

That's right, but for the moment it stops the endless loops 😛

Are you sure there's no context switch? i.e. a dialog task followed by a dialog task performed by the same user in background, followed by a 'real' background task?

I think so, yes!

Also worth checking if there are any OSS notes out there.

I have already checked a little before I've opened this thread - nothing yet.

Former Member
0 Kudos

Hey Patrick!

I will try this in some hours and provide information about the success.

Thanks.

pokrakam
Active Contributor
0 Kudos

> Funny thing - good you wrote that: I checked that point again just yet: It does not change the status in the 2. run of the loop anymore. Any change in code I've made in the last versions (and there were a lot!) has changed the behaviour of the databse. but at least it is consistent now

I think we're getting nearer to the problem

You also mention COMMIT WORK AND WAIT, yet your code only shows COMMIT WORK. The AND WAIT bit would be useful here.

Also, I know it shouldn't make any difference, but I would not put persistence code into the CHANGE_STATUS method. Rather than duplicate it, leave that to the persistent class, that's what it's there for. Thus:

method change_status. 
  set_status( iv_status ). 
  commit work and wait. 
endmethod.

Former Member
0 Kudos

Hey Guys,

I changed the method:


  lv_former_wf_status = get_wf_status( ).
  set_former_wf_status( lv_former_wf_status ).
  set_wf_status( i_wf_status ).
  commit work and wait.
  if sy-subrc = 0.
*     * raise event
    raise_evt_status_changed( ).
  else.
    raise exception type cx_bo_error.
  endif.

In this way I (hope to) satisfy both of your posts, becaus this is (like Mike said) the way that should be gone when changing attributes of a persistent class.

(Even if this whole persistence-thing is a little strange because it contradicts to OO-Patterns sometimes, I think )

I just tried it and...

endless loop:

The first run works and the second "path" is run endless without changing the attribute in database.

Any idea?

Thank you very much!

Former Member
0 Kudos

Hi Wolfgang,

If you still want to use an event the "commit work and wait" statement has to be after the raise event.

But according to Mike's suggestion you may also want to try changing your task from an asynchronous one back to a synchronous one. In that case you won't need to raise the event anymore.

Regards,

Patrick

pokrakam
Active Contributor
0 Kudos

> (Even if this whole persistence-thing is a little strange because it contradicts to OO-Patterns sometimes, I think )

Curious why you say that? My understanding is the opposite - persistent objects are a way of 'object-orienting' a database, turning table entries into objects (even if the implementation is a little clumsy at times).

Patrick is absolutely right though, it doesn't make sense to try both methods at the same time. I also think if you can reduce the problem as far as possible and reproduce it in a simple test WF you will either find the problem or have a good case for OSS.

Another approach to try is liberal sprinkling WAIT UP TO 1 SECONDS statements throughout your code, and maybe even inserting a WF step that does just that. If it then works, remove them until you've found the offending bit. Doesn't make sense but the symptoms are consistent with a synch/asynch problem.

Former Member
0 Kudos

First: excuse my late answer - I was on vacation last few days.

> Mike Pokraka wrote:

Curious why you say that? My understanding is the opposite - persistent objects are a way of 'object-orienting' a database, turning table entries into objects (even if the implementation is a little clumsy at times).

Persistent classes are a very good way to handle data. This concpet is good - that's right.

I see the problem in the implementation, especially this whole get/set-thing. If you want that an attribute a can be read by another programm, you say this persistent attribute is public and not changeable - a getter method is created - a setter not.

This is ok so far.

But if you implement a method, that does something and changes attribut a, you would have to use a setter-method. But there is none - you have now the possibility to change it manually (that is what you two suggested me not to do ) or you set the attribute cahngeable to let a settter-method create. But in this case every programm is able to change the attribut.

Actually SAP should offer the possibility to set the visibility not only for attributes, but for it's getter/setter-methods. Then you could restrict your attributes and accesses.

Do you know, what I mean?

Former Member
0 Kudos

> Mike Pokraka wrote:

Patrick is absolutely right though, it doesn't make sense to try both methods at the same time.

Of course. I am sorry. I just removed the event-raise and changed the task into a synchronous one - still endless loop.

But I made an interesting discovery:

I just took a closer look at my technical workflow-protocol. In the loop's first run, the container of the step, that changes the status contains the object-instance I assigned to the wf.

In the second run, the step's container contains the object with the key:0000000000000.

Perhaps again a little more background...

This is a part of my wf's structure:

start loop -decision of the way by reading the status

-- A

---(ok) -


set status:1 -
loop again

---(error)

-


set status:66

-


loop again

-- B

---(ok) -


set status:2 -
loop again

---(error)

-


set status:66

-


loop again

Step A: No problem

Step B: Wrong key (only in the step "set status"). In step B is still the right key.

I hope this helps to understand the problem a little more.

If we cannot find a solution, I will post an oss-message....

Thank you very much, so far!

Answers (0)