mirror of
https://github.com/microsoft/BCQuality.git
synced 2026-10-05 14:46:55 +01:00
Address Jesper Schulz-Wedde's review on PR #157
- release-must-update-app-version.md: reframe around AppSource's actual
strict full-version-ordering requirement; scope branching-policy
claims as team convention, not platform rule.
- pictures-must-use-media-not-blob.md: MediaSet is a collection of
independent media objects, not automatic image variants/thumbnails.
- log-writes-must-survive-rollback.{md,good.al}: StartSession's only
data channel into the new session is its Record parameter to a
TableNo-scoped codeunit; a setter called on a local instance before
starting the session populates nothing in the new session.
- exposed-objects-must-be-in-a-permission-set.md: correct the three
exposure mechanisms (Web Services config, PageType/QueryType=API,
ServiceEnabled as a method-only attribute).
- pages-must-not-contain-business-logic.md: scope to persisted
mutations and cross-entry-point rules; presentation-only
calculations and table-owned invariants are not violations.
- given-blocks-must-cover-full-precondition-chain.good.al: replace
invented LibrarySales calls with the real API
(CreateCustomer/CreateSalesOrderForCustomerNo/PostSalesDocument).
- test-feature-scenario-tags.{md,good.al}: move [SCENARIO] inside the
test procedure body to match the current BCApps corpus; keep
[FEATURE] at codeunit level per Microsoft's own documented option.
- ui-test-codeunit-naming.md: scope the _UT suffix and adjacent-ID
pairing as an explicit team convention, not a BCApps-wide standard.
- page-design-must-match-bc-page-type-conventions.md /
table-design-must-match-bc-table-type-conventions.md: Card's
single-key primary-key claim is a contextual heuristic, not a
mandatory constraint (Ship-to Address, Customer/Vendor Bank Account
are real composite-key Card pages); a Subsidiary table with its own
identity commonly gets List+Card, not Worksheet/Tabular.
- api-page-least-privilege-write-access.{md,good.al}: only page-placed
fields are ever exposed; set InsertAllowed/DeleteAllowed=false in the
good sample so a narrow field set can't still create/delete records.
- source-organized-by-feature-not-object-type.md,
test-one-when-per-test.md: scope as team/testing-design conventions,
not Microsoft platform requirements.
- upgrade-tag-logic-must-not-nest-deeply.md: add the Microsoft Learn
citation that already backs the two-level nesting limit.
- Wire the new articles into the testing/data-modeling/error-handling/
security/ui review skills' candidate-selection signals.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
a4d85c3e9e
commit
0120b874b2
22 changed files with 111 additions and 58 deletions
|
|
@ -1,26 +1,45 @@
|
|||
table 50100 "Sample Error Log Buffer"
|
||||
{
|
||||
TableType = Temporary;
|
||||
fields
|
||||
{
|
||||
field(1; "Call Duration (ms)"; Integer) { }
|
||||
field(2; "Error Message"; Text[250]) { }
|
||||
}
|
||||
}
|
||||
|
||||
codeunit 50100 "Sample Error Log Writer"
|
||||
{
|
||||
// Started via Session.StartSession so its commit is independent of the
|
||||
// caller's transaction. Does one thing: insert the log entry, commit.
|
||||
trigger OnRun()
|
||||
// TableNo makes OnRun receive the Record that Session.StartSession
|
||||
// passes to the new session. This is the only data channel into that
|
||||
// session — there is no shared memory with the caller's instance.
|
||||
TableNo = "Sample Error Log Buffer";
|
||||
|
||||
trigger OnRun(var Rec: Record "Sample Error Log Buffer")
|
||||
var
|
||||
ErrorLogEntry: Record "Sample Error Log";
|
||||
begin
|
||||
ErrorLogEntry.Init();
|
||||
ErrorLogEntry."Call Duration (ms)" := CallDurationMs;
|
||||
ErrorLogEntry."Call Duration (ms)" := Rec."Call Duration (ms)";
|
||||
ErrorLogEntry."Error Message" :=
|
||||
CopyStr(ErrorMessageText, 1, MaxStrLen(ErrorLogEntry."Error Message"));
|
||||
CopyStr(Rec."Error Message", 1, MaxStrLen(ErrorLogEntry."Error Message"));
|
||||
ErrorLogEntry.Insert(true);
|
||||
Commit();
|
||||
end;
|
||||
|
||||
procedure SetParameters(Duration: Integer; ErrorText: Text)
|
||||
begin
|
||||
CallDurationMs := Duration;
|
||||
ErrorMessageText := ErrorText;
|
||||
end;
|
||||
|
||||
var
|
||||
CallDurationMs: Integer;
|
||||
ErrorMessageText: Text;
|
||||
}
|
||||
|
||||
// Caller side: populate the buffer record, then hand it to StartSession.
|
||||
// The insert-and-commit above happens inside the started session, so it
|
||||
// survives even if the caller's own transaction rolls back afterward.
|
||||
codeunit 50101 "Sample Error Log Caller Excerpt"
|
||||
{
|
||||
procedure LogFailure(Duration: Integer; ErrorText: Text)
|
||||
var
|
||||
LogBuffer: Record "Sample Error Log Buffer" temporary;
|
||||
SessionId: Integer;
|
||||
begin
|
||||
LogBuffer."Call Duration (ms)" := Duration;
|
||||
LogBuffer."Error Message" := CopyStr(ErrorText, 1, MaxStrLen(LogBuffer."Error Message"));
|
||||
Session.StartSession(SessionId, Codeunit::"Sample Error Log Writer", CompanyName, LogBuffer);
|
||||
end;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ Inserting a log record inside the same transaction as the operation it logs look
|
|||
|
||||
## Best Practice
|
||||
|
||||
Write any log whose purpose includes capturing failures from a transaction that is independent of the operation being logged: start an isolated session (`StartSession` on a codeunit that only inserts the log record and commits) so the entry persists regardless of what happens to the caller's transaction. Capture duration and other telemetry values in the caller and pass them as parameters — the isolated session must not re-read state that a rollback may have erased. Logs that only record successful, committed work can safely stay in the main transaction; this pattern targets error and diagnostic logs specifically.
|
||||
Write any log whose purpose includes capturing failures from a transaction that is independent of the operation being logged: start an isolated session (`Session.StartSession` on a `TableNo`-scoped codeunit that only inserts the log record and commits) so the entry persists regardless of what happens to the caller's transaction. `StartSession`'s only channel for getting data into that new session is its optional `Record` parameter, delivered to the target codeunit's `OnRun` trigger — a separate session gets a fresh instantiation of the codeunit, so calling a setter procedure on a local object variable before starting the session does not populate anything in the new session's instance. Capture duration and other telemetry values in the caller, place them into the `Record` passed to `StartSession`, and do the insert-and-commit entirely inside that session's own `OnRun`. Logs that only record successful, committed work can safely stay in the main transaction; this pattern targets error and diagnostic logs specifically.
|
||||
|
||||
See sample: `log-writes-must-survive-rollback.good.al`.
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue