skip to content

A generic OData client is building a grid for an unfamiliar entity set; how does it learn from $metadata which filtering, sorting, paging and inserts the service allows?

level: seniorimportance: should knowfreq 9%

answer

  1. the service describes its own limits
  2. annotated on the set, via the container
  3. Filter, Sort, Insert restrictions
  4. absence means different things
  5. advice, not enforcement

basics

~20 s

An OData client reads Capabilities vocabulary annotations on the entity set in $metadata: FilterRestrictions, SortRestrictions, TopSupported, InsertRestrictions and others. Absence means paging, counting and expand are assumed, inserts are not, and the service may still refuse a request.

solid answer

~40 s

The **Capabilities vocabulary** (`Org.OData.Capabilities.V1`) lets a service publish its limits in `$metadata`. Its restriction terms are applied through the entity container, for example `Annotations Target="Sales.Container/Orders"`, so they describe that entity set as this service exposes it. A grid reads `FilterRestrictions` (`Filterable`, `RequiresFilter`, `NonFilterableProperties`, `FilterExpressionRestrictions`), `SortRestrictions` (`NonSortableProperties`, ascending-only and descending-only lists), `TopSupported` and `SkipSupported`, `CountRestrictions`, `SearchRestrictions` and `InsertRestrictions`. Absence is not uniform: the vocabulary assumes `$count`, `$top`/`$skip` and `$expand` work, expects missing filter or sort support to be declared, and says a client cannot assume insert, update or delete. The annotations are advice: a service MUST still fail an unsupported system query option and SHOULD answer `501 Not Implemented`, so the client handles errors anyway.

code

xml · 23 lines
xml
<Annotations Target="Sales.Container/Orders">
  <Annotation Term="Capabilities.FilterRestrictions">
    <Record>
      <PropertyValue Property="RequiresFilter" Bool="true"/>
      <PropertyValue Property="RequiredProperties">
        <Collection><PropertyPath>OrderDate</PropertyPath></Collection>
      </PropertyValue>
      <PropertyValue Property="NonFilterableProperties">
        <Collection><PropertyPath>Notes</PropertyPath></Collection>
      </PropertyValue>
    </Record>
  </Annotation>
  <Annotation Term="Capabilities.SortRestrictions">
    <Record>
      <PropertyValue Property="NonSortableProperties">
        <Collection><PropertyPath>Notes</PropertyPath></Collection>
      </PropertyValue>
    </Record>
  </Annotation>
  <Annotation Term="Capabilities.InsertRestrictions">
    <Record><PropertyValue Property="Insertable" Bool="false"/></Record>
  </Annotation>
</Annotations>

go deeper

for a junior

Recall that $metadata can carry Capabilities annotations saying which query options and writes an entity set supports.

for a middle

Name the main restriction terms and their members, and explain why they target the entity set through the entity container.

for a senior

Explain what absence means for each kind of capability, how a generic grid maps restrictions to controls, and why it must still handle 501 and other errors.

for a principal

Judge how much of a service's limits to publish and how to keep the annotations true as the query engine changes, since stale capabilities mislead every generic client.

