Translate the LINQ operators that materialise a sequence - #99
Merged
Merged
Conversation
ToDictionary, ToLookup and GroupBy were classified Unsupported, so a client writing e.Nodes.ToDictionary(n => n.ResourceReference, n => n, comparer) got "the LINQ operator 'ToDictionary' cannot be translated to a GraphQL selection" instead of a query. Nothing about them is untranslatable: the query tree only records which fields are mentioned and where they hang, and their lambdas read fields of a single sequence, which is the pass-through shape - the keying itself happens on the client after the response is deserialized. Add them, along with the operators the list had never caught up with (Append, Contains, CountBy, Index, Prepend, Shuffle, TryGetNonEnumeratedCount), and say in LinqOperatorKind what is left: operators that combine several sequences or fold into an accumulator. The list stays an allow-list rather than becoming shape based. An operator we have not listed throws, which is loud and one line to fix; guessing pass-through for an unknown operator would instead produce a query missing fields, and that surfaces as nulls in a deserialized object with no error anywhere. Pass-through lambdas now bind their later parameters too when the parameter is a sequence of the element type, so GroupBy's result selector - (key, orders) => ... - can select through orders instead of reporting it as unbound. The Unsupported message gained a hint naming the way out, since the operator can almost always be applied to the result of ExecuteAsync, and translation failures now throw GraphQueryTranslationException carrying the offending Expression. It derives from NotSupportedException, which is what the parser threw before, so existing catch clauses keep working.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
ToDictionary,ToLookupandGroupBywere classifiedUnsupported, so a client writinggot "the LINQ operator 'ToDictionary' cannot be translated to a GraphQL selection" instead of a query. Nothing about them is untranslatable: the query tree only records which fields are mentioned and where they hang, and their lambdas read fields of a single sequence — the pass-through shape. The keying itself happens on the client after the response is deserialized.
What changed
LinqOperator— added the single-sequence operators toPassThroughOperators:ToDictionary,ToLookup,GroupBy, plus the ones the list had never caught up with (Append,Contains,CountBy,Index,Prepend,Shuffle,TryGetNonEnumeratedCount).LinqOperatorKindnow says whatUnsupportedactually means: operators that combine several sequences (Concat,Join,Zip) or fold into an accumulator (Aggregate).QueryExpressionVisitor— pass-through lambdas bind their later parameters too when the parameter is a sequence of the element type, soGroupBy's result selector(key, orders) => …can select throughordersinstead of reporting it as unbound.ExecuteAsync.GraphQueryTranslationException— translation failures were the one failure category with no named type and no structured context; they now throw this, carrying the offendingExpressionand the operator/member name. It derives fromNotSupportedException, which is what the parser threw before, so existingcatchclauses keep working.Why the list stays an allow-list
Classifying by shape instead would remove the maintenance, but it changes how the code fails when it meets an operator we haven't anticipated. An unlisted operator throws today: loud, and one line to fix. Guessing pass-through would instead emit a query missing fields, which surfaces as
nulls anddefaults in a deserialized object with no error anywhere — the failure mode the parser rewrite set out to eliminate. It also wouldn't remove the name knowledge entirely, sinceSelect/SelectManymust still be recognized to move the selection and the combining operators still need explicit rejection.Tests
Four parser cases in
ExpressionParserTests(ToDictionarywith an element selector, with a comparer,GroupBy,GroupBywith a result selector) and two end-to-end cases inQueryOperatorTests. BothUnsupportedOperator_IsReportedtests usedGroupByas their example and now useConcat; they assert the hint andMemberName, and the parser one pins that the exception is still assignable toNotSupportedException. Full CI suite: 166 passed. No generator or template changes, so thegenerated-clientsdrift check is unaffected.