-
Notifications
You must be signed in to change notification settings - Fork 3.2k
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Fix to #31365 - Query: add support for projecting JSON entities that have been composed on #31391
Conversation
@roji, turned out bit easier than I thought (knock on wood) there are still some tests failing due to some bad assumptions in the JSON post processor. Also SelectMany is still broken (will take a look), and it looks like OPENJSON with WITH needs to add AS JSON for the primitve collection properties, otherwise they come out as null (at least in some scenarios). But when I do that, I run into some bad state in the JSON post processor. Also code still needs some major cleanup, as I hacked it rather quickly ^^' |
{ | ||
AddToProjection(entityProjectionExpression.DiscriminatorExpression, DiscriminatorColumnAlias); | ||
//achievement unlocked ;P | ||
do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahahahaha 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm 100% sure we'll figure out why this is somehow incorrect and needs to be a regular while
var targetPrimaryKeyProperties = targetEntityType.FindPrimaryKey()!.Properties.Take(KeyPropertyMap.Count); | ||
var sourcePrimaryKeyProperties = EntityType.FindPrimaryKey()!.Properties.Take(KeyPropertyMap.Count); | ||
foreach (var (target, source) in targetPrimaryKeyProperties.Zip(sourcePrimaryKeyProperties, (t, s) => (t, s))) | ||
{ | ||
newKeyPropertyMap[target] = _keyPropertyMap[source]; | ||
newKeyPropertyMap[target] = KeyPropertyMap[source]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: If accessing KeyPropertyMap is considered to be somehow expensive due to the virtual call, it can be cached into a local here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll leave it for our Resident Optimization Justification Investigator to decide, @roji
src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs
Outdated
Show resolved
Hide resolved
Name = jsonPropertyName, | ||
TypeMapping = property.GetRelationalTypeMapping(), | ||
Path = new PathSegment[] { new(jsonPropertyName) }, | ||
//AsJson = property.GetRelationalTypeMapping().ElementTypeMapping is not null |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
when using OPENJSON with WITH we need to mark primitive collections with AS JSON keyword, otherwise they come out as null. When I do it, we hit some bad assumptions in the json post processor, so I commented it temporarily. Will uncomment and fix json post processor in the final version.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that I made some minor changes in #31369 to the JSON postprocessor, these could in theory be related (so rebase in any case).
Anyway, let me know if you want my help with this tomorrow.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🐑 🇮🇹
Ahh, the season of 🐑 🇮🇹 has begun :) @maumar will also review shortly for the SelectExpression parts. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My understanding here is pretty vague... But looks OK :)
Let's think about cleanup later when everything is calmer.
@@ -1690,6 +1682,33 @@ Expression CopyProjectionToOuter(SelectExpression innerSelectExpression, Express | |||
projectionIndexMap[jsonProjectionInfo.JsonColumnIndex], | |||
newKeyAccessInfo)); | |||
} | |||
else if (constantValue is QueryableJsonProjectionInfo queryableJsonProjectionInfo) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This whole thing can be a switch 😇
...l/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs
Outdated
Show resolved
Hide resolved
...l/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs
Outdated
Show resolved
Hide resolved
...l/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs
Show resolved
Hide resolved
rebased and updated, please take a final look @roji @ajcvickers |
|
||
protected override Expression VisitMethodCall(MethodCallExpression methodCallExpression) | ||
{ | ||
if (methodCallExpression is |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should be good enough - just replace reference to the ordinal key with default value for the type we expect. In case of key comparison (handled above) we make an exception because we need to convert key != null
into true, rather than null != null
which would have been false. In general, since this materialization path is always for non-tracking, we shouldn't have too many instances of the keys present, one other place that I haven't yet tested would be materialization interceptor. @ajcvickers
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
update: added test for materialization interceptor - works like a charm
@@ -789,7 +789,9 @@ public void ApplyDistinct() | |||
// for JSON entities identifier is the key that was generated when we convert from json to query root (OPENJSON, json_each, etc) | |||
// but we can't use it for distinct, as it would warp the results | |||
// instead, we will treat every non-key property as identifier | |||
foreach (var property in entityProjection.EntityType.GetDeclaredProperties().Where(p => !p.IsPrimaryKey())) | |||
|
|||
// TODO: hack/workaround, see #31398 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI
@@ -239,8 +239,10 @@ static bool IsKeyColumn(SqlExpression sqlExpression, TableExpressionBase table) | |||
columnExpression.Table, "value", columnExpression.Type, _nvarcharMaxTypeMapping, columnExpression.IsNullable); | |||
|
|||
Check.DebugAssert(columnInfo.Path is not null, "Path shouldn't be null in OPENJSON WITH"); | |||
Check.DebugAssert(!columnInfo.AsJson, "AS JSON signifies an owned sub-entity being projected out of OPENJSON/WITH. " | |||
+ "Columns referring to that must be wrapped be Json{Scalar,Query}Expression and will have been already dealt with above"); | |||
//Check.DebugAssert( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@roji uncomment to see a bunch of tests failing due to incorrect assumptions we had in the post processor.
443c468
to
d7fd111
Compare
…have been composed on Adding new materialization path for JSON entities that have been converted to query roots - they look like normal entity, but materialization is somewhat different. We need to prune synthesized key properties from materializer code (as they are not associated with any column, but rather made up on the fly) Also added code to include all the child navigations. For normal entities we have IncludeExpressions in the shaper expression, but for JSON we prune all includes and build them in the materializer. Fix is to recognize the scenario (entity projection but the entity is mapped to JSON) during apply projection phase and generate all the necessary info needed rather than just dictionary of property to index maps like we do for regular entity. Then when we build materializer we use that info to prune the synthesized properties from key check and re-use existing include JSON entity code to add child navigations. Fixes #31365
d7fd111
to
88f17aa
Compare
Adding new materialization path for JSON entities that have been converted to query roots - they look like normal entity, but materialization is somewhat different.
We need to prune synthesized key properties from materializer code (as they are not associated with any column, but rather made up on the fly)
Also added code to include all the child navigations. For normal entities we have IncludeExpressions in the shaper expression, but for JSON we prune all includes and build them in the materializer.
Fix is to recognize the scenario (entity projection but the entity is mapped to JSON) during apply projection phase and generate all the necessary info needed rather than just dictionary of property to index maps like we do for regular entity.
Then when we build materializer we use that info to prune the synthesized properties from key check and re-use existing include JSON entity code to add child navigations.
Fixes #31365