## Capabilities: a service describing its own limits OData's query language is open: any client may combine `$filter`, `$orderby`, `$top`, `$skip`, `$expand`, `$count` and `$search`, and may try to insert, update or delete. Few services support every combination on every entity set. The **Capabilities vocabulary** (`Org.OData.Capabilities.V1`, alias `Capabilities`) lets a service state in its metadata what it does and does not allow, so a generic client can shape its screen before it sends a request that would fail. ## Where the annotations sit Most restriction terms are themselves tagged `Core.AppliesViaContainer`: the target path of the annotation MUST start with an entity container, or the annotation MUST be embedded in the container, an entity set or a singleton. The restrictions therefore describe *an entity set as this service exposes it*, for example `Annotations Target="Sales.Container/Orders"`, and not the entity type in the abstract. The same type can be exposed through two sets with different limits. A service MAY additionally annotate a type or property when the restriction holds for every use of it. ## What a grid client reads | Term (alias `Capabilities`) | Members a client uses | Consequence for the grid | |---|---|---| | `FilterRestrictions` | `Filterable`, `RequiresFilter`, `RequiredProperties`, `NonFilterableProperties`, `FilterExpressionRestrictions`, `MaxLevels` | which columns get filter controls; whether a filter is required before the first query | | `SortRestrictions` | `Sortable`, `AscendingOnlyProperties`, `DescendingOnlyProperties`, `NonSortableProperties` | which column headers sort, and in which direction | | `TopSupported`, `SkipSupported` | `Core.Tag` values | whether client-driven paging works | | `CountRestrictions` | `Countable` | whether to show a total | | `ExpandRestrictions` | `Expandable`, `MaxLevels` | whether related data can be inlined | | `SearchRestrictions` | `Searchable` | whether to offer a free-text box | | `InsertRestrictions`, `UpdateRestrictions`, `DeleteRestrictions` | `Insertable`, `RequiredProperties`, `NonInsertableProperties`; `Updatable`; `Deletable` | whether to offer create, edit and delete, and which fields a create form requires | `FilterExpressionRestrictions` narrows single properties to an allowed shape, `SingleValue`, `MultiValue`, `SingleRange`, `MultiRange`, `SearchExpression` or `MultiRangeOrSearchExpression`, which maps directly onto the kind of filter widget a column can have. Container-level terms complete the picture: `ConformanceLevel` (`Minimal`, `Intermediate` or `Advanced`), `SupportedFormats`, `BatchSupported` and `AsynchronousRequestsSupported`. ## What absence means The vocabulary's own description sets three different defaults: 1. **Assumed supported** even without an annotation: `$count`, client paging with `$top` and `$skip`, `$expand`, addressing by key, `$batch`, and navigating navigation properties. Annotations mostly exist to say that one of these is *not* supported. 2. **Expected** to be supported, with restrictions called out by annotation when they apply: `$filter`, `$orderby`, querying top-level entity sets, and query functions. 3. **Not assumable**: insert, update and delete. A client can try, but it must be ready for an error. So a missing `InsertRestrictions` annotation does not tell the client that the set is insertable, while a missing `TopSupported` annotation does let it assume paging works. ## How a grid applies them 1. Fetch `$metadata` once and resolve the entity set through the entity container named in it. 2. Collect the annotations that target that set, directly or through the container, and fall back to the defaults above for every term that is absent. 3. Build the controls: filter widgets only on filterable columns and in the allowed shapes, sort arrows only where sorting is permitted, a pager only if `$top` and `$skip` are not switched off, a create button only when `Insertable` is not false. 4. Treat every refusal from the service as normal: show the error and keep the grid usable, because the annotations can be incomplete or out of date. ## Descriptions, not guarantees - The annotations describe; the service still decides at request time. If a service does not support a system query option, it MUST fail any request containing it and SHOULD return `501 Not Implemented`. - At the minimal conformance level a service MUST NOT require clients to understand annotations, so a client that ignores Capabilities still works, only with more failed requests. - Clients SHOULD tolerate terms and members they do not know; the vocabulary keeps evolving and a newer service may use terms an older client has never seen. - How a service arrives at its limits (maximum page size, expand depth, which queries it refuses as too expensive) belongs to its query engine; the annotations only publish the result, and they help only while they stay true to it.

  • How should a client use the Capabilities FilterExpressionRestrictions member when building OData filter controls?
    Each entry names a property and its `AllowedExpressions`: `SingleValue` allows one `eq`, so a single picker; `MultiValue` allows several `eq` or `in` clauses joined by `or`, so a multi-select; `SingleRange` and `MultiRange` allow one or several intervals, so from-to inputs; `SearchExpression` allows `startswith`, `endswith` and `contains`, so a text box. Per-property expressions are combined with `and`.
  • Why are most OData Capabilities restriction terms applied through the entity container rather than on the entity type?
    The terms are tagged `Core.AppliesViaContainer`, so their target path must start at an entity container or the annotation must sit inside the container, an entity set or a singleton. One entity type can be exposed through several sets with different limits, and the restriction belongs to the set. A service may also annotate the type when the limit holds for every use.

saying these in an interview costs you the question

  • A missing InsertRestrictions annotation means the entity set accepts inserts.
  • Capabilities annotations are enforced, so a client that reads them never sees an error.
  • Restrictions annotated on the entity type always hold for every set of that type.
  • No FilterRestrictions annotation means the entity set cannot be filtered.
  • RequiresFilter set to true means every property must appear in $filter.