bc-code-intelligence-mcp
Version:
BC Code Intelligence MCP Server - Complete Specialist Bundle with AI-driven expert consultation, seamless handoffs, and context-preserving workflows
238 lines (206 loc) • 7.89 kB
Markdown
# SingleInstance Subscriber Patterns - AL Code Samples
## Basic SingleInstance Implementation
```al
// SingleInstance subscriber codeunit - only one instance exists per session
[SingleInstance]
codeunit 50100 "Sales Order Workflow Manager"
{
var
WorkflowActive: Boolean;
ProcessedOrderCount: Integer;
LastProcessedTime: DateTime;
// Event subscriber that benefits from SingleInstance pattern
[EventSubscriber(ObjectType::Table, Database::"Sales Header", 'OnAfterInsertEvent', '', false, false)]
local procedure OnSalesOrderInsert(var Rec: Record "Sales Header")
begin
// SingleInstance ensures this counter is accurate across all sessions
if Rec."Document Type" = Rec."Document Type"::Order then begin
ProcessedOrderCount += 1;
LastProcessedTime := CurrentDateTime;
// State persists because of SingleInstance
if ProcessedOrderCount mod 100 = 0 then
SendBatchNotification(ProcessedOrderCount);
end;
end;
// Method that leverages persistent state
procedure GetProcessingStats(var OrderCount: Integer; var LastProcessed: DateTime)
begin
OrderCount := ProcessedOrderCount;
LastProcessed := LastProcessedTime;
end;
local procedure SendBatchNotification(Count: Integer)
begin
// Implementation for milestone notifications
Message('Processed %1 sales orders in this session', Count);
end;
}
```
## Advanced SingleInstance with Configuration Management
```al
// SingleInstance subscriber for system-wide configuration caching
[SingleInstance]
codeunit 50101 "System Configuration Manager"
{
var
ConfigurationLoaded: Boolean;
CacheValidUntil: DateTime;
SystemSettings: Dictionary of [Text, Text];
// Expensive configuration loading done once per server instance
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Company-Initialize", 'OnCompanyInitialize', '', false, false)]
local procedure OnCompanyInitialize()
begin
LoadSystemConfiguration();
end;
// Public method that benefits from cached configuration
procedure GetConfigurationValue(SettingKey: Text): Text
var
SettingValue: Text;
begin
// Refresh cache if expired
if (CacheValidUntil < CurrentDateTime) or (not ConfigurationLoaded) then
LoadSystemConfiguration();
if SystemSettings.Get(SettingKey, SettingValue) then
exit(SettingValue)
else
exit('');
end;
local procedure LoadSystemConfiguration()
begin
Clear(SystemSettings);
// Load configuration once - expensive operation
// Simulate configuration loading
SystemSettings.Set('MaxOrderValue', '100000');
SystemSettings.Set('DefaultShipping', 'STANDARD');
SystemSettings.Set('AutoApprovalLimit', '5000');
ConfigurationLoaded := true;
CacheValidUntil := CurrentDateTime + (60 * 60 * 1000); // 1 hour cache
end;
}
```
## Performance Impact Demonstration
```al
// GOOD: SingleInstance subscriber for shared state
[SingleInstance]
codeunit 50102 "Performance Optimized Subscriber"
{
var
ExpensiveResourceHandle: Integer;
ResourceInitialized: Boolean;
[EventSubscriber(ObjectType::Table, Database::"Item Ledger Entry", 'OnAfterInsertEvent', '', false, false)]
local procedure OnItemLedgerEntryInsert(var Rec: Record "Item Ledger Entry")
begin
// Resource initialized once per server instance
if not ResourceInitialized then
InitializeExpensiveResource();
// Fast processing using pre-initialized resource
ProcessItemTransaction(Rec, ExpensiveResourceHandle);
end;
local procedure InitializeExpensiveResource()
begin
// Expensive initialization (database connections, external API setup, etc.)
Sleep(1000); // Simulating expensive setup
ExpensiveResourceHandle := 12345;
ResourceInitialized := true;
end;
local procedure ProcessItemTransaction(ItemLedger: Record "Item Ledger Entry"; ResourceHandle: Integer)
begin
// Fast processing using initialized resource
// Implementation details...
end;
}
// BAD: Normal subscriber recreated for each event (anti-pattern)
codeunit 50103 "Performance Poor Subscriber"
{
[EventSubscriber(ObjectType::Table, Database::"Item Ledger Entry", 'OnAfterInsertEvent', '', false, false)]
local procedure OnItemLedgerEntryInsert(var Rec: Record "Item Ledger Entry")
var
ExpensiveResourceHandle: Integer;
begin
// BAD: Expensive initialization happens for EVERY event
InitializeExpensiveResource(ExpensiveResourceHandle);
ProcessItemTransaction(Rec, ExpensiveResourceHandle);
end;
local procedure InitializeExpensiveResource(var ResourceHandle: Integer)
begin
// This runs for every single item ledger entry - very inefficient!
Sleep(1000);
ResourceHandle := 12345;
end;
local procedure ProcessItemTransaction(ItemLedger: Record "Item Ledger Entry"; ResourceHandle: Integer)
begin
// Processing happens after expensive setup each time
end;
}
```
## Memory Management Best Practices
```al
// Comprehensive SingleInstance subscriber with proper lifecycle management
[SingleInstance]
codeunit 50104 "Enterprise Event Manager"
{
var
SubscriberActive: Boolean;
EventProcessingQueue: List of [Text];
MaxQueueSize: Integer;
trigger OnRun()
begin
Initialize();
end;
local procedure Initialize()
begin
SubscriberActive := true;
MaxQueueSize := 1000;
Clear(EventProcessingQueue);
end;
[EventSubscriber(ObjectType::Table, Database::"Sales Header", 'OnAfterModifyEvent', '', false, false)]
local procedure OnSalesHeaderModify(var Rec: Record "Sales Header")
begin
if not SubscriberActive then exit;
QueueEvent('SalesHeaderModified', Rec."No.");
ProcessEventQueue();
end;
// Queue management with SingleInstance persistence
local procedure QueueEvent(EventType: Text; DocumentNo: Text)
var
EventDescription: Text;
begin
EventDescription := StrSubstNo('%1:%2:%3', EventType, DocumentNo, Format(CurrentDateTime));
if EventProcessingQueue.Count >= MaxQueueSize then
EventProcessingQueue.RemoveAt(1); // Remove oldest event
EventProcessingQueue.Add(EventDescription);
end;
local procedure ProcessEventQueue()
begin
// Batch processing of queued events
if EventProcessingQueue.Count >= 10 then begin
ProcessBatchEvents();
EventProcessingQueue.RemoveRange(1, 10);
end;
end;
local procedure ProcessBatchEvents()
var
EventItem: Text;
i: Integer;
begin
// Process events in batches for efficiency
for i := 1 to MinValue(10, EventProcessingQueue.Count) do begin
EventItem := EventProcessingQueue.Get(i);
// Process individual event
end;
end;
// Public method to control subscriber behavior
procedure SetActiveState(Active: Boolean)
begin
SubscriberActive := Active;
if not Active then
Clear(EventProcessingQueue);
end;
local procedure MinValue(Value1: Integer; Value2: Integer): Integer
begin
if Value1 < Value2 then
exit(Value1)
else
exit(Value2);
end;
}
```