Skip to content

$filter/$orderby over a dynamic (open-type) property returns 500 instead of 400 #390

Description

@engenb

Found while implementing #389 (EDM-driven open types). Pre-existing and unrelated to that change — confirmed by mutation run: with the open-type modifier disabled, these four cases behave identically.

Symptom

Against EF Core/SQLite, with an open complex type carrying dynamic keys:

Request Result
$filter=Metadata/tier eq 3 (dynamic key) 500
$orderby=Metadata/tier (dynamic key) 500
$select=Metadata/tier (dynamic key) 200, silently behaves as $select=Metadata
$filter=Metadata/Region eq 'eu' (declared) 200, WHERE "r"."Metadata_Region" = @TypedProperty

The 500 carries:

System.ArgumentException: Method 'System.Object get_Item(System.String)' declared on type
'IDictionary<string,object>' cannot be called with instance of type 'System.Object'

thrown while building the expression tree, inside ODataQueryOptions.ApplyTo.

The one piece of good news

No query reaches the database. Asserted in OpenTypeDynamicKeyFilterSqliteTests via SQL capture: no FROM "Refs" statement is executed. So this is not the silent-client-evaluation failure mode that would have been much worse — it fails fast, it just fails with the wrong status code.

Why it should be 400

The client asked for something the server cannot do. That is 400 InvalidQueryOption, consistent with every other unsupported-query-option path in this codebase. A 500 tells operators there is a server bug and tells the client to retry, both wrong.

It is also anonymously reachable: any caller can drive unhandled 500s by filtering on a dynamic key, which is the same exposure class as #358.

Note the third row

$select=Metadata/tier returning 200 while silently ignoring the path is arguably worse than the 500 — it is a wrong answer under a success status, the #353/#354 family. Worth deciding whether it should also 400.

Suggested direction

The fault originates in Microsoft's filter binder, not in our code, so we cannot make it translate. But we can recognise it: the exception is thrown during ApplyTo, which is already wrapped for ODataException on the collection-read paths. Either catch this specific ArgumentException shape and convert to 400, or — better and provider-independent — detect a dynamic-property path in the parsed $filter/$orderby AST before execution and reject it there with a clear message naming the unsupported path.

The second approach mirrors the direction taken for #385 (pre-execution literal-zero divisor check) and would also let $select be handled consistently.

Documented as a known limitation in docs/open-types.md by #389.

Related: #389, #385, #358, #353, #354.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions