Skip to content

Commit 447f48e

Browse files
committed
Query: Improvements to Navigation Expansion
- Skip/Take does not force applying pending selector and changing shape. - Throw translation failure message for Querayble methods which we don't translate (hence we don't process in navigation expansion). Earlier we threw query failed message. Now Navigation Expansion does not throw QueryFailed error message from any place. - Unwrap type conversion for validating member access during include expansion so that we don't generate include when derived type's member is accessed. Resolves #18140 Resolves #18374 Resolves #18672 Resolves #18734 Resolves #19138 Resolves #19207
1 parent 347be2d commit 447f48e

24 files changed

+1767
-341
lines changed

src/EFCore.Cosmos/Query/Internal/QuerySqlGenerator.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,8 @@ protected override Expression VisitSelect(SelectExpression selectExpression)
247247
}
248248
else
249249
{
250-
throw new InvalidOperationException(CoreStrings.QueryFailed(selectExpression.Print(), GetType().Name));
250+
// TODO: See Issue#18923
251+
throw new InvalidOperationException("Cosmos Sql API does not support Offset without Limit.");
251252
}
252253
}
253254

src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.ExpressionVisitors.cs

+17-8
Original file line numberDiff line numberDiff line change
@@ -246,11 +246,8 @@ protected Expression ExpandNavigation(
246246

247247
var outerKeySelector = _navigationExpandingExpressionVisitor.GenerateLambda(
248248
outerKey, _source.CurrentParameter);
249-
var innerKeySelector = _navigationExpandingExpressionVisitor.GenerateLambda(
250-
_navigationExpandingExpressionVisitor.ExpandNavigationsInLambdaExpression(
251-
innerSource,
252-
Expression.Lambda(innerKey, innerParameter)),
253-
innerSource.CurrentParameter);
249+
var innerKeySelector = _navigationExpandingExpressionVisitor.ProcessLambdaExpression(
250+
innerSource, Expression.Lambda(innerKey, innerParameter));
254251

255252
var resultSelectorOuterParameter = Expression.Parameter(_source.SourceElementType, "o");
256253
var resultSelectorInnerParameter = Expression.Parameter(innerSource.SourceElementType, "i");
@@ -349,10 +346,22 @@ protected override Expression VisitMember(MemberExpression memberExpression)
349346
{
350347
Check.NotNull(memberExpression, nameof(memberExpression));
351348

352-
if (UnwrapEntityReference(memberExpression.Expression) is EntityReference entityReferece)
349+
var innerExpression = memberExpression.Expression.UnwrapTypeConversion(out var convertedType);
350+
if (UnwrapEntityReference(innerExpression) is EntityReference entityReference)
353351
{
354352
// If it is mapped property then, it would get converted to a column so we don't need to expand includes.
355-
var property = entityReferece.EntityType.FindProperty(memberExpression.Member);
353+
var entityType = entityReference.EntityType;
354+
if (convertedType != null)
355+
{
356+
entityType = entityType.GetTypesInHierarchy()
357+
.FirstOrDefault(et => et.ClrType == convertedType);
358+
if (entityType == null)
359+
{
360+
return base.VisitMember(memberExpression);
361+
}
362+
}
363+
364+
var property = entityType.FindProperty(memberExpression.Member);
356365
if (property != null)
357366
{
358367
return memberExpression;
@@ -366,7 +375,7 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp
366375
{
367376
Check.NotNull(methodCallExpression, nameof(methodCallExpression));
368377

369-
if (methodCallExpression.TryGetEFPropertyArguments(out var _, out var __))
378+
if (methodCallExpression.TryGetEFPropertyArguments(out _, out _))
370379
{
371380
// If it is EF.Property then, it would get converted to a column or throw
372381
// so we don't need to expand includes.

src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.Expressions.cs

-5
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66
using System.Linq;
77
using System.Linq.Expressions;
88
using System.Reflection;
9-
using Microsoft.EntityFrameworkCore.Diagnostics;
10-
using Microsoft.EntityFrameworkCore.Infrastructure;
119
using Microsoft.EntityFrameworkCore.Metadata;
1210
using Microsoft.EntityFrameworkCore.Utilities;
1311

@@ -316,9 +314,6 @@ private set
316314
public virtual NavigationTreeNode Right { get; }
317315
public virtual ParameterExpression CurrentParameter { get; private set; }
318316

319-
protected override Expression VisitChildren(ExpressionVisitor visitor)
320-
=> throw new InvalidOperationException(CoreStrings.QueryFailed(this.Print(), GetType().Name));
321-
322317
public virtual void SetParameter(string parameterName) => CurrentParameter = Parameter(Type, parameterName);
323318

324319
public override ExpressionType NodeType => ExpressionType.Extension;

src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.cs

+120-163
Large diffs are not rendered by default.

src/EFCore/Query/QueryableMethodTranslatingExpressionVisitor.cs

+1-7
Original file line numberDiff line numberDiff line change
@@ -56,13 +56,7 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp
5656

5757
ShapedQueryExpression CheckTranslated(ShapedQueryExpression translated)
5858
{
59-
if (translated == null)
60-
{
61-
throw new InvalidOperationException(
62-
CoreStrings.TranslationFailed(methodCallExpression.Print()));
63-
}
64-
65-
return translated;
59+
return translated ?? throw new InvalidOperationException(CoreStrings.TranslationFailed(methodCallExpression.Print()));
6660
}
6761

6862
var method = methodCallExpression.Method;

src/Shared/EnumerableMethods.cs

+3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ namespace Microsoft.EntityFrameworkCore
1010
{
1111
internal static class EnumerableMethods
1212
{
13+
public static MethodInfo AsEnumerable { get; }
1314
public static MethodInfo Cast { get; }
1415
public static MethodInfo OfType { get; }
1516

@@ -130,6 +131,8 @@ static EnumerableMethods()
130131
.GetMethods(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly)
131132
.ToList();
132133

134+
AsEnumerable = enumerableMethods.Single(
135+
mi => mi.Name == nameof(Enumerable.AsEnumerable) && mi.IsGenericMethod && mi.GetParameters().Length == 1);
133136
Cast = enumerableMethods.Single(
134137
mi => mi.Name == nameof(Enumerable.Cast) && mi.GetParameters().Length == 1);
135138
OfType = enumerableMethods.Single(

src/Shared/ExpressionExtensions.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ public static Expression UnwrapTypeConversion(this Expression expression, out Ty
1818
{
1919
convertedType = null;
2020
while (expression is UnaryExpression unaryExpression
21-
&& unaryExpression.NodeType == ExpressionType.Convert)
21+
&& (unaryExpression.NodeType == ExpressionType.Convert
22+
|| unaryExpression.NodeType == ExpressionType.ConvertChecked
23+
|| unaryExpression.NodeType == ExpressionType.TypeAs))
2224
{
2325
expression = unaryExpression.Operand;
2426
if (unaryExpression.Type != typeof(object) // Ignore object conversion

test/EFCore.Cosmos.FunctionalTests/Query/NorthwindMiscellaneousQueryCosmosTest.cs

+51
Original file line numberDiff line numberDiff line change
@@ -3988,6 +3988,57 @@ public override Task Subquery_DefaultIfEmpty_Any(bool async)
39883988
return base.Subquery_DefaultIfEmpty_Any(async);
39893989
}
39903990

3991+
[ConditionalTheory(Skip = "Issue #17246")]
3992+
public override Task Projection_skip_collection_projection(bool async)
3993+
{
3994+
return base.Projection_skip_collection_projection(async);
3995+
}
3996+
3997+
[ConditionalTheory(Skip = "Issue #17246")]
3998+
public override Task Projection_take_collection_projection(bool async)
3999+
{
4000+
return base.Projection_take_collection_projection(async);
4001+
}
4002+
4003+
[ConditionalTheory(Skip = "Issue #17246")]
4004+
public override Task Projection_skip_take_collection_projection(bool async)
4005+
{
4006+
return base.Projection_skip_take_collection_projection(async);
4007+
}
4008+
4009+
public override Task Projection_skip_projection(bool async)
4010+
{
4011+
return AssertTranslationFailed(() => base.Projection_skip_projection(async));
4012+
}
4013+
4014+
public override Task Projection_take_projection(bool async)
4015+
{
4016+
return AssertTranslationFailed(() => base.Projection_take_projection(async));
4017+
}
4018+
4019+
public override Task Projection_skip_take_projection(bool async)
4020+
{
4021+
return AssertTranslationFailed(() => base.Projection_skip_take_projection(async));
4022+
}
4023+
4024+
[ConditionalTheory(Skip = "Issue #17246")]
4025+
public override Task Collection_projection_skip(bool async)
4026+
{
4027+
return base.Collection_projection_skip(async);
4028+
}
4029+
4030+
[ConditionalTheory(Skip = "Issue #17246")]
4031+
public override Task Collection_projection_take(bool async)
4032+
{
4033+
return base.Collection_projection_take(async);
4034+
}
4035+
4036+
[ConditionalTheory(Skip = "Issue #17246")]
4037+
public override Task Collection_projection_skip_take(bool async)
4038+
{
4039+
return base.Collection_projection_skip_take(async);
4040+
}
4041+
39914042
private void AssertSql(params string[] expected)
39924043
=> Fixture.TestSqlLoggerFactory.AssertBaseline(expected);
39934044

test/EFCore.Cosmos.FunctionalTests/Query/OwnedQueryCosmosTest.cs

+12
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,18 @@ public override Task No_ignored_include_warning_when_implicit_load(bool async)
236236
return base.No_ignored_include_warning_when_implicit_load(async);
237237
}
238238

239+
[ConditionalTheory(Skip = "Skip withouth Take #18923")]
240+
public override Task Client_method_skip_loads_owned_navigations(bool async)
241+
{
242+
return base.Client_method_skip_loads_owned_navigations(async);
243+
}
244+
245+
[ConditionalTheory(Skip = "Skip withouth Take #18923")]
246+
public override Task Client_method_skip_loads_owned_navigations_variation_2(bool async)
247+
{
248+
return base.Client_method_skip_loads_owned_navigations_variation_2(async);
249+
}
250+
239251
private void AssertSql(params string[] expected)
240252
=> Fixture.TestSqlLoggerFactory.AssertBaseline(expected);
241253

test/EFCore.Relational.Specification.Tests/Query/QueryNoClientEvalTestBase.cs

+10-35
Original file line numberDiff line numberDiff line change
@@ -103,59 +103,34 @@ public virtual void Throws_when_subquery_main_from_clause()
103103
public virtual void Throws_when_select_many()
104104
{
105105
using var context = CreateContext();
106-
Assert.Equal(
107-
CoreStrings.QueryFailed(
108-
"c1 => int[] { 1, 2, 3, }",
109-
"NavigationExpandingExpressionVisitor"),
110-
Assert.Throws<InvalidOperationException>(
111-
() => (from c1 in context.Customers
112-
from i in new[] { 1, 2, 3 }
113-
select c1)
114-
.ToList()).Message);
106+
107+
AssertTranslationFailed(
108+
() => (from c1 in context.Customers
109+
from i in new[] { 1, 2, 3 }
110+
select c1)
111+
.ToList());
115112
}
116113

117114
[ConditionalFact]
118115
public virtual void Throws_when_join()
119116
{
120117
using var context = CreateContext();
121-
var message = Assert.Throws<InvalidOperationException>(
118+
AssertTranslationFailed(
122119
() => (from e1 in context.Employees
123120
join i in new uint[] { 1, 2, 3 } on e1.EmployeeID equals i
124121
select e1)
125-
.ToList()).Message;
126-
127-
Assert.Equal(
128-
CoreStrings.QueryFailed(
129-
@"DbSet<Employee>
130-
.Join(
131-
inner: __p_0,
132-
outerKeySelector: e1 => e1.EmployeeID,
133-
innerKeySelector: i => i,
134-
resultSelector: (e1, i) => e1)",
135-
"NavigationExpandingExpressionVisitor"),
136-
message, ignoreLineEndingDifferences: true);
122+
.ToList());
137123
}
138124

139125
[ConditionalFact]
140126
public virtual void Throws_when_group_join()
141127
{
142128
using var context = CreateContext();
143-
var message = Assert.Throws<InvalidOperationException>(
129+
AssertTranslationFailed(
144130
() => (from e1 in context.Employees
145131
join i in new uint[] { 1, 2, 3 } on e1.EmployeeID equals i into g
146132
select e1)
147-
.ToList()).Message;
148-
149-
Assert.Equal(
150-
CoreStrings.QueryFailed(
151-
@"DbSet<Employee>
152-
.GroupJoin(
153-
inner: __p_0,
154-
outerKeySelector: e1 => e1.EmployeeID,
155-
innerKeySelector: i => i,
156-
resultSelector: (e1, g) => e1)",
157-
"NavigationExpandingExpressionVisitor"),
158-
message, ignoreLineEndingDifferences: true);
133+
.ToList());
159134
}
160135

161136
[ConditionalFact(Skip = "Issue#18923")]

test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs

+8-12
Original file line numberDiff line numberDiff line change
@@ -1559,7 +1559,7 @@ public virtual async Task Select_navigation_with_concat_and_count(bool async)
15591559
ss => ss.Set<Gear>().Where(g => !g.HasSoulPatch).Select(g => g.Weapons.Concat(g.Weapons).Count())))).Message;
15601560

15611561
Assert.Equal(
1562-
CoreStrings.QueryFailed(
1562+
CoreStrings.TranslationFailed(
15631563
@"(MaterializeCollectionNavigation(
15641564
navigation: Navigation: Gear.Weapons,
15651565
subquery: (NavigationExpansionExpression
@@ -1588,7 +1588,7 @@ public virtual async Task Select_navigation_with_concat_and_count(bool async)
15881588
Value: (EntityReference: Gear)
15891589
Expression: g), ""FullName"") != null && EF.Property<string>((NavigationTreeExpression
15901590
Value: (EntityReference: Gear)
1591-
Expression: g), ""FullName"") == EF.Property<string>(i, ""OwnerFullName""))))", "NavigationExpandingExpressionVisitor"),
1591+
Expression: g), ""FullName"") == EF.Property<string>(i, ""OwnerFullName""))))"),
15921592
message, ignoreLineEndingDifferences: true);
15931593
}
15941594

