Subscriber Codeunits
Categories:
Created by waldo, Described by waldo
Description
In general, subscribers have to be put in codeunits. There are a few performance considerations that you should keep in the back of your minds, when designing such a codeunit.
- Keep the codeunit as small as possible
- Work with a single instance codeunit
- only subscribe when necessary
- Avoid generic OnInsert/OnModify/OnDelete
Let’s discuss all points
Keep the codeunit as small as possible
Every time a subscriber gets called, a new instance of the codeunit is being loaded in memory, which takes memory and processing power. The smaller the codeunit, the less memory, and the faster it is.
Therefore, it’s suggested to split the subscribers by functionality and avoid putting business logic in the actual codeunit. Tip: put all business logic in an “Method Codeunit”.
Examples:
- if you app does things on Sales and Purchase, create a Sales-subs codeunit, and a Purchase-subs.
- if you have multiple functionalities in your app (let’s call’m modules), create a subs-codeunit per module, and only add the subscribers in there that are necessary for that module.
Bad code
codeunit 2037325 "Setup Subs"
{
SingleInstance = true;
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Manual Setup", 'OnRegisterManualSetup', '', false, false)]
local procedure OnRegisterManualSetup(sender: Codeunit "Manual Setup")
var
AppId: ModuleInfo;
NameListLbl: Label 'Linked Texts Framework - List', Locked = true;
DescriptionListLbl: Label 'Edit linked texts', Locked = true;
KeyWordListLbl: Label 'LT,Distri,Technical,Functional,Reports', Locked = true;
NameReportLbl: Label 'Linked Texts Framework - Reports', Locked = true;
DescriptionReportLbl: Label 'View linked texts reports', Locked = true;
KeyWordReportLbl: Label 'LT,Distri,Technical,Functional,Reports', Locked = true;
begin
navapp.GetCurrentModuleInfo(AppId);
Sender.Insert(NameListLbl, DescriptionListLbl, KeyWordListLbl, page::"LTE Linked Text List", AppId.Id(), "Manual Setup Category"::General);
Sender.Insert(NameReportLbl, DescriptionReportLbl, KeyWordReportLbl, page::"LTE Linked Texts Reports", AppId.Id(), "Manual Setup Category"::General);
end;
[EventSubscriber(ObjectType::Codeunit, codeunit::"Manual Setup", 'OnRegisterManualSetup', '', false, false)]
local procedure OnRegisterManualSetup(sender: Codeunit "Manual Setup")
var
AppId: ModuleInfo;
NameLayoutLbl: Label 'Report Helper - Layout', Locked = true;
DescriptionLayoutLbl: Label 'Set up or update report layout list', Locked = true;
KeyWordLayoutLbl: Label 'RH,Distri,Technical,Functional,Reports,Layout', Locked = true;
NameCaptionsLbl: Label 'Report Helper - Captions', Locked = true;
DescriptionCaptionsLbl: Label 'Set up or update captions list', Locked = true;
KeyWordCaptionsLbl: Label 'RH,Distri,Technical,Functional,Reports,Captions', Locked = true;
NameFunctionsLbl: Label 'Report Helper - Functions', Locked = true;
DescriptionFunctionsLbl: Label 'Set up or disable functions', Locked = true;
KeyWordFunctionsLbl: Label 'RH,Distri,Technical,Functional,Reports,Functions', Locked = true;
NameDFCLbl: Label 'Report Helper - Default Footer', Locked = true;
DescriptionDFCLbl: Label 'Set up or update default footer', Locked = true;
KeyWordDFCLbl: Label 'RH,Distri,Technical,Functional,Reports,Default,Footer', Locked = true;
begin
navapp.GetCurrentModuleInfo(AppId);
Sender.Insert(NameLayoutLbl, DescriptionLayoutLbl, KeyWordLayoutLbl, page::"RHE Report Layout List", AppId.Id(), "Manual Setup Category"::General);
Sender.Insert(NameCaptionsLbl, DescriptionCaptionsLbl, KeyWordCaptionsLbl, page::"RHE Captions", AppId.Id(), "Manual Setup Category"::General);
Sender.Insert(NameFunctionsLbl, DescriptionFunctionsLbl, KeyWordFunctionsLbl, page::"RHE Functions", AppId.Id(), "Manual Setup Category"::General);
Sender.Insert(NameDFCLbl, DescriptionDFCLbl, KeyWordDFCLbl, page::"RHE Default Footer Card", AppId.Id(), "Manual Setup Category"::General);
end;
}
Good code
Split into 2 codeunits, and move the business logic out.
codeunit 2037325 "LTE Setup Subs"
{
SingleInstance = true;
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Manual Setup", 'OnRegisterManualSetup', '', false, false)]
local procedure OnRegisterManualSetup(sender: Codeunit "Manual Setup")
var
RegisterLTEManualSetup: codeunit "Register LTE Manual Setup";
begin
RegisterLTEManualSetup.RegisterLTEManualSetup();
end;
}
codeunit 2037324 "RHE Setup Subs"
{
SingleInstance = true;
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Manual Setup", 'OnRegisterManualSetup', '', false, false)]
local procedure OnRegisterManualSetup(sender: Codeunit "Manual Setup")
var
RegisterRHEManualSetup: codeunit "Register RHE Manual Setup";
begin
RegisterRHEManualSetup.RegisterRHEManualSetup();
end;
}
Work with a single instance codeunit
To avoid the extra “loading of the content” while a subscriber is being executed, use Single Instance codeunit for subscribers. Do take into account, of course, that it would share the state across the entire session.
Bad code
codeunit 2037324 "RHE Setup Subs"
{
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Manual Setup", 'OnRegisterManualSetup', '', false, false)]
local procedure OnRegisterManualSetup(sender: Codeunit "Manual Setup")
var
RegisterRHEManualSetup: codeunit "Register RHE Manual Setup";
begin
RegisterRHEManualSetup.RegisterRHEManualSetup();
end;
}
Good code
codeunit 2037324 "RHE Setup Subs"
{
SingleInstance = true;
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Manual Setup", 'OnRegisterManualSetup', '', false, false)]
local procedure OnRegisterManualSetup(sender: Codeunit "Manual Setup")
var
RegisterRHEManualSetup: codeunit "Register RHE Manual Setup";
begin
RegisterRHEManualSetup.RegisterRHEManualSetup();
end;
}
only subscribe when necessary
If possible, only execute the subscriber when really necessary by using Manual Binding.
Bad code
//subscriber - code should actually only run when Color=Red.
[EventSubscriber(ObjectType::Table, Database::"Just Some Table WLD", 'OnAfterValidateEvent', 'Message 2', false, false)]
local procedure JustDoSomthing(var Rec: Record "Just Some Table WLD"; var xRec: Record "Just Some Table WLD")
begin
if Rec.color <> 'RED' then
exit; //only execute when necessary
...
end;
//business logic
if JustSomeTable.FindSet() then
repeat
JustSomeTable.Validate("Message 2", format(Random(1000)));
until JustSomeTable.Next() < 1;
Good code
if JustSomeTable.FindSet() then
repeat
if JustSomeTable.Color = 'RED' then
BindSubscription(DemoSubs);
JustSomeTable.Validate("Message 2", format(Random(1000)));
if JustSomeTable.Color = 'RED' then
UnbindSubscription(DemoSubs);
until JustSomeTable.Next() < 1;
Avoid OnInsert/OnModify/OnDelete
The reason for this is, that it breaks the batch-calls:
- Any “OnInsert” subscriber breaks the bulk inserts, simply because it needs to perform an operation after every record that was inserted
- Any “OnModify” subscriber slows down the “ModifyAll”, simply because it needs to perform an operation after every record that was modified. I fact: 1 SQL call is turned into a loop of SQL calls.
- Any “OnDelete” subscriber slows down the “DeleteAll”, simply because it needs to perform an operation after every record that was deleted. I fact: 1 SQL call is turned into a loop of SQL calls.
Avoid subscribers to these events.
References
Feedback
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.