Add query filter semantics guidance (#186)
Some checks are pending
Validate knowledge index / validate-index (push) Waiting to run
Validate AL review fixtures / validate-review-fixtures (push) Waiting to run
Validate skill index and report schemas / validate-contract (push) Waiting to run
Validate frontmatter and structure / validate (push) Waiting to run

This commit is contained in:
Stefano Demiliani 2026-09-15 12:51:15 +02:00 • committed by GitHub
parent b7617fb48a
commit 861f53dd97
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 225 additions and 3 deletions

View file

@ -0,0 +1,39 @@
query 50428 "Static Query Filter Bad"
{
QueryType = Normal;
elements
{
dataitem(SalesHeader; "Sales Header")
{
DataItemTableFilter = Status = const(Open);
column(DocumentNo; "No.")
{
}
filter(StatusFilter; Status)
{
}
}
}
}
codeunit 50429 "Static Query Filter Bad"
{
procedure ReadReleasedOrders()
var
SalesHeader: Record "Sales Header";
SalesHeaderQuery: Query "Static Query Filter Bad";
begin
// This is combined with Status = Open and returns no rows.
SalesHeaderQuery.SetRange(StatusFilter, SalesHeader.Status::Released);
SalesHeaderQuery.Open();
while SalesHeaderQuery.Read() do
ProcessOrder(SalesHeaderQuery.DocumentNo);
SalesHeaderQuery.Close();
end;
local procedure ProcessOrder(DocumentNo: Code[20])
begin
end;
}

View file

@ -0,0 +1,38 @@
query 50430 "Static Query Filter Good"
{
QueryType = Normal;
elements
{
dataitem(SalesHeader; "Sales Header")
{
DataItemTableFilter = "Document Type" = const(Order);
column(DocumentNo; "No.")
{
}
filter(StatusFilter; Status)
{
}
}
}
}
codeunit 50431 "Static Query Filter Good"
{
procedure ReadReleasedOrders()
var
SalesHeader: Record "Sales Header";
SalesHeaderQuery: Query "Static Query Filter Good";
begin
SalesHeaderQuery.SetRange(StatusFilter, SalesHeader.Status::Released);
SalesHeaderQuery.Open();
while SalesHeaderQuery.Read() do
ProcessOrder(SalesHeaderQuery.DocumentNo);
SalesHeaderQuery.Close();
end;
local procedure ProcessOrder(DocumentNo: Code[20])
begin
end;
}

View file

@ -0,0 +1,30 @@
---
bc-version: [all]
domain: query
keywords: [query, dataitemtablefilter, setfilter, setrange, static-filter, filter-precedence]
technologies: [al]
countries: [w1]
application-area: [all]
---
# DataItemTableFilter cannot be overwritten at runtime
## Description
`DataItemTableFilter` defines a static filter on a Query dataitem. A runtime `SetFilter` or `SetRange` on the same source field does not replace that filter. The static and runtime filters are combined with AND, so contradictory values produce an empty dataset instead of broadening or replacing the query definition.
## Best Practice
Keep only invariant restrictions in `DataItemTableFilter`. Expose caller-selectable fields through a column or filter row and apply their values with `SetFilter` or `SetRange` before `Open()`. When both filter types intentionally target the same field, ensure their intersection represents the required dataset.
See sample: [`dataitemtablefilter-cannot-be-overwritten-at-runtime.good.al`](dataitemtablefilter-cannot-be-overwritten-at-runtime.good.al).
## Anti Pattern
Define a static filter in `DataItemTableFilter`, then apply a contradictory runtime filter to the same source field while expecting the runtime filter to replace the static one. Both filters remain effective and the query returns no rows.
See sample: [`dataitemtablefilter-cannot-be-overwritten-at-runtime.bad.al`](dataitemtablefilter-cannot-be-overwritten-at-runtime.bad.al).
## References
Filtering in Query objects — https://learn.microsoft.com/en-us/dynamics365/business-central/dev-itpro/developer/devenv-query-filters

View file

@ -0,0 +1,37 @@
query 50432 "Column Query Filter Bad"
{
QueryType = Normal;
elements
{
dataitem(SalesLine; "Sales Line")
{
column(DocumentNo; "Document No.")
{
}
column(LineQuantity; Quantity)
{
ColumnFilter = LineQuantity = filter(> 0);
}
}
}
}
codeunit 50433 "Column Query Filter Bad"
{
procedure ReadSmallPositiveLines()
var
SalesLineQuery: Query "Column Query Filter Bad";
begin
// This replaces > 0, so negative quantities are also returned.
SalesLineQuery.SetFilter(LineQuantity, '<100');
SalesLineQuery.Open();
while SalesLineQuery.Read() do
ProcessLine(SalesLineQuery.DocumentNo, SalesLineQuery.LineQuantity);
SalesLineQuery.Close();
end;
local procedure ProcessLine(DocumentNo: Code[20]; Quantity: Decimal)
begin
end;
}

View file

@ -0,0 +1,38 @@
query 50434 "Column Query Filter Good"
{
QueryType = Normal;
elements
{
dataitem(SalesLine; "Sales Line")
{
DataItemTableFilter = Quantity = filter(> 0);
column(DocumentNo; "Document No.")
{
}
column(LineQuantity; Quantity)
{
}
}
}
}
codeunit 50435 "Column Query Filter Good"
{
procedure ReadSmallPositiveLines()
var
SalesLineQuery: Query "Column Query Filter Good";
begin
// This combines with the invariant Quantity > 0 dataitem filter.
SalesLineQuery.SetFilter(LineQuantity, '<100');
SalesLineQuery.Open();
while SalesLineQuery.Read() do
ProcessLine(SalesLineQuery.DocumentNo, SalesLineQuery.LineQuantity);
SalesLineQuery.Close();
end;
local procedure ProcessLine(DocumentNo: Code[20]; Quantity: Decimal)
begin
end;
}

View file

@ -0,0 +1,30 @@
---
bc-version: [all]
domain: query
keywords: [query, columnfilter, setfilter, setrange, filter-precedence, runtime-filter]
technologies: [al]
countries: [w1]
application-area: [all]
---
# SetFilter and SetRange overwrite Query ColumnFilter
## Description
`ColumnFilter` on a Query column or filter row defines a dynamic filter. A runtime `SetFilter` or `SetRange` on that same column or filter row replaces the `ColumnFilter`; it does not combine the two conditions. Rows excluded by the declarative filter can therefore reappear when the runtime filter omits that restriction.
## Best Practice
Place invariant restrictions in `DataItemTableFilter`, which runtime filters cannot overwrite. When a `ColumnFilter` is intentionally replaceable, make each runtime `SetFilter` or `SetRange` express the complete required condition before `Open()`.
See sample: [`setfilter-overwrites-query-columnfilter.good.al`](setfilter-overwrites-query-columnfilter.good.al).
## Anti Pattern
Apply `SetFilter` or `SetRange` to a column or filter row and rely on its existing `ColumnFilter` to remain effective. The runtime call replaces that filter and can admit rows that the query definition appeared to exclude.
See sample: [`setfilter-overwrites-query-columnfilter.bad.al`](setfilter-overwrites-query-columnfilter.bad.al).
## References
Filtering in Query objects — https://learn.microsoft.com/en-us/dynamics365/business-central/dev-itpro/developer/devenv-query-filters