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.
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:
$filter=Metadata/tier eq 3(dynamic key)$orderby=Metadata/tier(dynamic key)$select=Metadata/tier(dynamic key)$select=Metadata$filter=Metadata/Region eq 'eu'(declared)WHERE "r"."Metadata_Region" = @TypedProperty✓The 500 carries:
thrown while building the expression tree, inside
ODataQueryOptions.ApplyTo.The one piece of good news
No query reaches the database. Asserted in
OpenTypeDynamicKeyFilterSqliteTestsvia SQL capture: noFROM "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/tierreturning 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 forODataExceptionon the collection-read paths. Either catch this specificArgumentExceptionshape and convert to 400, or — better and provider-independent — detect a dynamic-property path in the parsed$filter/$orderbyAST 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
$selectbe handled consistently.Documented as a known limitation in
docs/open-types.mdby #389.Related: #389, #385, #358, #353, #354.