skip to content

Should an OData client generate typed proxies from $metadata at build time or discover the model at runtime, and how does each cope with model changes?

level: seniorimportance: should knowfreq 11%

answer

  1. snapshot versus live model
  2. the safe-change list
  3. tolerate unknown properties and types
  4. the metadata ETag in responses
  5. $schemaversion pins a version

basics

~20 s

Typed OData proxies generated from $metadata give compile-time checks but freeze the model; runtime discovery adapts but gives up types. Either way, clients must tolerate OData's safe additive changes and can detect drift through the metadata ETag and, in 4.01, $schemaversion.

solid answer

~40 s

Generate proxies when a product integrates with one known OData service and wants typed entities and operations; discover at runtime when the client must handle models it does not know in advance, such as admin screens, explorers or configurable tenants. Either way, OData's model-versioning rules are the contract: adding nullable properties, types, entity sets, operations and annotations is **safe**, and clients SHOULD be prepared for properties and derived types they have never seen, so a generated deserializer must ignore unknown properties and fall back to a known base type. Breaking changes need a new service root or, in 4.01, metadata versioned with `Core.SchemaVersion` and requests pinned with `$schemaversion`. To notice drift, compare the `ETag` of `$metadata` with the metadata ETag a service SHOULD put in responses when it publishes one.

code

http · 22 lines
http
GET /sales/$metadata?$schemaversion=* HTTP/1.1
Host: api.example.com
Accept: application/json
OData-MaxVersion: 4.01

HTTP/1.1 200 OK
Content-Type: application/json
OData-Version: 4.01
ETag: W/"md-0042"

{
  "$Version": "4.01",
  "$EntityContainer": "Sales.Container",
  "$Reference": {
    "https://oasis-tcs.github.io/odata-vocabularies/vocabularies/Org.OData.Core.V1.json": {
      "$Include": [ { "$Namespace": "Org.OData.Core.V1", "$Alias": "Core" } ]
    }
  },
  "Sales": {
    "@Core.SchemaVersion": "2.3"
  }
}

go deeper

for a junior

Recall the two options, generating typed code from $metadata or reading $metadata at runtime, and one advantage of each.

for a middle

Explain what a generated proxy contains and why its deserializer must ignore unknown properties and map unknown derived types to a known base type.

for a senior

Show how you detect model drift with the metadata ETag, which changes the specification calls safe, and how $schemaversion pins a client in OData 4.01.

for a principal

Choose the strategy per consumer and own the compatibility policy, including what the service team may change without a new service root or schema version.

## Two ways to consume $metadata - **Build-time proxy generation**: a tool reads `$metadata` and emits typed code, with classes for entity and complex types, enumerations, accessors for entity sets and singletons, and methods for actions and functions. The model is frozen into the client when it is built. - **Runtime discovery**: the client fetches `$metadata` when it starts, or on demand, and drives itself from the model, choosing columns, building `$filter` and `$select` expressions and reading annotations. The OData protocol presents this as the reason a service advertises its model in machine-readable form: so that generic clients can interact with it in a well-defined way. | | Generated proxies | Runtime discovery | |---|---|---| | Type safety | checked when the client is compiled | checked only at run time | | Developer experience | typed entities and operations | generic code and string paths | | A new property or type | invisible until regeneration | visible on the next fetch | | Start-up cost | none | fetching and parsing a possibly large document | | Good fit | a fixed integration with a known service | admin UIs, explorers, multi-tenant or configurable models | ## What OData promises about change The specification treats the data model as a contract between the service and its clients: a service may extend it only in ways that do not break existing clients. Its list of **safe changes** includes: - adding a property that is nullable or has a default value, or a navigation property that is nullable or collection-valued; - adding entity types, complex types, entity sets, singletons, type definitions, enumerations and terms; - adding actions, functions and their imports, and adding, after the existing ones, a nullable action parameter or a parameter annotated `Core.OptionalParameter`; - adding annotations a client need not understand to interact correctly. Clients SHOULD be prepared for such changes, in particular for properties and derived types the service did not define before. Breaking changes, such as removing a property, changing its type, adding or removing key properties, or reordering operation parameters, require a new service version at a different service root. OData 4.01 adds a second option: versioning the metadata with the `Core.SchemaVersion` annotation. ## What that means for generated proxies 1. Deserialization must tolerate unknown properties. The payload rules already require clients to handle or safely ignore content their payload version does not define. 2. An entity that arrives with a derived type the proxy has no class for should map to the nearest known base type instead of failing. 3. The safe-change list mentions adding new enumerations, not new members of an existing one, so a generator that turns an enumeration into a closed type should still decide what an unknown value does. 4. Generate from the fullest model. Services SHOULD NOT vary the model by authenticated user, and if they do, every difference MUST be a safe change relative to the full model, so a proxy generated with a restricted account can lack types that other users see. ## Noticing that the model moved - A service MAY return an `ETag` on the metadata document, which lets a client make a conditional request instead of downloading it again. - When it does, it SHOULD also put that value in responses as the metadata ETag control information (`@odata.metadataEtag`, or `@metadataEtag` in a 4.01 payload) under minimal or full metadata, so a client can see that a response was produced from a newer model. - In OData 4.01, a service that versions its metadata MUST support the `$schemaversion` system query option. On `$metadata`, `$schemaversion=*` returns the current version; the client SHOULD then send the `Core.SchemaVersion` value it found on later requests to pin itself. An unknown version gets `404 Not Found`, and without `$schemaversion` the service MUST serve metadata free of breaking changes over time. ## A pragmatic split One resolution is to combine both: typed proxies for the entities and operations a product depends on, and runtime reading of `$metadata` for annotations, optional fields and capabilities. Whichever the team chooses, regeneration belongs in the build pipeline, the client must follow the tolerance rules above, and the service team should treat the safe-change list as the line it does not cross without a new service root or schema version.

  • How should an OData client react when a response's metadata ETag differs from the one on its cached $metadata?
    It knows the response was produced from a different model, so it refetches `$metadata`, conditionally if it holds the old `ETag`. If the difference is a safe change, a typed proxy keeps working and the event is a prompt to regenerate; a runtime-discovery client simply reloads its model. A breaking difference without a new service root or schema version is the service breaking its contract.
  • Why does OData 4.01's $schemaversion matter to a long-lived generated client?
    It lets the service version its model in place instead of moving to a new service root. The client finds the current version with `$schemaversion=*`, records the `Core.SchemaVersion` value, and sends it on later requests so the service processes them against that version, even after newer, breaking versions appear.

saying these in an interview costs you the question

  • Once proxies are generated, the service may not add any property.
  • A generated OData client should fail on any property it does not recognise.
  • Changing a property's type is safe as long as old values still fit.
  • Every user sees the same $metadata, so the generating account does not matter.
  • Any OData 4.0 service supports $schemaversion.