Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

InMemory support for DefaultIfEmpty #17278

Closed
wants to merge 1 commit into from

Conversation

roji
Copy link
Member

@roji roji commented Aug 20, 2019

Part of #16963

First time going into InMemory so would appreciate a good review :)

One particular complication: for Default_if_empty_top_level_projection we need to apply Coalesce if a non-nullable value type member is accessed on a nullable entity projection (i.e. after DefaultIfEmpty). However, GroupJoin_DefaultIfEmpty_Project has the same structure within a Convert to object, which is then identified in VisitUnary and handled in a special way. I rewrote the pattern recognition to identify and remove the Coalesce for this particular case, but am not sure it's the right thing to do.

@roji roji requested a review from smitpatel August 20, 2019 15:00
// VisitMember introduces a coalesce to default for value-type members being accessed on nullable entities.
// However, if the member access was immediately wrapped by a Convert to object, we don't want the coalesce.
// See test GroupJoin_DefaultIfEmpty_Project
if (outerMostType == typeof(object)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the part I mentioned above, feels problematic.

@roji roji changed the base branch from release/3.0-preview9 to feature/in-memory August 23, 2019 18:36
.ToDictionary(
p => p,
p => CreateReadValueExpression(
nullableReadValueExpressionVisitor.Visit(entityProjection.BindProperty(p)).Type, index++, p));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is somewhat overkill. You are just using type. You can pass nullable type directly. Also it is coming from EntityProjection so it would be readValueMethod. NullableReadValueEV is for complex expression which may have nested ReadValue calls which needs to be converted to null. Further, for DefaultIfEmpty. you just need to update current projection Mapping rather than change anything going inside so you can just create nullable calls.

return source;
}

throw new InvalidOperationException(CoreStrings.TranslationFailed(defaultValue.Print()));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Returns null.

@@ -109,9 +109,19 @@ private Expression BindProperty(Expression source, string propertyName, Type typ
}

var result = BindProperty(entityProjection, entityType.FindProperty(propertyName));

// If the entity projection is nullable (e.g. because of DefaultIfEmpty) but the member is a non-nullable value type,
// we coalesce to its default value.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may not be right thing to do.
There is a lot of overlap in this area between work we all are doing, (mainly @maumar is tackling it).

Due to null propagation needs to introduced by EF core on nav expansion and depending on translation,
Translation of certain expression can return null. (mainly for unmapped property. Also see #17050), That means every expression composition when visiting children needs to take care of encountering null and cascade null forward. I made some changes in the one of the PR I merged.
Translation of certain non-null expression can return nullable expression back. This is due to the fact that property may be non-nullable but it may end up being null due to optional navigation path. In such cases we should be rewriting the expression tree to make things nullable. Coalesce may be ok in some cases but may not represent exact match.
e.g. (a => a.OptionalNav.Counter == 0), if you use coalesce it gives true back. But is counter really 0?

@roji
Copy link
Member Author

roji commented Aug 26, 2019

There is a lot of overlap in this area between work we all are doing

Would you prefer me to leave this work for you and @maumar? It may be a better idea if you're working on related things.

This is due to the fact that property may be non-nullable but it may end up being null due to optional navigation path.

I see what you mean. In any case, let me know if you want me to continue thinking about this.

}
else
{
projectionMapping[projection.Key] = CreateReadValueExpression(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is also incorrect.
This assumes that projection would be a CreateReadValue only. If it is CreateReadValue(0) + CreateReadValue(1) then you never added it to projection but then you decided to read 0 slot from inner.
The implementation should apply a Selector on ServerQuery before DefaultIfEmpty then based on selector applied pass an array of nulls containing ValueBuffer and reconstruct projection mapping like how it is happening here.

@smitpatel
Copy link
Member

I am not sure when is @maumar submitting PR for nullable conversion. Let's wait for that and we can revisit afterwards so you don't end up doing duplicated work as him.

@maumar
Copy link
Contributor

maumar commented Aug 26, 2019

I hope to have something out tomorrow @roji @smitpatel

@smitpatel
Copy link
Member

Replaced by #17484

@smitpatel smitpatel closed this Aug 28, 2019
@smitpatel smitpatel deleted the InMemoryDefaultOrEmpty branch August 28, 2019 22:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants