Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions content/docs/analyzers/PlatformCop/PC0040.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
+++
title = 'TryFunction database write is not rolled back'
linkTitle = 'PC0040'

[params]
id = 'PC0040'
severity = 'Warning'
category = 'Usage'
codeAction = false
ignoreObsolete = true
+++

Database changes made inside a `[TryFunction]` are not rolled back when an error is caught. On-premises, writes can also cause a runtime error when `DisableWriteInsideTryFunctions` is enabled.

PC0040 reports direct writes made by record built-in methods. A successful write can remain committed even if a later error causes the TryFunction to return `false`.

### Example

{{< highlight al "hl_lines=4-5" >}}
[TryFunction]
local procedure TryInsertCustomer(var Customer: Record Customer)
begin
Customer.Insert(); // The insert is not rolled back if a later error is caught [PC0040]
Error('Something failed after the insert.');
end;
{{< /highlight >}}

Keep the TryFunction for validation, and perform the write after it succeeds:

{{< highlight al "hl_lines=3-4" >}}
procedure CreateCustomer(var Customer: Record Customer)
begin
if TryValidateCustomer(Customer) then
Customer.Insert();
end;

[TryFunction]
local procedure TryValidateCustomer(var Customer: Record Customer)
begin
Customer.TestField("No.");
Customer.TestField(Name);
end;
{{< /highlight >}}

### When the diagnostic is reported

PC0040 is reported for calls to `Insert`, `Modify`, `ModifyAll`, `Delete`, `DeleteAll`, `Rename`, `Truncate`, `AddLink`, `CopyLinks`, `DeleteLink` or `DeleteLinks` inside a TryFunction when the receiver resolves to a non-temporary record and table. Calls with or without parentheses are recognized.

### When the diagnostic is not reported

- Calls outside TryFunctions.
- Writes to temporary records, tables declared with `TableType = Temporary`, or temporary page source tables.
- Same-named methods on non-record types, such as `List.Insert`, `TextBuilder.Insert` and `HttpClient.Delete`.
- `RecordRef` writes, because whether a `RecordRef` is temporary is only known at runtime.
- Calls to custom procedures. Enable [PC0041](../pc0041/) to review calls that may write without following method bodies.

### See also

- [Handling errors using try methods](https://learn.microsoft.com/en-us/dynamics365/business-central/dev-itpro/developer/devenv-handling-errors-using-try-methods) on Microsoft Learn
- [PC0041](../pc0041/) - Call may write to the database inside a TryFunction
62 changes: 62 additions & 0 deletions content/docs/analyzers/PlatformCop/PC0041.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
+++
title = 'Call may write to the database inside a TryFunction'
linkTitle = 'PC0041'

[params]
id = 'PC0041'
severity = 'Hidden'
category = 'Usage'
codeAction = false
ignoreObsolete = true
+++

Database changes made inside a `[TryFunction]` are not rolled back when an error is caught. PC0041 helps find calls whose effects are not known to be safe.

This diagnostic is disabled by default because it reports every custom procedure call and selected built-in methods without analyzing their implementations. Enable it when you want to review potential writes across your codebase; use [PC0040](../pc0040/) for direct record writes.

### Example

{{< highlight al "hl_lines=4" >}}
[TryFunction]
local procedure TryPrepareCustomer(var Customer: Record Customer)
begin
PrepareCustomer(Customer); // This procedure may write to the database [PC0041]
end;
{{< /highlight >}}

If you know a call is safe and does not write to the database, suppress the diagnostic with a reason:

{{< highlight al "hl_lines=4-6" >}}
[TryFunction]
local procedure TryPrepareCustomer(var Customer: Record Customer)
begin
#pragma warning disable PC0041 // PrepareCustomer only validates values in memory.
PrepareCustomer(Customer);
#pragma warning restore PC0041
end;
{{< /highlight >}}

### Enable the rule

Add PC0041 to your project's `.ruleset.json` file. `Info` shows potential writes as informational messages:

{{< highlight json >}}
{
"name": "My Project Ruleset",
"rules": [
{
"id": "PC0041",
"action": "Info"
}
]
}
{{< /highlight >}}

### When the diagnostic is reported

PC0041 is reported inside a TryFunction for every custom procedure call and for built-in `Commit`, `Execute`, `Print`, `Run`, `RunModal`, `RunRequestPage`, `SaveAs`, `SaveAsExcel`, `SaveAsHtml`, `SaveAsPdf`, `SaveAsWord`, `SaveAsXml` and `Validate` calls. It does not determine whether a custom procedure actually writes to the database.

### See also

- [Handling errors using try methods](https://learn.microsoft.com/en-us/dynamics365/business-central/dev-itpro/developer/devenv-handling-errors-using-try-methods) on Microsoft Learn
- [PC0040](../pc0040/) - TryFunction database write is not rolled back
2 changes: 2 additions & 0 deletions content/docs/analyzers/PlatformCop/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,5 @@ PlatformCop inspects AL code for constructs the platform rejects, ignores, or ex
| [PC0036](pc0036/) | SetRecord() does not support temporary records | Warning | ✓ | |
| [PC0037](pc0037/) | Use Validate() instead of direct field assignment | Warning | ✓ | ✓ |
| [PC0038](pc0038/) | Not all code paths return a value | Info | ✓ | |
| [PC0040](pc0040/) | TryFunction database write is not rolled back | Warning | ✓ | |
| [PC0041](pc0041/) | Call may write to the database inside a TryFunction | Hidden | | |