Hi Wolfgang,
wolfganguhr wrote
This is not correct! 'Nothing' should never be done
Christian this is done (I've found the code) but not by me and furthermore it is not allways wrong.
1) The fact that it is done somewhere else in code does not mean that it is correct;
2) It is not 'wrong' in a technical sense as it 'works', but it is always wrong in a design sense. Let me explain that: If some section of code simply runs a DB command without a reference to a DB Transaction that was started in a controlling super-structure (I call it a 'control procedure'), then that piece of code must never commit or roll back that global DB Transaction as it could not know what else would be committed or rolled back. And the poor programmer who looks at that piece of code cannot tell either in case it does run a Commit or a Rollback - that is why it is 'wrong' in a design sense! It makes for impossible debugging as the user can do many things in parallel and - by doing that - start and committ/rollback DB transactions 'here, there and everywhere' in server code - this makes for 1001 possibilities in which some data outside of the current code scope can be committed to the DB that is not wanted, or some data that was changed outside of the current code scope never ending up in the DB because a Rollback was issued in this piece of code, or 'somewhere else' indeed. There is no control, and that must not happen.
Methods wihich you found that do not start their own DB Transaction and issue a Commit or Rollback inside this Method need to be changed to prevent such loss of control!!!
wolfganguhr wrote
If this is not done, you leave an open DB Transaction 'dangling', which results in other server-side code tripping over it if that piece of code wants to open a DB Transaction and that piece of code is not designed to pick up an existing DB Transaction. This can easily happen
Yes that can be happen but not if you add a commit somewhere at the very end.
Precisely - you are talking about a controlling super-structure(I call it a 'control procedure').
wolfganguhr wrote
And if we commit every small transaction inside the object we actually use, we cannot use it inside a large transaction.
Correct - that is where an controlling super-structure comes into play, whose purpose is the execution of several steps in the correct order and the management of starting of a transaction and the eventual Commit or Rollback of it.
wolfganguhr wrote
the CloseBudget() simply needs to either (1) return bool true/false to the calling procedure
Please Christian: Not "CloseBudget();" is the "common operation", the sequence "CloseGifts(); CloseAccounts(); CloseGLMaster(); CloseBudget(); UpdatePeriods(); SetNewYear(); Finish();" is. We either do all of this parts in one time or nothing. So I can get the same effect if I throw a DoNothingException if a problem occurs.
There is an existing example of a controlling super-structure(a 'control procedure')in the server code that addresses the problem of execution flow and Exception handling as you need it for that sequence:
\csharp\ICT\Petra\Server\lib\MPartner\ui\Partner.PartnerEdit.cs, Method 'SubmitChanges'
This Method starts a DB Transaction, executes two steps in sequence('SubmitChangesOther' and 'SubmitChangesAddresses') and - depending on whether they ran fine or not (in one case determined by a bool and in the other case determined by a TSubmitChangesResult) - a DB Commit or a DB Rollback is issued. In case an Exception happens somewhere in those call paths, a DB Rollback is issued, the Exception gets logged and it gets re-thrown (it ends up on the client side in this case, which is able to handle certain specific Exceptions, and shows the 'Unhandled Exception Dialog' in all other cases).
The Method 'SubmitChanges' only 'chains' two steps, but an arbitrary number of steps can be chained using this pattern. It is up to the control procedure whether to proceed or not to proceed with (a) further step(s) once one of the steps fails - that depends on the requirements; usually it will not execute any further steps in such a case (as is done in this example). Note that the call to 'SubmitChangesOther' results in a huge and varying number of DB operations, depending on many conditions of the business logic contained within that Method. This Method has its own control logic, whose result is a simple true/false return value, which tells the control procedure 'SubmitChanges' whether 'anything' went wrong.
wolfganguhr wroteThrowing an exeption is one way, boolean value an other. And the session varialbe is a third.
That's right, but they have different purposes and scopes:
(A) A boolean value is always local to a Method (or to a Method call tree in case of a 'control procedure') and therefore completely safefrom causing any DB-Transaction-related side effects outside of such a scope (if the DB-Transaction handling is implemented diligently, of course!);
(B) Although an Exception always happens locally, the way you suggest with the proposed introduction of a 'ThrowDoNothingException' control structure by using the global GDBAccess object can potentially affect other, completely different code by causing DB-Transaction-related side effects in that 'other code' that cannot be predicted at runtime (more on that below)!;
(C) A Session Object/variable has a global scope for the one user session. It could potentially hold more than one open DB Transaction to a given RDBMS. This will be needed if we reach the point in which we will need 'nested' DB Transactions, as this is not supported by ADO.NET on a single DB Transaction. Also, it could hold other arbitrary information as well, as ThiasG pointed out. For those reasons (and probably a few more reasons once we start thinking about such an Object properly), a Session Object could be a useful thing to have and is distinctively different from our exsting global GDBAccess Object.
Regarding your suggestion of the proposed introduction of a 'ThrowDoNothingException' control structure, using the global GDBAccess object:
There are three problems with it:
1) Exceptions should not be used for the regular logical control flow of a program;
2) A programmer who does not know about the special (and quite hidden) implications of setting 'ThrowDoNothingException = true' and 'ThrowDoNothingException = false' cannot deduce the control flow from looking at the code (but can do so easily when looking at a Method like 'SubmitChanges');
3) Setting 'ThrowDoNothingException = true' in one specific piece of code on the server side enables this special behaviour globally for that user session. If that piece of code runs longer (as would be the case e.g. with Month End/Year End) and the user does 'something else that requires any DB Transaction' in parallel in that user session, that special behaviour is switched on from within the longer running code section and affects any other server-side code that requires a DB Transaction in that user session. Such parallel execution is perfectly possible if that piece of 'any other code' uses the 'GetNewOrExistingTransaction' technique to re-use an existing, running DB Transaction - and such parallel execution paths are not restricted to happen only when longer running code sections are executed, but there the likelihood of them happening are much higher. Such a dramatic side effect is not acceptable as it could lead to extremely-difficult-to-spot buggy behaviour and loss of data/unwanted saving of data in places a programmer doesn't expect and cannot even anticipate.
wolfganguhr wrote
Let us see to an example code of ABudgetAccess-Table ...
wolfganguhr wroteBut if this routine calls a "CommitTransaction" inside the CloseBudget() then the actual state of any "global transaction" is committed and this must not happen. Of course you can do "nothing" as I said above. The question is: How do we manage it in a unique way.
wolfganguhr wroteAnd furthermore we have actually about 159 such sequences in the code. Ho can we change the module easily not to run a commit or a rollback because we want to do it later.
The solution to all of that is to pass a running transaction from the control procedure into such Methods and to return true/false to the control procedure, and have the control procedure deal with the DB Commit/DB Rollback (as is done in the Partner.PartnerEdit.cs Class).
wolfganguhr wrote
Unfortunately, I did not find the time yet to look into those issues in detail.
The problem is: Solving this problem is actually an important part of my work. But I think we will find something else.
I know that you need that for your work! I hope that this answer shows the solution to your current problem, and that we will soon find a good way ahead with the overarching architecture of database access, exception handling, and other related issues.
It is very unfortunate that ThiasG was not around for four weeks and that I could not talk with him about the issues for that reason, and that I am busy with the student project as well. Please also keep in mind that ThiasG works only part time for OpenPetra and therefore is not available on any given day. ThiasG and I will have a phone call (the first one after six weeks) on next Tuesday to talk about architectural matters, including those mentioned.
Kind regards,
ChristianK