@@ -1602,7 +1602,7 @@ public virtual async Task Concat_with_collection_navigations(bool async)
16021602
ss => ss.Set<Gear>().Where(g => g.HasSoulPatch).Select(g => g.Weapons.Union(g.Weapons).Count())))).Message;
16031603

16041604
Assert.Equal(
1605-
CoreStrings.QueryFailed(
1605+
CoreStrings.TranslationFailed(
16061606
@"(MaterializeCollectionNavigation(
16071607
navigation: Navigation: Gear.Weapons,
16081608
subquery: (NavigationExpansionExpression
@@ -1631,7 +1631,7 @@ public virtual async Task Concat_with_collection_navigations(bool async)
16311631
Value: (EntityReference: Gear)
16321632
Expression: g), ""FullName"") != null && EF.Property<string>((NavigationTreeExpression
16331633
Value: (EntityReference: Gear)
1634-
Expression: g), ""FullName"") == EF.Property<string>(i, ""OwnerFullName""))))", "NavigationExpandingExpressionVisitor"),
1634+
Expression: g), ""FullName"") == EF.Property<string>(i, ""OwnerFullName""))))"),
16351635
message, ignoreLineEndingDifferences: true);
16361636
}
16371637

@@ -3106,19 +3106,15 @@ orderby FavoriteWeapon(g.Weapons).Name descending
31063106

