"If codeunit.run() then"
This has a couple of weird ancient quirks that result in a mix of worse performance, worse readability and more footguns for new AL developers.
Problems:
1. You cannot have an active transaction going before invoking it. There is no good technical reason for this limitation.
2. when it returns true, it has an implicit commit. This is a common footgun, even BC developers with 10+ years experience get surprised by that one.
3. It can only handle one record parameter.
4. OnRun is a "magic" trigger requiring a new codeunit, every time one is needed.
Suggestion:
Expand the TryFunction with a parameter "AllowTransactions" (bool), with default value false for backwards compatibility like this:
[TryFunction(true)]
ThisFunctionIsAMoreErgonomicCodeunitRunTrigger(Param1: Text; Param2: Integer)
If you leave out the old limitations described above in 1) and 2), but leave the forced rollback upon error it would behave like a more intuitive and more flexible Codeunit.Run() construct.
Side bonus: There has been suggestions in the community to add Rollback() to AL. This would be solved indirectly with this construct as one could do:
[TryFunction(true)]
Rollback()
begin
Error('');
end;
as it would be invokeable from inside a transaction, as opposed to "If Codeunit.Run() then"
Comments
This is very much needed especially for big integrations with million places where things are expected to to catch on fire at any time.I think more general solution is needed here. AL lacks a way to pass function invocations as values.In C# I can do this:Action doSomething = () => { doSomethingFoReal(param1, param2); };// and then laterdoSomething(); // actually invoke the body of the action.We don't need anonymous functions in AL, but being able to pass invocation as a value is something that could remove a lot of boilerplate code. Mountains.Something like this pseudocode would be more than enough.var Invocation: Invocation;Invocation := makeinvocation MyProcedure(Param1, Param2);// This should be equivalent to making object instance with globals Param1, Param2 that has a procedure that callsMyProcedure// Only non-var parameters should be allowed. Just a little thing to keep our sanity.// Database, codeunit parameters or events can be used to return results.Invocation.Execute(); // Actually call the saved procedure with the saved parameter values.This doesn't need to have codeunit.run semantics or anything else. Then programmer can make helper procedure that takes invocation and then calls invocation either in codeunit.run wrapper or in tryfunction wrapper.Bonus points 1: Add a function to GetLastErrorInfo().Bonus points 2: Add a function Error(ErrorInfo, KeepOriginalCallStack).
Category: Development
You could consider using a background session instead.
Category: Development
Vjeko did a good write-up of this in 2015 - noting that we already have some ability to nest SQL transactions through TestIsolation Fixing Preview Posting: Part 2 – Vjeko.com
Category: Development
I vote 10,000% for this. The `if Codeunit.Run()` pattern requires a tonne of boilerplate code, which is easy to write wrong, copy/paste wrong, etc. - to wrap the actual function, setting up its 'arguments', and getting out its results. It also wastes a codeunit ID, or requires some really complicated 'Try codeunits' that contain lots of globals - again simply inviting bugs later.AL developers who know what we're doing, and sure - know the limitations of even `if Codeunit.Run()` - could opt-in to this new `[TryFunction(ActLikeIfCodeunitRun := true)]` behaviour and avoid having to waste so much time and IDs on boilerplate.
Category: Development
Note: This would also fix the gap left behind by the removal of the ASSERTERROR hack used by some partners in production C/AL code.
Category: Development
Business Central Team (administrator)
Thank you for this suggestion! Currently this is not on our roadmap. We are tracking this idea and if it gathers more votes and comments we will consider it in the future. Best regards, Business Central Team