31073107
[ConditionalTheory]
31083108
[MemberData(nameof(IsAsyncData))]
3109-
public virtual async Task Client_method_on_collection_navigation_in_additional_from_clause(bool async)
3109+
public virtual Task Client_method_on_collection_navigation_in_additional_from_clause(bool async)
31103110
{
3111-
var message = (await Assert.ThrowsAsync<InvalidOperationException>(
3111+
return AssertTranslationFailed(
31123112
() => AssertQuery(
31133113
async,
31143114
ss => from g in ss.Set<Gear>().OfType<Officer>()
31153115
from v in Veterans(g.Reports)
31163116
select new { g = g.Nickname, v = v.Nickname },
3117-
elementSorter: e => e.g + e.v))).Message;
3118-
3119-
Assert.StartsWith(
3120-
CoreStrings.QueryFailed("", "").Substring(0, 35),
3121-
message);
3117+
elementSorter: e => e.g + e.v));
31223118
}
31233119

31243120
[ConditionalTheory(Skip = "Issue #17328")]
@@ -7397,7 +7393,7 @@ public virtual Task OrderBy_bool_coming_from_optional_navigation(bool async)
73977393
ss => ss.Set<Weapon>().Select(w => w.SynergyWith).OrderBy(g => MaybeScalar<bool>(g, () => g.IsAutomatic)),
73987394
assertOrder: true);
73997395
}
7400-
7396+
74017397
[ConditionalFact]
74027398
public virtual void Byte_array_filter_by_length_parameter_compiled()
74037399
{

0 commit comments

Comments
 (0)