From 30eea304831d2bb4dd33433782f303e515f21b13 Mon Sep 17 00:00:00 2001 From: Arthur Vickers Date: Tue, 20 Aug 2019 10:50:06 -0700 Subject: [PATCH 01/21] Support OfType/is in in-memory database Part of #16963 --- ...yExpressionTranslatingExpressionVisitor.cs | 45 ++++++++----- ...yableMethodTranslatingExpressionVisitor.cs | 67 ++++++++++++++++++- .../Query/InheritanceInMemoryTest.cs | 63 +---------------- .../Query/InheritanceTestBase.cs | 25 +++++++ 4 files changed, 119 insertions(+), 81 deletions(-) diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs b/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs index 260b2ee335a..5421394e391 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs @@ -109,7 +109,11 @@ private Expression BindProperty(Expression source, string propertyName, Type typ } } - var result = BindProperty(entityProjection, entityType.FindProperty(propertyName)); + var property = entityType.GetRootType().GetDerivedTypesInclusive() + .Select(et => et.FindProperty(propertyName)) + .FirstOrDefault(p => p != null); + + var result = BindProperty(entityProjection, property); return result.Type == type ? result : Expression.Convert(result, type); @@ -232,29 +236,36 @@ protected override Expression VisitTypeBinary(TypeBinaryExpression typeBinaryExp && Visit(typeBinaryExpression.Expression) is EntityProjectionExpression entityProjectionExpression) { var entityType = entityProjectionExpression.EntityType; + if (entityType.GetAllBaseTypesInclusive().Any(et => et.ClrType == typeBinaryExpression.TypeOperand)) { return Expression.Constant(true); } - //var derivedType = entityType.GetDerivedTypes().SingleOrDefault(et => et.ClrType == typeBinaryExpression.TypeOperand); - //if (derivedType != null) - //{ - // var concreteEntityTypes = derivedType.GetConcreteDerivedTypesInclusive().ToList(); - // var discriminatorColumn = BindProperty(entityProjectionExpression, entityType.GetDiscriminatorProperty()); - - // return concreteEntityTypes.Count == 1 - // ? _sqlExpressionFactory.Equal(discriminatorColumn, - // _sqlExpressionFactory.Constant(concreteEntityTypes[0].GetDiscriminatorValue())) - // : (Expression)_sqlExpressionFactory.In(discriminatorColumn, - // _sqlExpressionFactory.Constant(concreteEntityTypes.Select(et => et.GetDiscriminatorValue()).ToList()), - // negated: false); - //} - - //return _sqlExpressionFactory.Constant(false); + var derivedType = entityType.GetDerivedTypes().SingleOrDefault(et => et.ClrType == typeBinaryExpression.TypeOperand); + if (derivedType != null) + { + var discriminatorProperty = entityType.GetDiscriminatorProperty(); + var boundProperty = BindProperty(entityProjectionExpression, discriminatorProperty); + + var equals = Expression.Equal( + boundProperty, + Expression.Constant(derivedType.GetDiscriminatorValue(), discriminatorProperty.ClrType)); + + foreach (var derivedDerivedType in derivedType.GetDerivedTypes()) + { + equals = Expression.OrElse( + equals, + Expression.Equal( + boundProperty, + Expression.Constant(derivedDerivedType.GetDiscriminatorValue(), discriminatorProperty.ClrType))); + } + + return equals; + } } - return null; + return Expression.Constant(false); } protected override Expression VisitExtension(Expression extensionExpression) diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs index 2323108142e..6f5d46baf7a 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs @@ -8,6 +8,7 @@ using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Query; using Microsoft.EntityFrameworkCore.Storage; @@ -15,6 +16,8 @@ namespace Microsoft.EntityFrameworkCore.InMemory.Query.Internal { public class InMemoryQueryableMethodTranslatingExpressionVisitor : QueryableMethodTranslatingExpressionVisitor { + private static readonly MethodInfo _efPropertyMethod = typeof(EF).GetTypeInfo().GetDeclaredMethod(nameof(EF.Property)); + private readonly InMemoryExpressionTranslatingExpressionVisitor _expressionTranslator; private readonly InMemoryProjectionBindingExpressionVisitor _projectionBindingExpressionVisitor; private readonly IModel _model; @@ -105,7 +108,8 @@ protected override ShapedQueryExpression TranslateCast(ShapedQueryExpression sou return source; } - protected override ShapedQueryExpression TranslateConcat(ShapedQueryExpression source1, ShapedQueryExpression source2) => throw new NotImplementedException(); + protected override ShapedQueryExpression TranslateConcat(ShapedQueryExpression source1, ShapedQueryExpression source2) + => null; protected override ShapedQueryExpression TranslateContains(ShapedQueryExpression source, Expression item) { @@ -282,7 +286,66 @@ protected override ShapedQueryExpression TranslateMin(ShapedQueryExpression sour => TranslateScalarAggregate(source, selector, nameof(Enumerable.Min)); protected override ShapedQueryExpression TranslateOfType(ShapedQueryExpression source, Type resultType) - => null; + { + if (source.ShaperExpression is EntityShaperExpression entityShaperExpression) + { + var entityType = entityShaperExpression.EntityType; + if (entityType.ClrType == resultType) + { + return source; + } + + var baseType = entityType.GetAllBaseTypes().SingleOrDefault(et => et.ClrType == resultType); + if (baseType != null) + { + source.ShaperExpression = entityShaperExpression.WithEntityType(baseType); + + return source; + } + + var derivedType = entityType.GetDerivedTypes().SingleOrDefault(et => et.ClrType == resultType); + if (derivedType != null) + { + var inMemoryQueryExpression = (InMemoryQueryExpression)source.QueryExpression; + var discriminatorProperty = entityType.GetDiscriminatorProperty(); + var parameter = Expression.Parameter(entityType.ClrType); + + var callEFProperty = Expression.Call( + _efPropertyMethod.MakeGenericMethod( + discriminatorProperty.ClrType), + parameter, + Expression.Constant(discriminatorProperty.Name)); + + var equals = Expression.Equal( + callEFProperty, + Expression.Constant(derivedType.GetDiscriminatorValue(), discriminatorProperty.ClrType)); + + foreach (var derivedDerivedType in derivedType.GetDerivedTypes()) + { + equals = Expression.OrElse( + equals, + Expression.Equal( + callEFProperty, + Expression.Constant(derivedDerivedType.GetDiscriminatorValue(), discriminatorProperty.ClrType))); + } + + var predicate = Expression.Lambda( + equals, + parameter); + + inMemoryQueryExpression.ServerQueryExpression = Expression.Call( + InMemoryLinqOperatorProvider.Where.MakeGenericMethod(typeof(ValueBuffer)), + inMemoryQueryExpression.ServerQueryExpression, + TranslateLambdaExpression(source, predicate)); + + source.ShaperExpression = entityShaperExpression.WithEntityType(derivedType); + + return source; + } + } + + return null; + } protected override ShapedQueryExpression TranslateOrderBy(ShapedQueryExpression source, LambdaExpression keySelector, bool ascending) { diff --git a/test/EFCore.InMemory.FunctionalTests/Query/InheritanceInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/InheritanceInMemoryTest.cs index def2416a2cb..6411050ba02 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/InheritanceInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/InheritanceInMemoryTest.cs @@ -14,75 +14,14 @@ public InheritanceInMemoryTest(InheritanceInMemoryFixture fixture, ITestOutputHe //TestLoggerFactory.TestOutputHelper = testOutputHelper; } - [ConditionalFact(Skip = "Issue #16963")] - public override void Can_use_is_kiwi() - { - } - - [ConditionalFact(Skip = "Issue #16963")] - public override void Can_use_is_kiwi_with_other_predicate() - { - } - [ConditionalFact(Skip = "Issue #16963")] public override void Subquery_OfType() { } - [ConditionalFact(Skip = "Issue #16963")] - public override void Discriminator_used_when_projection_over_of_type() - { - } - - [ConditionalFact(Skip = "Issue #16963")] - public override void Can_use_of_type_animal() - { - } - - [ConditionalFact(Skip = "Issue #16963")] - public override void Can_use_of_type_bird() - { - } - - [ConditionalFact(Skip = "Issue #16963")] - public override void Can_use_of_type_bird_first() - { - } - - [ConditionalFact(Skip = "Issue #16963")] - public override void Can_use_of_type_bird_predicate() - { - } - - [ConditionalFact(Skip = "Issue #16963")] - public override void Can_use_of_type_bird_with_projection() - { - } - - [ConditionalFact(Skip = "Issue #16963")] - public override void Can_use_of_type_kiwi() - { - } - - [ConditionalFact(Skip = "Issue #16963")] - public override void Can_use_of_type_kiwi_where_north_on_derived_property() - { - } - - [ConditionalFact(Skip = "Issue #16963")] - public override void Can_use_of_type_kiwi_where_south_on_derived_property() - { - } - - [ConditionalFact(Skip = "Issue #16963")] - public override void Can_use_of_type_rose() - { - } - - [ConditionalFact(Skip = "See issue#13857")] + [ConditionalFact(Skip = "See issue#13857")] // Defining query public override void Can_query_all_animal_views() { - base.Can_query_all_animal_views(); } protected override bool EnforcesFkConstraints => false; diff --git a/test/EFCore.Specification.Tests/Query/InheritanceTestBase.cs b/test/EFCore.Specification.Tests/Query/InheritanceTestBase.cs index eb984f26486..ab5882a6407 100644 --- a/test/EFCore.Specification.Tests/Query/InheritanceTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/InheritanceTestBase.cs @@ -89,6 +89,18 @@ public virtual void Can_use_is_kiwi() } } + [ConditionalFact] + public virtual void Can_use_backwards_is_animal() + { + using (var context = CreateContext()) + { + // ReSharper disable once IsExpressionAlwaysTrue + var kiwis = context.Set().Where(a => a is Animal).ToList(); + + Assert.Equal(1, kiwis.Count); + } + } + [ConditionalFact] public virtual void Can_use_is_kiwi_with_other_predicate() { @@ -191,6 +203,19 @@ public virtual void Can_use_of_type_kiwi() } } + [ConditionalFact(Skip = "17364")] + public virtual void Can_use_backwards_of_type_animal() + { + using (var context = CreateContext()) + { + var animals = context.Set().OfType().ToList(); + + Assert.Equal(1, animals.Count); + Assert.IsType(animals[0]); + Assert.Equal(1, context.ChangeTracker.Entries().Count()); + } + } + [ConditionalFact] public virtual void Can_use_of_type_rose() { From b25b58b38e1ce06fdd072117b536e478a464d726 Mon Sep 17 00:00:00 2001 From: Maurycy Markowski Date: Wed, 21 Aug 2019 15:46:24 -0700 Subject: [PATCH 02/21] enabling FiltersInheritanceInMemoryTests Bug blocking those tests has been fixed in previous checkin (support for OfType and Is for InMemory) --- .../Query/FiltersInheritanceInMemoryTest.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/EFCore.InMemory.FunctionalTests/Query/FiltersInheritanceInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/FiltersInheritanceInMemoryTest.cs index 3a467f043d8..cb467a04d58 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/FiltersInheritanceInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/FiltersInheritanceInMemoryTest.cs @@ -3,8 +3,7 @@ namespace Microsoft.EntityFrameworkCore.Query { - // issue #17047 - internal class FiltersInheritanceInMemoryTest : FiltersInheritanceTestBase + public class FiltersInheritanceInMemoryTest : FiltersInheritanceTestBase { public FiltersInheritanceInMemoryTest(FiltersInheritanceInMemoryFixture fixture) : base(fixture) From c655d115f4738a8081f522507357831589af4718 Mon Sep 17 00:00:00 2001 From: Smit Patel Date: Thu, 22 Aug 2019 17:26:02 -0700 Subject: [PATCH 03/21] Query: Throw better exception message for client eval Resolves #17387 --- ...yableMethodTranslatingExpressionVisitor.cs | 94 +++---- ...yExpressionTranslatingExpressionVisitor.cs | 2 +- ...yableMethodTranslatingExpressionVisitor.cs | 115 +++++++-- ...yableMethodTranslatingExpressionVisitor.cs | 234 ++++++++++-------- ...yableMethodTranslatingExpressionVisitor.cs | 10 + .../Query/QueryNoClientEvalTestBase.cs | 72 +++--- .../Query/UdfDbFunctionTestBase.cs | 147 +++++------ .../Query/FiltersTestBase.cs | 15 +- .../Query/GearsOfWarQueryTestBase.cs | 18 +- .../Query/IncludeAsyncTestBase.cs | 4 +- .../Query/IncludeTestBase.cs | 4 +- .../Query/QueryNavigationsTestBase.cs | 18 +- .../SimpleQueryTestBase.ResultOperators.cs | 16 +- .../Query/SimpleQueryTestBase.Select.cs | 2 +- .../Query/SimpleQueryTestBase.Where.cs | 14 +- .../Query/SimpleQueryTestBase.cs | 36 +-- .../BuiltInDataTypesSqliteTest.cs | 5 +- .../Query/GearsOfWarQuerySqliteTest.cs | 68 ++--- 18 files changed, 493 insertions(+), 381 deletions(-) diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.Cosmos/Query/Internal/CosmosQueryableMethodTranslatingExpressionVisitor.cs index b877e3f1875..7f0eba862d3 100644 --- a/src/EFCore.Cosmos/Query/Internal/CosmosQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.Cosmos/Query/Internal/CosmosQueryableMethodTranslatingExpressionVisitor.cs @@ -115,10 +115,7 @@ protected override ShapedQueryExpression CreateShapedQueryExpression(Type elemen /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - protected override ShapedQueryExpression TranslateAll(ShapedQueryExpression source, LambdaExpression predicate) - { - throw new InvalidOperationException(CoreStrings.TranslationFailed(predicate.Print())); - } + protected override ShapedQueryExpression TranslateAll(ShapedQueryExpression source, LambdaExpression predicate) => null; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -126,10 +123,7 @@ protected override ShapedQueryExpression TranslateAll(ShapedQueryExpression sour /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - protected override ShapedQueryExpression TranslateAny(ShapedQueryExpression source, LambdaExpression predicate) - { - throw new InvalidOperationException(CoreStrings.TranslationFailed(predicate.Print())); - } + protected override ShapedQueryExpression TranslateAny(ShapedQueryExpression source, LambdaExpression predicate) => null; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -144,7 +138,7 @@ protected override ShapedQueryExpression TranslateAverage(ShapedQueryExpression || selectExpression.Limit != null || selectExpression.Offset != null) { - throw new InvalidOperationException(CoreStrings.TranslationFailed(selector.Print())); + return null; } if (selector != null) @@ -185,8 +179,7 @@ protected override ShapedQueryExpression TranslateCast(ShapedQueryExpression sou /// protected override ShapedQueryExpression TranslateConcat(ShapedQueryExpression source1, ShapedQueryExpression source2) { - throw new InvalidOperationException(CoreStrings.TranslationFailed( - source1.Print() + "; " + source2.Print())); + return null; } /// @@ -197,7 +190,7 @@ protected override ShapedQueryExpression TranslateConcat(ShapedQueryExpression s /// protected override ShapedQueryExpression TranslateContains(ShapedQueryExpression source, Expression item) { - throw new InvalidOperationException(CoreStrings.TranslationFailed(item.Print())); + return null; } /// @@ -213,12 +206,16 @@ protected override ShapedQueryExpression TranslateCount(ShapedQueryExpression so || selectExpression.Limit != null || selectExpression.Offset != null) { - throw new InvalidOperationException(CoreStrings.TranslationFailed(predicate.Print())); + return null; } if (predicate != null) { source = TranslateWhere(source, predicate); + if (source == null) + { + return null; + } } var translation = _sqlExpressionFactory.ApplyDefaultTypeMapping( @@ -244,7 +241,7 @@ protected override ShapedQueryExpression TranslateCount(ShapedQueryExpression so /// protected override ShapedQueryExpression TranslateDefaultIfEmpty(ShapedQueryExpression source, Expression defaultValue) { - throw new InvalidOperationException(CoreStrings.TranslationFailed(defaultValue.Print())); + return null; } /// @@ -268,7 +265,7 @@ protected override ShapedQueryExpression TranslateDistinct(ShapedQueryExpression /// protected override ShapedQueryExpression TranslateElementAtOrDefault(ShapedQueryExpression source, Expression index, bool returnDefault) { - throw new InvalidOperationException(CoreStrings.TranslationFailed(index.Print())); + return null; } /// @@ -279,8 +276,7 @@ protected override ShapedQueryExpression TranslateElementAtOrDefault(ShapedQuery /// protected override ShapedQueryExpression TranslateExcept(ShapedQueryExpression source1, ShapedQueryExpression source2) { - throw new InvalidOperationException(CoreStrings.TranslationFailed( - source1.Print() + "; " + source2.Print())); + return null; } /// @@ -294,6 +290,10 @@ protected override ShapedQueryExpression TranslateFirstOrDefault(ShapedQueryExpr if (predicate != null) { source = TranslateWhere(source, predicate); + if (source == null) + { + return null; + } } var selectExpression = (SelectExpression)source.QueryExpression; @@ -315,8 +315,7 @@ protected override ShapedQueryExpression TranslateFirstOrDefault(ShapedQueryExpr /// protected override ShapedQueryExpression TranslateGroupBy(ShapedQueryExpression source, LambdaExpression keySelector, LambdaExpression elementSelector, LambdaExpression resultSelector) { - throw new InvalidOperationException(CoreStrings.TranslationFailed( - keySelector.Print() + "; " + elementSelector.Print() + "; " + resultSelector.Print())); + return null; } /// @@ -338,8 +337,7 @@ protected override ShapedQueryExpression TranslateGroupJoin(ShapedQueryExpressio /// protected override ShapedQueryExpression TranslateIntersect(ShapedQueryExpression source1, ShapedQueryExpression source2) { - throw new InvalidOperationException(CoreStrings.TranslationFailed( - source1.Print() + "; " + source2.Print())); + return null; } /// @@ -350,8 +348,7 @@ protected override ShapedQueryExpression TranslateIntersect(ShapedQueryExpressio /// protected override ShapedQueryExpression TranslateJoin(ShapedQueryExpression outer, ShapedQueryExpression inner, LambdaExpression outerKeySelector, LambdaExpression innerKeySelector, LambdaExpression resultSelector) { - throw new InvalidOperationException(CoreStrings.TranslationFailed( - outerKeySelector.Print() + "; " + innerKeySelector.Print() + "; " + resultSelector.Print())); + return null; } /// @@ -365,6 +362,10 @@ protected override ShapedQueryExpression TranslateLastOrDefault(ShapedQueryExpre if (predicate != null) { source = TranslateWhere(source, predicate); + if (source == null) + { + return null; + } } var selectExpression = (SelectExpression)source.QueryExpression; @@ -387,8 +388,7 @@ protected override ShapedQueryExpression TranslateLastOrDefault(ShapedQueryExpre /// protected override ShapedQueryExpression TranslateLeftJoin(ShapedQueryExpression outer, ShapedQueryExpression inner, LambdaExpression outerKeySelector, LambdaExpression innerKeySelector, LambdaExpression resultSelector) { - throw new InvalidOperationException(CoreStrings.TranslationFailed( - outerKeySelector.Print() + "; " + innerKeySelector.Print() + "; " + resultSelector.Print())); + return null; } /// @@ -404,12 +404,16 @@ protected override ShapedQueryExpression TranslateLongCount(ShapedQueryExpressio || selectExpression.Limit != null || selectExpression.Offset != null) { - throw new InvalidOperationException(CoreStrings.TranslationFailed(predicate.Print())); + return null; } if (predicate != null) { source = TranslateWhere(source, predicate); + if (source == null) + { + return null; + } } var translation = _sqlExpressionFactory.ApplyDefaultTypeMapping( @@ -439,7 +443,7 @@ protected override ShapedQueryExpression TranslateMax(ShapedQueryExpression sour || selectExpression.Limit != null || selectExpression.Offset != null) { - throw new InvalidOperationException(CoreStrings.TranslationFailed(selector.Print())); + return null; } if (selector != null) @@ -467,7 +471,7 @@ protected override ShapedQueryExpression TranslateMin(ShapedQueryExpression sour || selectExpression.Limit != null || selectExpression.Offset != null) { - throw new InvalidOperationException(CoreStrings.TranslationFailed(selector.Print())); + return null; } if (selector != null) @@ -490,7 +494,7 @@ protected override ShapedQueryExpression TranslateMin(ShapedQueryExpression sour /// protected override ShapedQueryExpression TranslateOfType(ShapedQueryExpression source, Type resultType) { - throw new InvalidOperationException(CoreStrings.TranslationFailed(source.Print())); + return null; } /// @@ -509,7 +513,7 @@ protected override ShapedQueryExpression TranslateOrderBy(ShapedQueryExpression return source; } - throw new InvalidOperationException(CoreStrings.TranslationFailed(keySelector.Print())); + return null; } /// @@ -520,7 +524,7 @@ protected override ShapedQueryExpression TranslateOrderBy(ShapedQueryExpression /// protected override ShapedQueryExpression TranslateReverse(ShapedQueryExpression source) { - throw new InvalidOperationException(CoreStrings.TranslationFailed(source.Print())); + return null; } /// @@ -539,7 +543,7 @@ protected override ShapedQueryExpression TranslateSelect(ShapedQueryExpression s var selectExpression = (SelectExpression)source.QueryExpression; if (selectExpression.IsDistinct) { - throw new InvalidOperationException(CoreStrings.TranslationFailed(selector.Print())); + return null; } var newSelectorBody = ReplacingExpressionVisitor.Replace(selector.Parameters.Single(), source.ShaperExpression, selector.Body); @@ -558,8 +562,7 @@ protected override ShapedQueryExpression TranslateSelect(ShapedQueryExpression s /// protected override ShapedQueryExpression TranslateSelectMany(ShapedQueryExpression source, LambdaExpression collectionSelector, LambdaExpression resultSelector) { - throw new InvalidOperationException(CoreStrings.TranslationFailed( - collectionSelector.Print() + "; " + resultSelector.Print())); + return null; } /// @@ -570,7 +573,7 @@ protected override ShapedQueryExpression TranslateSelectMany(ShapedQueryExpressi /// protected override ShapedQueryExpression TranslateSelectMany(ShapedQueryExpression source, LambdaExpression selector) { - throw new InvalidOperationException(CoreStrings.TranslationFailed(selector.Print())); + return null; } /// @@ -584,6 +587,10 @@ protected override ShapedQueryExpression TranslateSingleOrDefault(ShapedQueryExp if (predicate != null) { source = TranslateWhere(source, predicate); + if (source == null) + { + return null; + } } var selectExpression = (SelectExpression)source.QueryExpression; @@ -615,7 +622,7 @@ protected override ShapedQueryExpression TranslateSkip(ShapedQueryExpression sou return source; } - throw new InvalidOperationException(CoreStrings.TranslationFailed(count.Print())); + return null; } /// @@ -626,7 +633,7 @@ protected override ShapedQueryExpression TranslateSkip(ShapedQueryExpression sou /// protected override ShapedQueryExpression TranslateSkipWhile(ShapedQueryExpression source, LambdaExpression predicate) { - throw new InvalidOperationException(CoreStrings.TranslationFailed(predicate.Print())); + return null; } /// @@ -642,7 +649,7 @@ protected override ShapedQueryExpression TranslateSum(ShapedQueryExpression sour || selectExpression.Limit != null || selectExpression.Offset != null) { - throw new InvalidOperationException(CoreStrings.TranslationFailed(selector.Print())); + return null; } if (selector != null) @@ -677,7 +684,7 @@ protected override ShapedQueryExpression TranslateTake(ShapedQueryExpression sou return source; } - throw new InvalidOperationException(CoreStrings.TranslationFailed(count.Print())); + return null; } /// @@ -688,7 +695,7 @@ protected override ShapedQueryExpression TranslateTake(ShapedQueryExpression sou /// protected override ShapedQueryExpression TranslateTakeWhile(ShapedQueryExpression source, LambdaExpression predicate) { - throw new InvalidOperationException(CoreStrings.TranslationFailed(predicate.Print())); + return null; } /// @@ -707,7 +714,7 @@ protected override ShapedQueryExpression TranslateThenBy(ShapedQueryExpression s return source; } - throw new InvalidOperationException(CoreStrings.TranslationFailed(keySelector.Print())); + return null; } /// @@ -718,8 +725,7 @@ protected override ShapedQueryExpression TranslateThenBy(ShapedQueryExpression s /// protected override ShapedQueryExpression TranslateUnion(ShapedQueryExpression source1, ShapedQueryExpression source2) { - throw new InvalidOperationException(CoreStrings.TranslationFailed( - source1.Print() + "; " + source2.Print())); + return null; } /// @@ -738,7 +744,7 @@ protected override ShapedQueryExpression TranslateWhere(ShapedQueryExpression so return source; } - throw new InvalidOperationException(CoreStrings.TranslationFailed(predicate.Print())); + return null; } private SqlExpression TranslateExpression(Expression expression) diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs b/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs index 5421394e391..f02f4a449c3 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs @@ -292,7 +292,7 @@ protected override Expression VisitExtension(Expression extensionExpression) } default: - throw new InvalidOperationException(CoreStrings.QueryFailed(extensionExpression.Print(), GetType().Name)); + return null; } } diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs index 6f5d46baf7a..46392f752cb 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs @@ -63,12 +63,17 @@ protected override ShapedQueryExpression CreateShapedQueryExpression(Type elemen protected override ShapedQueryExpression TranslateAll(ShapedQueryExpression source, LambdaExpression predicate) { var inMemoryQueryExpression = (InMemoryQueryExpression)source.QueryExpression; + predicate = TranslateLambdaExpression(source, predicate); + if (predicate == null) + { + return null; + } inMemoryQueryExpression.ServerQueryExpression = Expression.Call( InMemoryLinqOperatorProvider.All.MakeGenericMethod(typeof(ValueBuffer)), inMemoryQueryExpression.ServerQueryExpression, - TranslateLambdaExpression(source, predicate)); + predicate); source.ShaperExpression = inMemoryQueryExpression.GetSingleScalarProjection(); @@ -79,14 +84,25 @@ protected override ShapedQueryExpression TranslateAny(ShapedQueryExpression sour { var inMemoryQueryExpression = (InMemoryQueryExpression)source.QueryExpression; - inMemoryQueryExpression.ServerQueryExpression = predicate == null - ? Expression.Call( + if (predicate == null) + { + inMemoryQueryExpression.ServerQueryExpression = Expression.Call( InMemoryLinqOperatorProvider.Any.MakeGenericMethod(typeof(ValueBuffer)), - inMemoryQueryExpression.ServerQueryExpression) - : Expression.Call( + inMemoryQueryExpression.ServerQueryExpression); + } + else + { + predicate = TranslateLambdaExpression(source, predicate); + if (predicate == null) + { + return null; + } + + inMemoryQueryExpression.ServerQueryExpression = Expression.Call( InMemoryLinqOperatorProvider.AnyPredicate.MakeGenericMethod(typeof(ValueBuffer)), inMemoryQueryExpression.ServerQueryExpression, - TranslateLambdaExpression(source, predicate)); + predicate); + } source.ShaperExpression = inMemoryQueryExpression.GetSingleScalarProjection(); @@ -114,8 +130,11 @@ protected override ShapedQueryExpression TranslateConcat(ShapedQueryExpression s protected override ShapedQueryExpression TranslateContains(ShapedQueryExpression source, Expression item) { var inMemoryQueryExpression = (InMemoryQueryExpression)source.QueryExpression; - item = TranslateExpression(item); + if (item == null) + { + return null; + } inMemoryQueryExpression.ServerQueryExpression = Expression.Call( @@ -145,11 +164,17 @@ protected override ShapedQueryExpression TranslateCount(ShapedQueryExpression so } else { + predicate = TranslateLambdaExpression(source, predicate); + if (predicate == null) + { + return null; + } + inMemoryQueryExpression.ServerQueryExpression = Expression.Call( InMemoryLinqOperatorProvider.CountPredicate.MakeGenericMethod(typeof(ValueBuffer)), inMemoryQueryExpression.ServerQueryExpression, - TranslateLambdaExpression(source, predicate)); + predicate); } source.ShaperExpression = inMemoryQueryExpression.GetSingleScalarProjection(); @@ -203,6 +228,10 @@ protected override ShapedQueryExpression TranslateJoin(ShapedQueryExpression out { outerKeySelector = TranslateLambdaExpression(outer, outerKeySelector); innerKeySelector = TranslateLambdaExpression(inner, innerKeySelector); + if (outerKeySelector == null || innerKeySelector == null) + { + return null; + } var transparentIdentifierType = TransparentIdentifierFactory.Create( resultSelector.Parameters[0].Type, @@ -236,6 +265,10 @@ protected override ShapedQueryExpression TranslateLeftJoin(ShapedQueryExpression { outerKeySelector = TranslateLambdaExpression(outer, outerKeySelector); innerKeySelector = TranslateLambdaExpression(inner, innerKeySelector); + if (outerKeySelector == null || innerKeySelector == null) + { + return null; + } var transparentIdentifierType = TransparentIdentifierFactory.Create( resultSelector.Parameters[0].Type, @@ -267,11 +300,17 @@ protected override ShapedQueryExpression TranslateLongCount(ShapedQueryExpressio } else { + predicate = TranslateLambdaExpression(source, predicate); + if (predicate == null) + { + return null; + } + inMemoryQueryExpression.ServerQueryExpression = Expression.Call( InMemoryLinqOperatorProvider.LongCountPredicate.MakeGenericMethod(typeof(ValueBuffer)), inMemoryQueryExpression.ServerQueryExpression, - TranslateLambdaExpression(source, predicate)); + predicate); } source.ShaperExpression = inMemoryQueryExpression.GetSingleScalarProjection(); @@ -329,14 +368,16 @@ protected override ShapedQueryExpression TranslateOfType(ShapedQueryExpression s Expression.Constant(derivedDerivedType.GetDiscriminatorValue(), discriminatorProperty.ClrType))); } - var predicate = Expression.Lambda( - equals, - parameter); + var predicate = TranslateLambdaExpression(source, Expression.Lambda(equals, parameter)); + if (predicate == null) + { + return null; + } inMemoryQueryExpression.ServerQueryExpression = Expression.Call( InMemoryLinqOperatorProvider.Where.MakeGenericMethod(typeof(ValueBuffer)), inMemoryQueryExpression.ServerQueryExpression, - TranslateLambdaExpression(source, predicate)); + predicate); source.ShaperExpression = entityShaperExpression.WithEntityType(derivedType); @@ -352,6 +393,10 @@ protected override ShapedQueryExpression TranslateOrderBy(ShapedQueryExpression var inMemoryQueryExpression = (InMemoryQueryExpression)source.QueryExpression; keySelector = TranslateLambdaExpression(source, keySelector); + if (keySelector == null) + { + return null; + } inMemoryQueryExpression.ServerQueryExpression = Expression.Call( @@ -472,12 +517,17 @@ protected override ShapedQueryExpression TranslateSingleOrDefault(ShapedQueryExp protected override ShapedQueryExpression TranslateSkip(ShapedQueryExpression source, Expression count) { var inMemoryQueryExpression = (InMemoryQueryExpression)source.QueryExpression; + count = TranslateExpression(count); + if (count == null) + { + return null; + } inMemoryQueryExpression.ServerQueryExpression = Expression.Call( InMemoryLinqOperatorProvider.Skip.MakeGenericMethod(typeof(ValueBuffer)), inMemoryQueryExpression.ServerQueryExpression, - TranslateExpression(count)); + count); return source; } @@ -491,12 +541,17 @@ protected override ShapedQueryExpression TranslateSum(ShapedQueryExpression sour protected override ShapedQueryExpression TranslateTake(ShapedQueryExpression source, Expression count) { var inMemoryQueryExpression = (InMemoryQueryExpression)source.QueryExpression; + count = TranslateExpression(count); + if (count == null) + { + return null; + } inMemoryQueryExpression.ServerQueryExpression = Expression.Call( InMemoryLinqOperatorProvider.Take.MakeGenericMethod(typeof(ValueBuffer)), inMemoryQueryExpression.ServerQueryExpression, - TranslateExpression(count)); + count); return source; } @@ -507,8 +562,11 @@ protected override ShapedQueryExpression TranslateTakeWhile(ShapedQueryExpressio protected override ShapedQueryExpression TranslateThenBy(ShapedQueryExpression source, LambdaExpression keySelector, bool ascending) { var inMemoryQueryExpression = (InMemoryQueryExpression)source.QueryExpression; - keySelector = TranslateLambdaExpression(source, keySelector); + if (keySelector == null) + { + return null; + } inMemoryQueryExpression.ServerQueryExpression = Expression.Call( @@ -526,10 +584,16 @@ protected override ShapedQueryExpression TranslateUnion(ShapedQueryExpression so protected override ShapedQueryExpression TranslateWhere(ShapedQueryExpression source, LambdaExpression predicate) { var inMemoryQueryExpression = (InMemoryQueryExpression)source.QueryExpression; + predicate = TranslateLambdaExpression(source, predicate); + if (predicate == null) + { + return null; + } + inMemoryQueryExpression.ServerQueryExpression = Expression.Call( InMemoryLinqOperatorProvider.Where.MakeGenericMethod(typeof(ValueBuffer)), inMemoryQueryExpression.ServerQueryExpression, - TranslateLambdaExpression(source, predicate)); + predicate); return source; } @@ -544,9 +608,12 @@ private LambdaExpression TranslateLambdaExpression( { var lambdaBody = ReplacingExpressionVisitor.Replace( lambdaExpression.Parameters.Single(), shapedQueryExpression.ShaperExpression, lambdaExpression.Body); + lambdaBody = TranslateExpression(lambdaBody); - return Expression.Lambda(TranslateExpression(lambdaBody), - ((InMemoryQueryExpression)shapedQueryExpression.QueryExpression).ValueBufferParameter); + return lambdaBody != null + ? Expression.Lambda(lambdaBody, + ((InMemoryQueryExpression)shapedQueryExpression.QueryExpression).ValueBufferParameter) + : null; } private ShapedQueryExpression TranslateScalarAggregate( @@ -561,6 +628,11 @@ private ShapedQueryExpression TranslateScalarAggregate( inMemoryQueryExpression.ValueBufferParameter) : TranslateLambdaExpression(source, selector); + if (selector == null) + { + return null; + } + inMemoryQueryExpression.ServerQueryExpression = Expression.Call( InMemoryLinqOperatorProvider @@ -583,6 +655,11 @@ private ShapedQueryExpression TranslateSingleResultOperator( ? Expression.Lambda(Expression.Constant(true), Expression.Parameter(typeof(ValueBuffer))) : TranslateLambdaExpression(source, predicate); + if (predicate == null) + { + return null; + } + inMemoryQueryExpression.ServerQueryExpression = Expression.Call( method.MakeGenericMethod(typeof(ValueBuffer)), diff --git a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs index 0d6249ffd8a..ac9d7da97af 100644 --- a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs @@ -105,26 +105,25 @@ private static ShapedQueryExpression CreateShapedQueryExpression(IEntityType ent protected override ShapedQueryExpression TranslateAll(ShapedQueryExpression source, LambdaExpression predicate) { var translation = TranslateLambdaExpression(source, predicate); - - if (translation != null) + if (translation == null) { - var selectExpression = (SelectExpression)source.QueryExpression; - selectExpression.ApplyPredicate(_sqlExpressionFactory.Not(translation)); - selectExpression.ReplaceProjectionMapping(new Dictionary()); - if (selectExpression.Limit == null - && selectExpression.Offset == null) - { - selectExpression.ClearOrdering(); - } - - translation = _sqlExpressionFactory.Exists(selectExpression, true); - source.QueryExpression = _sqlExpressionFactory.Select(translation); - source.ShaperExpression = new ProjectionBindingExpression(source.QueryExpression, new ProjectionMember(), typeof(bool)); + return null; + } - return source; + var selectExpression = (SelectExpression)source.QueryExpression; + selectExpression.ApplyPredicate(_sqlExpressionFactory.Not(translation)); + selectExpression.ReplaceProjectionMapping(new Dictionary()); + if (selectExpression.Limit == null + && selectExpression.Offset == null) + { + selectExpression.ClearOrdering(); } - throw new InvalidOperationException(CoreStrings.TranslationFailed(predicate.Print())); + translation = _sqlExpressionFactory.Exists(selectExpression, true); + source.QueryExpression = _sqlExpressionFactory.Select(translation); + source.ShaperExpression = new ProjectionBindingExpression(source.QueryExpression, new ProjectionMember(), typeof(bool)); + + return source; } protected override ShapedQueryExpression TranslateAny(ShapedQueryExpression source, LambdaExpression predicate) @@ -132,6 +131,10 @@ protected override ShapedQueryExpression TranslateAny(ShapedQueryExpression sour if (predicate != null) { source = TranslateWhere(source, predicate); + if (source == null) + { + return null; + } } var selectExpression = (SelectExpression)source.QueryExpression; @@ -160,24 +163,18 @@ protected override ShapedQueryExpression TranslateAverage(ShapedQueryExpression : RemapLambdaBody(source, selector); var projection = _sqlTranslator.TranslateAverage(newSelector); - - if (projection == null) - { - throw new InvalidOperationException(CoreStrings.TranslationFailed(selectExpression.Print())); - } - - return AggregateResultShaper(source, projection, throwOnNullResult: true, resultType); + return projection != null + ? AggregateResultShaper(source, projection, throwOnNullResult: true, resultType) + : null; } protected override ShapedQueryExpression TranslateCast(ShapedQueryExpression source, Type resultType) { - if (source.ShaperExpression.Type == resultType) + if (source.ShaperExpression.Type != resultType) { - return source; + source.ShaperExpression = Expression.Convert(source.ShaperExpression, resultType); } - source.ShaperExpression = Expression.Convert(source.ShaperExpression, resultType); - return source; } @@ -192,24 +189,23 @@ protected override ShapedQueryExpression TranslateContains(ShapedQueryExpression { var selectExpression = (SelectExpression)source.QueryExpression; var translation = TranslateExpression(item); - - if (translation != null) + if (translation == null) { - if (selectExpression.Limit == null - && selectExpression.Offset == null) - { - selectExpression.ClearOrdering(); - } - - selectExpression.ApplyProjection(); - translation = _sqlExpressionFactory.In(translation, selectExpression, false); - source.QueryExpression = _sqlExpressionFactory.Select(translation); - source.ShaperExpression = new ProjectionBindingExpression(source.QueryExpression, new ProjectionMember(), typeof(bool)); + return null; + } - return source; + if (selectExpression.Limit == null + && selectExpression.Offset == null) + { + selectExpression.ClearOrdering(); } - throw new InvalidOperationException(CoreStrings.TranslationFailed(item.Print())); + selectExpression.ApplyProjection(); + translation = _sqlExpressionFactory.In(translation, selectExpression, false); + source.QueryExpression = _sqlExpressionFactory.Select(translation); + source.ShaperExpression = new ProjectionBindingExpression(source.QueryExpression, new ProjectionMember(), typeof(bool)); + + return source; } protected override ShapedQueryExpression TranslateCount(ShapedQueryExpression source, LambdaExpression predicate) @@ -220,9 +216,17 @@ protected override ShapedQueryExpression TranslateCount(ShapedQueryExpression so if (predicate != null) { source = TranslateWhere(source, predicate); + if (source == null) + { + return null; + } } var translation = _sqlTranslator.TranslateCount(); + if (translation == null) + { + return null; + } var projectionMapping = new Dictionary { @@ -246,7 +250,7 @@ protected override ShapedQueryExpression TranslateDefaultIfEmpty(ShapedQueryExpr return source; } - throw new InvalidOperationException(CoreStrings.TranslationFailed(defaultValue.Print())); + return null; } protected override ShapedQueryExpression TranslateDistinct(ShapedQueryExpression source) @@ -257,7 +261,7 @@ protected override ShapedQueryExpression TranslateDistinct(ShapedQueryExpression } protected override ShapedQueryExpression TranslateElementAtOrDefault(ShapedQueryExpression source, Expression index, bool returnDefault) - => throw new InvalidOperationException(CoreStrings.TranslationFailed(index.Print())); + => null; protected override ShapedQueryExpression TranslateExcept(ShapedQueryExpression source1, ShapedQueryExpression source2) { @@ -270,6 +274,10 @@ protected override ShapedQueryExpression TranslateFirstOrDefault(ShapedQueryExpr if (predicate != null) { source = TranslateWhere(source, predicate); + if (source == null) + { + return null; + } } var selectExpression = (SelectExpression)source.QueryExpression; @@ -330,8 +338,7 @@ protected override ShapedQueryExpression TranslateGroupBy( return source; } - throw new InvalidOperationException(CoreStrings.TranslationFailed( - keySelector.Print() + "; " + elementSelector.Print() + "; " + resultSelector.Print())); + return null; } private Expression TranslateGroupingKey(Expression expression) @@ -380,10 +387,9 @@ private Expression TranslateGroupingKey(Expression expression) default: var translation = _sqlTranslator.Translate(expression); - if (translation == null) { - throw new InvalidOperationException(CoreStrings.TranslationFailed(expression.Print())); + return null; } return translation.Type == expression.Type @@ -434,9 +440,7 @@ protected override ShapedQueryExpression TranslateGroupJoin(ShapedQueryExpressio // innerTransparentIdentifierType); //} - // See #17236 - throw new InvalidOperationException(CoreStrings.TranslationFailed( - outerKeySelector.Print() + "; " + innerKeySelector.Print() + "; " + resultSelector.Print())); + return null; } protected override ShapedQueryExpression TranslateIntersect(ShapedQueryExpression source1, ShapedQueryExpression source2) @@ -469,12 +473,15 @@ protected override ShapedQueryExpression TranslateJoin( transparentIdentifierType); } - // See #17236 - throw new InvalidOperationException(CoreStrings.TranslationFailed( - outerKeySelector.Print() + "; " + innerKeySelector.Print() + "; " + resultSelector.Print())); + return null; } - protected override ShapedQueryExpression TranslateLeftJoin(ShapedQueryExpression outer, ShapedQueryExpression inner, LambdaExpression outerKeySelector, LambdaExpression innerKeySelector, LambdaExpression resultSelector) + protected override ShapedQueryExpression TranslateLeftJoin( + ShapedQueryExpression outer, + ShapedQueryExpression inner, + LambdaExpression outerKeySelector, + LambdaExpression innerKeySelector, + LambdaExpression resultSelector) { var joinPredicate = CreateJoinPredicate(outer, outerKeySelector, inner, innerKeySelector); if (joinPredicate != null) @@ -493,9 +500,7 @@ protected override ShapedQueryExpression TranslateLeftJoin(ShapedQueryExpression transparentIdentifierType); } - // See #17236 - throw new InvalidOperationException(CoreStrings.TranslationFailed( - outerKeySelector.Print() + "; " + innerKeySelector.Print() + "; " + resultSelector.Print())); + return null; } private SqlBinaryExpression CreateJoinPredicate( @@ -562,6 +567,10 @@ protected override ShapedQueryExpression TranslateLastOrDefault( if (predicate != null) { source = TranslateWhere(source, predicate); + if (source == null) + { + return null; + } } selectExpression.ReverseOrderings(); @@ -583,9 +592,18 @@ protected override ShapedQueryExpression TranslateLongCount(ShapedQueryExpressio if (predicate != null) { source = TranslateWhere(source, predicate); + if (source == null) + { + return null; + } } var translation = _sqlTranslator.TranslateLongCount(); + if (translation == null) + { + return null; + } + var projectionMapping = new Dictionary { { new ProjectionMember(), translation } @@ -692,18 +710,18 @@ protected override ShapedQueryExpression TranslateOfType(ShapedQueryExpression s protected override ShapedQueryExpression TranslateOrderBy(ShapedQueryExpression source, LambdaExpression keySelector, bool ascending) { var translation = TranslateLambdaExpression(source, keySelector); - if (translation != null) + if (translation == null) { - ((SelectExpression)source.QueryExpression).ApplyOrdering(new OrderingExpression(translation, ascending)); - - return source; + return null; } - throw new InvalidOperationException(CoreStrings.TranslationFailed(keySelector.Print())); + ((SelectExpression)source.QueryExpression).ApplyOrdering(new OrderingExpression(translation, ascending)); + + return source; } protected override ShapedQueryExpression TranslateReverse(ShapedQueryExpression source) - => throw new InvalidOperationException(CoreStrings.TranslationFailed(source.Print())); + => null; protected override ShapedQueryExpression TranslateSelect(ShapedQueryExpression source, LambdaExpression selector) { @@ -766,6 +784,10 @@ protected override ShapedQueryExpression TranslateSelectMany( if (defaultIfEmpty) { inner = TranslateDefaultIfEmpty(inner, null); + if (inner == null) + { + return null; + } } var transparentIdentifierType = TransparentIdentifierFactory.Create( @@ -783,9 +805,7 @@ protected override ShapedQueryExpression TranslateSelectMany( } } - // See #17236 - throw new InvalidOperationException(CoreStrings.TranslationFailed( - collectionSelector.Print() + "; " + resultSelector.Print())); + return null; } private class CorrelationFindingExpressionVisitor : ExpressionVisitor @@ -849,6 +869,10 @@ protected override ShapedQueryExpression TranslateSingleOrDefault(ShapedQueryExp if (predicate != null) { source = TranslateWhere(source, predicate); + if (source == null) + { + return null; + } } var selectExpression = (SelectExpression)source.QueryExpression; @@ -866,19 +890,18 @@ protected override ShapedQueryExpression TranslateSkip(ShapedQueryExpression sou { var selectExpression = (SelectExpression)source.QueryExpression; var translation = TranslateExpression(count); - - if (translation != null) + if (translation == null) { - selectExpression.ApplyOffset(translation); - - return source; + return null; } - throw new InvalidOperationException(CoreStrings.TranslationFailed(count.Print())); + selectExpression.ApplyOffset(translation); + + return source; } protected override ShapedQueryExpression TranslateSkipWhile(ShapedQueryExpression source, LambdaExpression predicate) - => throw new InvalidOperationException(CoreStrings.TranslationFailed(predicate.Print())); + => null; protected override ShapedQueryExpression TranslateSum(ShapedQueryExpression source, LambdaExpression selector, Type resultType) { @@ -890,44 +913,39 @@ protected override ShapedQueryExpression TranslateSum(ShapedQueryExpression sour : RemapLambdaBody(source, selector); var projection = _sqlTranslator.TranslateSum(newSelector); - - if (projection == null) - { - throw new InvalidOperationException(CoreStrings.TranslationFailed(selectExpression.Print())); - } - - return AggregateResultShaper(source, projection, throwOnNullResult: false, resultType); + return projection != null + ? AggregateResultShaper(source, projection, throwOnNullResult: false, resultType) + : null; } protected override ShapedQueryExpression TranslateTake(ShapedQueryExpression source, Expression count) { var selectExpression = (SelectExpression)source.QueryExpression; var translation = TranslateExpression(count); - - if (translation != null) + if (translation == null) { - selectExpression.ApplyLimit(translation); - - return source; + return null; } - throw new InvalidOperationException(CoreStrings.TranslationFailed(count.Print())); + selectExpression.ApplyLimit(translation); + + return source; } protected override ShapedQueryExpression TranslateTakeWhile(ShapedQueryExpression source, LambdaExpression predicate) - => throw new InvalidOperationException(CoreStrings.TranslationFailed(predicate.Print())); + => null; protected override ShapedQueryExpression TranslateThenBy(ShapedQueryExpression source, LambdaExpression keySelector, bool ascending) { var translation = TranslateLambdaExpression(source, keySelector); - if (translation != null) + if (translation == null) { - ((SelectExpression)source.QueryExpression).AppendOrdering(new OrderingExpression(translation, ascending)); - - return source; + return null; } - throw new InvalidOperationException(CoreStrings.TranslationFailed(keySelector.Print())); + ((SelectExpression)source.QueryExpression).AppendOrdering(new OrderingExpression(translation, ascending)); + + return source; } protected override ShapedQueryExpression TranslateUnion(ShapedQueryExpression source1, ShapedQueryExpression source2) @@ -939,28 +957,21 @@ protected override ShapedQueryExpression TranslateUnion(ShapedQueryExpression so protected override ShapedQueryExpression TranslateWhere(ShapedQueryExpression source, LambdaExpression predicate) { var translation = TranslateLambdaExpression(source, predicate); - if (translation != null) + if (translation == null) { - ((SelectExpression)source.QueryExpression).ApplyPredicate(translation); - - return source; + return null; } - throw new InvalidOperationException(CoreStrings.TranslationFailed(predicate.Print())); - } + ((SelectExpression)source.QueryExpression).ApplyPredicate(translation); - private SqlExpression TranslateExpression(Expression expression) - { - return _sqlTranslator.Translate(expression); + return source; } + private SqlExpression TranslateExpression(Expression expression) => _sqlTranslator.Translate(expression); + private SqlExpression TranslateLambdaExpression( ShapedQueryExpression shapedQueryExpression, LambdaExpression lambdaExpression) - { - var lambdaBody = RemapLambdaBody(shapedQueryExpression, lambdaExpression); - - return TranslateExpression(lambdaBody); - } + => TranslateExpression(RemapLambdaBody(shapedQueryExpression, lambdaExpression)); private Expression RemapLambdaBody(ShapedQueryExpression shapedQueryExpression, LambdaExpression lambdaExpression) { @@ -971,11 +982,9 @@ private Expression RemapLambdaBody(ShapedQueryExpression shapedQueryExpression, } internal Expression ExpandWeakEntities(SelectExpression selectExpression, Expression lambdaBody) - { - return _weakEntityExpandingExpressionVisitor.Expand(selectExpression, lambdaBody); - } + => _weakEntityExpandingExpressionVisitor.Expand(selectExpression, lambdaBody); - public class WeakEntityExpandingExpressionVisitor : ExpressionVisitor + private class WeakEntityExpandingExpressionVisitor : ExpressionVisitor { private SelectExpression _selectExpression; private readonly RelationalSqlTranslatingExpressionVisitor _sqlTranslator; @@ -1162,6 +1171,11 @@ private Expression Expand(Expression source, MemberIdentity member) private ShapedQueryExpression AggregateResultShaper( ShapedQueryExpression source, Expression projection, bool throwOnNullResult, Type resultType) { + if (projection == null) + { + return null; + } + var selectExpression = (SelectExpression)source.QueryExpression; selectExpression.ReplaceProjectionMapping( new Dictionary diff --git a/src/EFCore.Sqlite.Core/Query/Internal/SqliteQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.Sqlite.Core/Query/Internal/SqliteQueryableMethodTranslatingExpressionVisitor.cs index aa551713738..fcf079ad4a0 100644 --- a/src/EFCore.Sqlite.Core/Query/Internal/SqliteQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.Sqlite.Core/Query/Internal/SqliteQueryableMethodTranslatingExpressionVisitor.cs @@ -34,6 +34,11 @@ protected override QueryableMethodTranslatingExpressionVisitor CreateSubqueryVis protected override ShapedQueryExpression TranslateOrderBy(ShapedQueryExpression source, LambdaExpression keySelector, bool ascending) { var translation = base.TranslateOrderBy(source, keySelector, ascending); + if (translation == null) + { + return null; + } + var orderingExpression = ((SelectExpression)translation.QueryExpression).Orderings.Last(); var orderingExpressionType = GetProviderType(orderingExpression.Expression); if (orderingExpressionType == typeof(DateTimeOffset) @@ -51,6 +56,11 @@ protected override ShapedQueryExpression TranslateOrderBy(ShapedQueryExpression protected override ShapedQueryExpression TranslateThenBy(ShapedQueryExpression source, LambdaExpression keySelector, bool ascending) { var translation = base.TranslateThenBy(source, keySelector, ascending); + if (translation == null) + { + return null; + } + var orderingExpression = ((SelectExpression)translation.QueryExpression).Orderings.Last(); var orderingExpressionType = GetProviderType(orderingExpression.Expression); if (orderingExpressionType == typeof(DateTimeOffset) diff --git a/test/EFCore.Relational.Specification.Tests/Query/QueryNoClientEvalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/QueryNoClientEvalTestBase.cs index dea268a8426..d7d61fb17f1 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/QueryNoClientEvalTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/QueryNoClientEvalTestBase.cs @@ -12,7 +12,6 @@ // ReSharper disable InconsistentNaming namespace Microsoft.EntityFrameworkCore.Query { -#pragma warning disable CS0612 // Type or member is obsolete public abstract class QueryNoClientEvalTestBase : IClassFixture where TFixture : NorthwindQueryRelationalFixture, new() { @@ -26,10 +25,10 @@ public virtual void Throws_when_where() using (var context = CreateContext()) { Assert.Equal( - CoreStrings.TranslationFailed("(c) => c.IsLondon"), - Assert.Throws( + CoreStrings.TranslationFailed("Where( source: DbSet, predicate: (c) => c.IsLondon)"), + RemoveNewLines(Assert.Throws( () => context.Customers.Where(c => c.IsLondon).ToList()) - .Message); + .Message)); } } @@ -39,9 +38,9 @@ public virtual void Throws_when_orderby() using (var context = CreateContext()) { Assert.Equal( - CoreStrings.TranslationFailed("(c) => c.IsLondon"), - Assert.Throws( - () => context.Customers.OrderBy(c => c.IsLondon).ToList()).Message); + CoreStrings.TranslationFailed("OrderBy( source: DbSet, keySelector: (c) => c.IsLondon)"), + RemoveNewLines(Assert.Throws( + () => context.Customers.OrderBy(c => c.IsLondon).ToList()).Message)); } } @@ -51,12 +50,12 @@ public virtual void Throws_when_orderby_multiple() using (var context = CreateContext()) { Assert.Equal( - CoreStrings.TranslationFailed("(c) => c.IsLondon"), - Assert.Throws( + CoreStrings.TranslationFailed("OrderBy( source: DbSet, keySelector: (c) => c.IsLondon)"), + RemoveNewLines(Assert.Throws( () => context.Customers .OrderBy(c => c.IsLondon) .ThenBy(c => ClientMethod(c)) - .ToList()).Message); + .ToList()).Message)); } } @@ -69,7 +68,7 @@ public virtual void Throws_when_where_subquery_correlated() { Assert.Equal( CoreStrings.TranslationFailed( - "(c0) => EntityShaperExpression: EntityType: Customer ValueBufferExpression: ProjectionBindingExpression: EmptyProjectionMember IsNullable: False.CustomerID == c0.CustomerID && c0.IsLondon"), + "Any( source: DbSet, predicate: (c0) => EntityShaperExpression: EntityType: Customer ValueBufferExpression: ProjectionBindingExpression: EmptyProjectionMember IsNullable: False .CustomerID == c0.CustomerID && c0.IsLondon)"), RemoveNewLines( Assert.Throws( () => context.Customers @@ -86,9 +85,9 @@ public virtual void Throws_when_all() using (var context = CreateContext()) { Assert.Equal( - CoreStrings.TranslationFailed("(c) => c.IsLondon"), - Assert.Throws( - () => context.Customers.All(c => c.IsLondon)).Message); + CoreStrings.TranslationFailed("All( source: DbSet, predicate: (c) => c.IsLondon)"), + RemoveNewLines(Assert.Throws( + () => context.Customers.All(c => c.IsLondon)).Message)); } } @@ -98,12 +97,12 @@ public virtual void Throws_when_from_sql_composed() using (var context = CreateContext()) { Assert.Equal( - CoreStrings.TranslationFailed("(c) => c.IsLondon"), - Assert.Throws( + CoreStrings.TranslationFailed("Where( source: FromSqlOnQueryable( source: DbSet, sql: \"select * from \"Customers\"\", parameters: (Unhandled parameter: __p_0)), predicate: (c) => c.IsLondon)"), + RemoveNewLines(Assert.Throws( () => context.Customers .FromSqlRaw(NormalizeDelimetersInRawString("select * from [Customers]")) .Where(c => c.IsLondon) - .ToList()).Message); + .ToList()).Message)); } } @@ -127,15 +126,15 @@ public virtual void Throws_when_subquery_main_from_clause() using (var context = CreateContext()) { Assert.Equal( - CoreStrings.TranslationFailed("(c) => c.IsLondon"), - Assert.Throws( + CoreStrings.TranslationFailed("Where( source: DbSet, predicate: (c) => c.IsLondon)"), + RemoveNewLines(Assert.Throws( () => (from c1 in context.Customers .Where(c => c.IsLondon) .OrderBy(c => c.CustomerID) .Take(5) select c1) - .ToList()).Message); + .ToList()).Message)); } } @@ -146,7 +145,7 @@ public virtual void Throws_when_select_many() { Assert.Equal( CoreStrings.QueryFailed("(c1) => int[] { 1, 2, 3, }", "NavigationExpandingExpressionVisitor"), - Assert.Throws( + RemoveNewLines(Assert.Throws( () => (from c1 in context.Customers from i in new[] @@ -154,7 +153,7 @@ from i in new[] 1, 2, 3 } select c1) - .ToList()).Message); + .ToList()).Message)); } } @@ -210,10 +209,10 @@ public virtual void Throws_when_group_by() .ToList(); Assert.Equal( CoreStrings.TranslationFailed("GroupBy([c].CustomerID, [c])"), - Assert.Throws( + RemoveNewLines(Assert.Throws( () => context.Customers .GroupBy(c => c.CustomerID) - .ToList()).Message); + .ToList()).Message)); } } @@ -223,9 +222,9 @@ public virtual void Throws_when_first() using (var context = CreateContext()) { Assert.Equal( - CoreStrings.TranslationFailed("(c) => c.IsLondon"), - Assert.Throws( - () => context.Customers.First(c => c.IsLondon)).Message); + CoreStrings.TranslationFailed("Where( source: DbSet, predicate: (c) => c.IsLondon)"), + RemoveNewLines(Assert.Throws( + () => context.Customers.First(c => c.IsLondon)).Message)); } } @@ -235,9 +234,9 @@ public virtual void Throws_when_single() using (var context = CreateContext()) { Assert.Equal( - CoreStrings.TranslationFailed("(c) => c.IsLondon"), - Assert.Throws( - () => context.Customers.Single(c => c.IsLondon)).Message); + CoreStrings.TranslationFailed("Where( source: DbSet, predicate: (c) => c.IsLondon)"), + RemoveNewLines(Assert.Throws( + () => context.Customers.Single(c => c.IsLondon)).Message)); } } @@ -247,9 +246,9 @@ public virtual void Throws_when_first_or_default() using (var context = CreateContext()) { Assert.Equal( - CoreStrings.TranslationFailed("(c) => c.IsLondon"), - Assert.Throws( - () => context.Customers.FirstOrDefault(c => c.IsLondon)).Message); + CoreStrings.TranslationFailed("Where( source: DbSet, predicate: (c) => c.IsLondon)"), + RemoveNewLines(Assert.Throws( + () => context.Customers.FirstOrDefault(c => c.IsLondon)).Message)); } } @@ -259,9 +258,9 @@ public virtual void Throws_when_single_or_default() using (var context = CreateContext()) { Assert.Equal( - CoreStrings.TranslationFailed("(c) => c.IsLondon"), - Assert.Throws( - () => context.Customers.SingleOrDefault(c => c.IsLondon)).Message); + CoreStrings.TranslationFailed("Where( source: DbSet, predicate: (c) => c.IsLondon)"), + RemoveNewLines(Assert.Throws( + () => context.Customers.SingleOrDefault(c => c.IsLondon)).Message)); } } @@ -276,5 +275,4 @@ private FormattableString NormalizeDelimetersInInterpolatedString(FormattableStr protected NorthwindContext CreateContext() => Fixture.CreateContext(); } -#pragma warning restore CS0612 // Type or member is obsolete } diff --git a/test/EFCore.Relational.Specification.Tests/Query/UdfDbFunctionTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/UdfDbFunctionTestBase.cs index 4840c17e8d4..74178ea1509 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/UdfDbFunctionTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/UdfDbFunctionTestBase.cs @@ -614,11 +614,11 @@ public virtual void Scalar_Nested_Function_Unwind_Client_Eval_Where_Static() using (var context = CreateContext()) { Assert.Equal( - CoreStrings.TranslationFailed("(c) => 2 == AddOneStatic(c.Id)"), - Assert.Throws( + CoreStrings.TranslationFailed("Where( source: DbSet, predicate: (c) => 2 == AddOneStatic(c.Id))"), + RemoveNewLines(Assert.Throws( () => (from c in context.Customers where 2 == UDFSqlContext.AddOneStatic(c.Id) - select c.Id).Single()).Message); + select c.Id).Single()).Message)); } } @@ -628,11 +628,11 @@ public virtual void Scalar_Nested_Function_Unwind_Client_Eval_OrderBy_Static() using (var context = CreateContext()) { Assert.Equal( - CoreStrings.TranslationFailed("(c) => AddOneStatic(c.Id)"), - Assert.Throws( + CoreStrings.TranslationFailed("OrderBy( source: DbSet, keySelector: (c) => AddOneStatic(c.Id))"), + RemoveNewLines(Assert.Throws( () => (from c in context.Customers orderby UDFSqlContext.AddOneStatic(c.Id) - select c.Id).ToList()).Message); + select c.Id).ToList()).Message)); } } @@ -657,11 +657,11 @@ public virtual void Scalar_Nested_Function_Client_BCL_UDF_Static() { Assert.Equal( CoreStrings.TranslationFailed( - "(c) => 2 == AddOneStatic(Abs(CustomerOrderCountWithClientStatic(c.Id)))"), - Assert.Throws( + "Where( source: DbSet, predicate: (c) => 2 == AddOneStatic(Abs(CustomerOrderCountWithClientStatic(c.Id))))"), + RemoveNewLines(Assert.Throws( () => (from c in context.Customers where 2 == UDFSqlContext.AddOneStatic(Math.Abs(UDFSqlContext.CustomerOrderCountWithClientStatic(c.Id))) - select c.Id).Single()).Message); + select c.Id).Single()).Message)); } } @@ -672,11 +672,11 @@ public virtual void Scalar_Nested_Function_Client_UDF_BCL_Static() { Assert.Equal( CoreStrings.TranslationFailed( - "(c) => 2 == AddOneStatic(CustomerOrderCountWithClientStatic(Abs(c.Id)))"), - Assert.Throws( + "Where( source: DbSet, predicate: (c) => 2 == AddOneStatic(CustomerOrderCountWithClientStatic(Abs(c.Id))))"), + RemoveNewLines(Assert.Throws( () => (from c in context.Customers where 2 == UDFSqlContext.AddOneStatic(UDFSqlContext.CustomerOrderCountWithClientStatic(Math.Abs(c.Id))) - select c.Id).Single()).Message); + select c.Id).Single()).Message)); } } @@ -687,11 +687,11 @@ public virtual void Scalar_Nested_Function_BCL_Client_UDF_Static() { Assert.Equal( CoreStrings.TranslationFailed( - "(c) => 2 == Abs(AddOneStatic(CustomerOrderCountWithClientStatic(c.Id)))"), - Assert.Throws( + "Where( source: DbSet, predicate: (c) => 2 == Abs(AddOneStatic(CustomerOrderCountWithClientStatic(c.Id))))"), + RemoveNewLines(Assert.Throws( () => (from c in context.Customers where 2 == Math.Abs(UDFSqlContext.AddOneStatic(UDFSqlContext.CustomerOrderCountWithClientStatic(c.Id))) - select c.Id).Single()).Message); + select c.Id).Single()).Message)); } } @@ -702,11 +702,11 @@ public virtual void Scalar_Nested_Function_BCL_UDF_Client_Static() { Assert.Equal( CoreStrings.TranslationFailed( - "(c) => 1 == Abs(CustomerOrderCountWithClientStatic(AddOneStatic(c.Id)))"), - Assert.Throws( + "Where( source: DbSet, predicate: (c) => 1 == Abs(CustomerOrderCountWithClientStatic(AddOneStatic(c.Id))))"), + RemoveNewLines(Assert.Throws( () => (from c in context.Customers where 1 == Math.Abs(UDFSqlContext.CustomerOrderCountWithClientStatic(UDFSqlContext.AddOneStatic(c.Id))) - select c.Id).Single()).Message); + select c.Id).Single()).Message)); } } @@ -717,11 +717,11 @@ public virtual void Scalar_Nested_Function_UDF_BCL_Client_Static() { Assert.Equal( CoreStrings.TranslationFailed( - "(c) => 1 == CustomerOrderCountWithClientStatic(Abs(AddOneStatic(c.Id)))"), - Assert.Throws( + "Where( source: DbSet, predicate: (c) => 1 == CustomerOrderCountWithClientStatic(Abs(AddOneStatic(c.Id))))"), + RemoveNewLines(Assert.Throws( () => (from c in context.Customers where 1 == UDFSqlContext.CustomerOrderCountWithClientStatic(Math.Abs(UDFSqlContext.AddOneStatic(c.Id))) - select c.Id).Single()).Message); + select c.Id).Single()).Message)); } } @@ -732,11 +732,11 @@ public virtual void Scalar_Nested_Function_UDF_Client_BCL_Static() { Assert.Equal( CoreStrings.TranslationFailed( - "(c) => 1 == CustomerOrderCountWithClientStatic(AddOneStatic(Abs(c.Id)))"), - Assert.Throws( + "Where( source: DbSet, predicate: (c) => 1 == CustomerOrderCountWithClientStatic(AddOneStatic(Abs(c.Id))))"), + RemoveNewLines(Assert.Throws( () => (from c in context.Customers where 1 == UDFSqlContext.CustomerOrderCountWithClientStatic(UDFSqlContext.AddOneStatic(Math.Abs(c.Id))) - select c.Id).Single()).Message); + select c.Id).Single()).Message)); } } @@ -746,11 +746,11 @@ public virtual void Scalar_Nested_Function_Client_BCL_Static() using (var context = CreateContext()) { Assert.Equal( - CoreStrings.TranslationFailed("(c) => 3 == AddOneStatic(Abs(c.Id))"), - Assert.Throws( + CoreStrings.TranslationFailed("Where( source: DbSet, predicate: (c) => 3 == AddOneStatic(Abs(c.Id)))"), + RemoveNewLines(Assert.Throws( () => (from c in context.Customers where 3 == UDFSqlContext.AddOneStatic(Math.Abs(c.Id)) - select c.Id).Single()).Message); + select c.Id).Single()).Message)); } } @@ -760,11 +760,11 @@ public virtual void Scalar_Nested_Function_Client_UDF_Static() using (var context = CreateContext()) { Assert.Equal( - CoreStrings.TranslationFailed("(c) => 2 == AddOneStatic(CustomerOrderCountWithClientStatic(c.Id))"), - Assert.Throws( + CoreStrings.TranslationFailed("Where( source: DbSet, predicate: (c) => 2 == AddOneStatic(CustomerOrderCountWithClientStatic(c.Id)))"), + RemoveNewLines(Assert.Throws( () => (from c in context.Customers where 2 == UDFSqlContext.AddOneStatic(UDFSqlContext.CustomerOrderCountWithClientStatic(c.Id)) - select c.Id).Single()).Message); + select c.Id).Single()).Message)); } } @@ -774,11 +774,11 @@ public virtual void Scalar_Nested_Function_BCL_Client_Static() using (var context = CreateContext()) { Assert.Equal( - CoreStrings.TranslationFailed("(c) => 3 == Abs(AddOneStatic(c.Id))"), - Assert.Throws( + CoreStrings.TranslationFailed("Where( source: DbSet, predicate: (c) => 3 == Abs(AddOneStatic(c.Id)))"), + RemoveNewLines(Assert.Throws( () => (from c in context.Customers where 3 == Math.Abs(UDFSqlContext.AddOneStatic(c.Id)) - select c.Id).Single()).Message); + select c.Id).Single()).Message)); } } @@ -801,11 +801,11 @@ public virtual void Scalar_Nested_Function_UDF_Client_Static() using (var context = CreateContext()) { Assert.Equal( - CoreStrings.TranslationFailed("(c) => 2 == CustomerOrderCountWithClientStatic(AddOneStatic(c.Id))"), - Assert.Throws( + CoreStrings.TranslationFailed("Where( source: DbSet, predicate: (c) => 2 == CustomerOrderCountWithClientStatic(AddOneStatic(c.Id)))"), + RemoveNewLines(Assert.Throws( () => (from c in context.Customers where 2 == UDFSqlContext.CustomerOrderCountWithClientStatic(UDFSqlContext.AddOneStatic(c.Id)) - select c.Id).Single()).Message); + select c.Id).Single()).Message)); } } @@ -1134,11 +1134,11 @@ public virtual void Scalar_Nested_Function_Unwind_Client_Eval_Where_Instance() using (var context = CreateContext()) { Assert.Equal( - CoreStrings.TranslationFailed("(c) => 2 == (Unhandled parameter: __context_0).AddOneInstance(c.Id)"), - Assert.Throws( + CoreStrings.TranslationFailed("Where( source: DbSet, predicate: (c) => 2 == (Unhandled parameter: __context_0).AddOneInstance(c.Id))"), + RemoveNewLines(Assert.Throws( () => (from c in context.Customers where 2 == context.AddOneInstance(c.Id) - select c.Id).Single()).Message); + select c.Id).Single()).Message)); } } @@ -1148,11 +1148,11 @@ public virtual void Scalar_Nested_Function_Unwind_Client_Eval_OrderBy_Instance() using (var context = CreateContext()) { Assert.Equal( - CoreStrings.TranslationFailed("(c) => (Unhandled parameter: __context_0).AddOneInstance(c.Id)"), - Assert.Throws( + CoreStrings.TranslationFailed("OrderBy( source: DbSet, keySelector: (c) => (Unhandled parameter: __context_0).AddOneInstance(c.Id))"), + RemoveNewLines(Assert.Throws( () => (from c in context.Customers orderby context.AddOneInstance(c.Id) - select c.Id).ToList()).Message); + select c.Id).ToList()).Message)); } } @@ -1177,11 +1177,11 @@ public virtual void Scalar_Nested_Function_Client_BCL_UDF_Instance() { Assert.Equal( CoreStrings.TranslationFailed( - "(c) => 2 == (Unhandled parameter: __context_0).AddOneInstance(Abs((Unhandled parameter: __context_0).CustomerOrderCountWithClientInstance(c.Id)))"), - Assert.Throws( + "Where( source: DbSet, predicate: (c) => 2 == (Unhandled parameter: __context_0).AddOneInstance(Abs((Unhandled parameter: __context_0).CustomerOrderCountWithClientInstance(c.Id))))"), + RemoveNewLines(Assert.Throws( () => (from c in context.Customers where 2 == context.AddOneInstance(Math.Abs(context.CustomerOrderCountWithClientInstance(c.Id))) - select c.Id).Single()).Message); + select c.Id).Single()).Message)); } } @@ -1192,11 +1192,11 @@ public virtual void Scalar_Nested_Function_Client_UDF_BCL_Instance() { Assert.Equal( CoreStrings.TranslationFailed( - "(c) => 2 == (Unhandled parameter: __context_0).AddOneInstance((Unhandled parameter: __context_0).CustomerOrderCountWithClientInstance(Abs(c.Id)))"), - Assert.Throws( + "Where( source: DbSet, predicate: (c) => 2 == (Unhandled parameter: __context_0).AddOneInstance((Unhandled parameter: __context_0).CustomerOrderCountWithClientInstance(Abs(c.Id))))"), + RemoveNewLines(Assert.Throws( () => (from c in context.Customers where 2 == context.AddOneInstance(context.CustomerOrderCountWithClientInstance(Math.Abs(c.Id))) - select c.Id).Single()).Message); + select c.Id).Single()).Message)); } } @@ -1206,11 +1206,11 @@ public virtual void Scalar_Nested_Function_BCL_Client_UDF_Instance() using (var context = CreateContext()) { Assert.Equal( - CoreStrings.TranslationFailed("(c) => 2 == Abs((Unhandled parameter: __context_0).AddOneInstance((Unhandled parameter: __context_0).CustomerOrderCountWithClientInstance(c.Id)))"), - Assert.Throws( + CoreStrings.TranslationFailed("Where( source: DbSet, predicate: (c) => 2 == Abs((Unhandled parameter: __context_0).AddOneInstance((Unhandled parameter: __context_0).CustomerOrderCountWithClientInstance(c.Id))))"), + RemoveNewLines(Assert.Throws( () => (from c in context.Customers where 2 == Math.Abs(context.AddOneInstance(context.CustomerOrderCountWithClientInstance(c.Id))) - select c.Id).Single()).Message); + select c.Id).Single()).Message)); } } @@ -1221,11 +1221,11 @@ public virtual void Scalar_Nested_Function_BCL_UDF_Client_Instance() { Assert.Equal( CoreStrings.TranslationFailed( - "(c) => 1 == Abs((Unhandled parameter: __context_0).CustomerOrderCountWithClientInstance((Unhandled parameter: __context_0).AddOneInstance(c.Id)))"), - Assert.Throws( + "Where( source: DbSet, predicate: (c) => 1 == Abs((Unhandled parameter: __context_0).CustomerOrderCountWithClientInstance((Unhandled parameter: __context_0).AddOneInstance(c.Id))))"), + RemoveNewLines(Assert.Throws( () => (from c in context.Customers where 1 == Math.Abs(context.CustomerOrderCountWithClientInstance(context.AddOneInstance(c.Id))) - select c.Id).Single()).Message); + select c.Id).Single()).Message)); } } @@ -1236,11 +1236,11 @@ public virtual void Scalar_Nested_Function_UDF_BCL_Client_Instance() { Assert.Equal( CoreStrings.TranslationFailed( - "(c) => 1 == (Unhandled parameter: __context_0).CustomerOrderCountWithClientInstance(Abs((Unhandled parameter: __context_0).AddOneInstance(c.Id)))"), - Assert.Throws( + "Where( source: DbSet, predicate: (c) => 1 == (Unhandled parameter: __context_0).CustomerOrderCountWithClientInstance(Abs((Unhandled parameter: __context_0).AddOneInstance(c.Id))))"), + RemoveNewLines(Assert.Throws( () => (from c in context.Customers where 1 == context.CustomerOrderCountWithClientInstance(Math.Abs(context.AddOneInstance(c.Id))) - select c.Id).Single()).Message); + select c.Id).Single()).Message)); } } @@ -1251,11 +1251,11 @@ public virtual void Scalar_Nested_Function_UDF_Client_BCL_Instance() { Assert.Equal( CoreStrings.TranslationFailed( - "(c) => 1 == (Unhandled parameter: __context_0).CustomerOrderCountWithClientInstance((Unhandled parameter: __context_0).AddOneInstance(Abs(c.Id)))"), - Assert.Throws( + "Where( source: DbSet, predicate: (c) => 1 == (Unhandled parameter: __context_0).CustomerOrderCountWithClientInstance((Unhandled parameter: __context_0).AddOneInstance(Abs(c.Id))))"), + RemoveNewLines(Assert.Throws( () => (from c in context.Customers where 1 == context.CustomerOrderCountWithClientInstance(context.AddOneInstance(Math.Abs(c.Id))) - select c.Id).Single()).Message); + select c.Id).Single()).Message)); } } @@ -1266,11 +1266,11 @@ public virtual void Scalar_Nested_Function_Client_BCL_Instance() { Assert.Equal( CoreStrings.TranslationFailed( - "(c) => 3 == (Unhandled parameter: __context_0).AddOneInstance(Abs(c.Id))"), - Assert.Throws( + "Where( source: DbSet, predicate: (c) => 3 == (Unhandled parameter: __context_0).AddOneInstance(Abs(c.Id)))"), + RemoveNewLines(Assert.Throws( () => (from c in context.Customers where 3 == context.AddOneInstance(Math.Abs(c.Id)) - select c.Id).Single()).Message); + select c.Id).Single()).Message)); } } @@ -1281,11 +1281,11 @@ public virtual void Scalar_Nested_Function_Client_UDF_Instance() { Assert.Equal( CoreStrings.TranslationFailed( - "(c) => 2 == (Unhandled parameter: __context_0).AddOneInstance((Unhandled parameter: __context_0).CustomerOrderCountWithClientInstance(c.Id))"), - Assert.Throws( + "Where( source: DbSet, predicate: (c) => 2 == (Unhandled parameter: __context_0).AddOneInstance((Unhandled parameter: __context_0).CustomerOrderCountWithClientInstance(c.Id)))"), + RemoveNewLines(Assert.Throws( () => (from c in context.Customers where 2 == context.AddOneInstance(context.CustomerOrderCountWithClientInstance(c.Id)) - select c.Id).Single()).Message); + select c.Id).Single()).Message)); } } @@ -1295,11 +1295,11 @@ public virtual void Scalar_Nested_Function_BCL_Client_Instance() using (var context = CreateContext()) { Assert.Equal( - CoreStrings.TranslationFailed("(c) => 3 == Abs((Unhandled parameter: __context_0).AddOneInstance(c.Id))"), - Assert.Throws( + CoreStrings.TranslationFailed("Where( source: DbSet, predicate: (c) => 3 == Abs((Unhandled parameter: __context_0).AddOneInstance(c.Id)))"), + RemoveNewLines(Assert.Throws( () => (from c in context.Customers where 3 == Math.Abs(context.AddOneInstance(c.Id)) - select c.Id).Single()).Message); + select c.Id).Single()).Message)); } } @@ -1331,11 +1331,11 @@ public virtual void Scalar_Nested_Function_UDF_Client_Instance() { Assert.Equal( CoreStrings.TranslationFailed( - "(c) => 2 == (Unhandled parameter: __context_0).CustomerOrderCountWithClientInstance((Unhandled parameter: __context_0).AddOneInstance(c.Id))"), - Assert.Throws( + "Where( source: DbSet, predicate: (c) => 2 == (Unhandled parameter: __context_0).CustomerOrderCountWithClientInstance((Unhandled parameter: __context_0).AddOneInstance(c.Id)))"), + RemoveNewLines(Assert.Throws( () => (from c in context.Customers where 2 == context.CustomerOrderCountWithClientInstance(context.AddOneInstance(c.Id)) - select c.Id).Single()).Message); + select c.Id).Single()).Message)); } } @@ -1355,5 +1355,8 @@ public virtual void Scalar_Nested_Function_UDF_BCL_Instance() #endregion #endregion + + private string RemoveNewLines(string message) + => message.Replace("\n", "").Replace("\r", ""); } } diff --git a/test/EFCore.Specification.Tests/Query/FiltersTestBase.cs b/test/EFCore.Specification.Tests/Query/FiltersTestBase.cs index b4236a1d54b..913d0312519 100644 --- a/test/EFCore.Specification.Tests/Query/FiltersTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/FiltersTestBase.cs @@ -51,9 +51,9 @@ public virtual void Find() public virtual void Client_eval() { Assert.Equal( - CoreStrings.TranslationFailed("(p) => ClientMethod(p)"), - Assert.Throws( - () => _context.Products.ToList()).Message); + CoreStrings.TranslationFailed("Where( source: DbSet, predicate: (p) => ClientMethod(p))"), + RemoveNewLines(Assert.Throws( + () => _context.Products.ToList()).Message)); } [ConditionalFact] @@ -135,9 +135,9 @@ public virtual void Project_reference_that_itself_has_query_filter_with_another_ public virtual void Included_one_to_many_query_with_client_eval() { Assert.Equal( - CoreStrings.TranslationFailed("(p) => ClientMethod(p)"), - Assert.Throws( - () => _context.Products.Include(p => p.OrderDetails).ToList()).Message); + CoreStrings.TranslationFailed("Where( source: DbSet, predicate: (p) => ClientMethod(p))"), + RemoveNewLines(Assert.Throws( + () => _context.Products.Include(p => p.OrderDetails).ToList()).Message)); } [ConditionalFact(Skip = "issue #15081")] @@ -171,5 +171,8 @@ public virtual void Compiled_query() protected NorthwindContext CreateContext() => Fixture.CreateContext(); public void Dispose() => _context.Dispose(); + + private string RemoveNewLines(string message) + => message.Replace("\n", "").Replace("\r", ""); } } diff --git a/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs index 678e8fe27cc..aa116131d95 100644 --- a/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs @@ -1328,7 +1328,7 @@ orderby g.Nickname assertOrder: true))).Message; Assert.Equal( - CoreStrings.TranslationFailed("(g) => g.LeaderNickname != null ? new { HasSoulPatch = g.HasSoulPatch } : null == null"), + CoreStrings.TranslationFailed("Where( source: OrderBy( source: DbSet, keySelector: (g) => g.Nickname), predicate: (g) => g.LeaderNickname != null ? new { HasSoulPatch = g.HasSoulPatch } : null == null)"), RemoveNewLines(message)); } @@ -1370,7 +1370,7 @@ public virtual async Task Where_coalesce_with_anonymous_types(bool isAsync) select g.Nickname))).Message; Assert.Equal( - CoreStrings.TranslationFailed("(g) => new { Name = g.LeaderNickname } ?? new { Name = g.FullName } != null"), + CoreStrings.TranslationFailed("Where( source: DbSet, predicate: (g) => new { Name = g.LeaderNickname } ?? new { Name = g.FullName } != null)"), RemoveNewLines(message)); } @@ -1804,7 +1804,7 @@ public virtual async Task Select_Where_Navigation_Client(bool isAsync) select t))).Message; Assert.Equal( - CoreStrings.TranslationFailed("(c) => Property(c.Inner, \"Nickname\") != null && c.Inner.IsMarcus"), + CoreStrings.TranslationFailed("Where>( source: LeftJoin>( outer: DbSet, inner: DbSet, outerKeySelector: (c) => new AnonymousObject(new object[] { (object)Property(c, \"GearNickName\"), (object)Property>(c, \"GearSquadId\") }), innerKeySelector: (g) => new AnonymousObject(new object[] { (object)Property(g, \"Nickname\"), (object)Property>(g, \"SquadId\") }), resultSelector: (o, i) => new TransparentIdentifier( Outer = o, Inner = i )), predicate: (c) => Property(c.Inner, \"Nickname\") != null && c.Inner.IsMarcus)"), RemoveNewLines(message)); } @@ -3083,7 +3083,7 @@ public virtual async Task Client_side_equality_with_parameter_works_with_optiona elementAsserter: (e, a) => Assert.Equal(e.Nickname, a.Nickname)))).Message; Assert.Equal( - CoreStrings.TranslationFailed("(g) => ClientEquals( first: g.Inner.Note, second: (Unhandled parameter: __prm_0))"), + CoreStrings.TranslationFailed("Where>( source: LeftJoin>( outer: DbSet, inner: DbSet, outerKeySelector: (g) => new AnonymousObject(new object[] { (object)Property(g, \"Nickname\"), (object)Property>(g, \"SquadId\") }), innerKeySelector: (c) => new AnonymousObject(new object[] { (object)Property(c, \"GearNickName\"), (object)Property>(c, \"GearSquadId\") }), resultSelector: (o, i) => new TransparentIdentifier( Outer = o, Inner = i )), predicate: (g) => ClientEquals( first: g.Inner.Note, second: (Unhandled parameter: __prm_0)))"), RemoveNewLines(message)); } @@ -3417,7 +3417,7 @@ public virtual async Task Client_method_on_collection_navigation_in_predicate(bo select g.Nickname))).Message; Assert.Equal( - CoreStrings.TranslationFailed("(g) => g.HasSoulPatch && FavoriteWeapon(MaterializeCollectionNavigation(Navigation: Gear.Weapons (k__BackingField, ICollection) Collection ToDependent Weapon Inverse: Owner, Where( source: DbSet, predicate: (w) => Property(g, \"FullName\") == Property(w, \"OwnerFullName\")))).Name == \"Marcus' Lancer\""), + CoreStrings.TranslationFailed("Where( source: DbSet, predicate: (g) => g.HasSoulPatch && FavoriteWeapon(MaterializeCollectionNavigation(Navigation: Gear.Weapons (k__BackingField, ICollection) Collection ToDependent Weapon Inverse: Owner, Where( source: DbSet, predicate: (w) => Property(g, \"FullName\") == Property(w, \"OwnerFullName\")))).Name == \"Marcus' Lancer\")"), RemoveNewLines(message)); } @@ -3436,7 +3436,7 @@ public virtual async Task Client_method_on_collection_navigation_in_predicate_ac select g.Nickname))).Message; Assert.Equal( - CoreStrings.TranslationFailed("(g) => !(g.HasSoulPatch) && FavoriteWeapon(MaterializeCollectionNavigation(Navigation: Gear.Weapons (k__BackingField, ICollection) Collection ToDependent Weapon Inverse: Owner, Where( source: DbSet, predicate: (w) => Property(g, \"FullName\") == Property(w, \"OwnerFullName\")))).Name == \"Cole's Gnasher\""), + CoreStrings.TranslationFailed("Where( source: DbSet, predicate: (g) => !(g.HasSoulPatch) && FavoriteWeapon(MaterializeCollectionNavigation(Navigation: Gear.Weapons (k__BackingField, ICollection) Collection ToDependent Weapon Inverse: Owner, Where( source: DbSet, predicate: (w) => Property(g, \"FullName\") == Property(w, \"OwnerFullName\")))).Name == \"Cole's Gnasher\")"), RemoveNewLines(message)); } @@ -3454,7 +3454,7 @@ orderby FavoriteWeapon(g.Weapons).Name descending assertOrder: true))).Message; Assert.Equal( - CoreStrings.TranslationFailed("(g) => FavoriteWeapon(MaterializeCollectionNavigation(Navigation: Gear.Weapons (k__BackingField, ICollection) Collection ToDependent Weapon Inverse: Owner, Where( source: DbSet, predicate: (w) => Property(g, \"FullName\") == Property(w, \"OwnerFullName\")))).Name"), + CoreStrings.TranslationFailed("OrderByDescending( source: Where( source: DbSet, predicate: (g) => !(g.HasSoulPatch)), keySelector: (g) => FavoriteWeapon(MaterializeCollectionNavigation(Navigation: Gear.Weapons (k__BackingField, ICollection) Collection ToDependent Weapon Inverse: Owner, Where( source: DbSet, predicate: (w) => Property(g, \"FullName\") == Property(w, \"OwnerFullName\")))).Name)"), RemoveNewLines(message)); } @@ -6496,7 +6496,7 @@ public virtual async Task Correlated_collection_order_by_constant_null_of_non_ma }))).Message; Assert.Equal( - CoreStrings.TranslationFailed("(g) => null"), + CoreStrings.TranslationFailed("OrderByDescending( source: DbSet, keySelector: (g) => null)"), RemoveNewLines(message)); } @@ -7213,7 +7213,7 @@ public virtual async Task GetValueOrDefault_on_DateTimeOffset(bool isAsync) ms => ms.Where(m => ((DateTimeOffset?)m.Timeline).GetValueOrDefault() == defaultValue)))).Message; Assert.Equal( - CoreStrings.TranslationFailed("(m) => (Nullable)m.Timeline.GetValueOrDefault() == (Unhandled parameter: __defaultValue_0)"), + CoreStrings.TranslationFailed("Where( source: DbSet, predicate: (m) => (Nullable)m.Timeline.GetValueOrDefault() == (Unhandled parameter: __defaultValue_0))"), RemoveNewLines(message)); } diff --git a/test/EFCore.Specification.Tests/Query/IncludeAsyncTestBase.cs b/test/EFCore.Specification.Tests/Query/IncludeAsyncTestBase.cs index 134662acf0c..aad6d1b5f73 100644 --- a/test/EFCore.Specification.Tests/Query/IncludeAsyncTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/IncludeAsyncTestBase.cs @@ -715,12 +715,12 @@ public virtual async Task Include_collection_with_client_filter() using (var context = CreateContext()) { Assert.Equal( - CoreStrings.TranslationFailed("(c) => c.IsLondon"), + CoreStrings.TranslationFailed("Where( source: DbSet, predicate: (c) => c.IsLondon)"), (await Assert.ThrowsAsync( () => context.Set() .Include(c => c.Orders) .Where(c => c.IsLondon) - .ToListAsync())).Message); + .ToListAsync())).Message.Replace("\r", "").Replace("\n","")); } } diff --git a/test/EFCore.Specification.Tests/Query/IncludeTestBase.cs b/test/EFCore.Specification.Tests/Query/IncludeTestBase.cs index a9d1f8f5e25..b306ab9f21e 100644 --- a/test/EFCore.Specification.Tests/Query/IncludeTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/IncludeTestBase.cs @@ -1991,7 +1991,7 @@ public virtual void Include_collection_with_client_filter(bool useString) using (var context = CreateContext()) { Assert.Equal( - CoreStrings.TranslationFailed("(c) => c.IsLondon"), + CoreStrings.TranslationFailed("Where( source: DbSet, predicate: (c) => c.IsLondon)"), Assert.Throws( () => useString ? context.Set() @@ -2001,7 +2001,7 @@ public virtual void Include_collection_with_client_filter(bool useString) : context.Set() .Include(c => c.Orders) .Where(c => c.IsLondon) - .ToList()).Message); + .ToList()).Message.Replace("\r", "").Replace("\n", "")); } } diff --git a/test/EFCore.Specification.Tests/Query/QueryNavigationsTestBase.cs b/test/EFCore.Specification.Tests/Query/QueryNavigationsTestBase.cs index aa267139027..23379258937 100644 --- a/test/EFCore.Specification.Tests/Query/QueryNavigationsTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/QueryNavigationsTestBase.cs @@ -47,7 +47,7 @@ public virtual async Task Join_with_nav_projected_in_subquery_when_client_eval(b { Assert.Equal( CoreStrings.TranslationFailed( - "(c) => c.CustomerID; (o) => ClientProjection( t: o.Outer, _: o.Inner).CustomerID; (c, o) => new TransparentIdentifier>( Outer = c, Inner = o)"), + "Join, string, TransparentIdentifier>>( outer: DbSet, inner: LeftJoin>( outer: DbSet, inner: DbSet, outerKeySelector: (o) => Property(o, \"CustomerID\"), innerKeySelector: (c0) => Property(c0, \"CustomerID\"), resultSelector: (o, i) => new TransparentIdentifier( Outer = o, Inner = i )), outerKeySelector: (c) => c.CustomerID, innerKeySelector: (o) => ClientProjection( t: o.Outer, _: o.Inner).CustomerID, resultSelector: (c, o) => new TransparentIdentifier>( Outer = c, Inner = o ))"), RemoveNewLines( (await Assert.ThrowsAsync( () => AssertQuery( @@ -81,7 +81,7 @@ from od in grouping2 public virtual async Task Join_with_nav_in_predicate_in_subquery_when_client_eval(bool isAsync) { Assert.Equal( - CoreStrings.TranslationFailed("(o) => ClientPredicate( t: o.Outer, _: o.Inner)"), + CoreStrings.TranslationFailed("Where>( source: LeftJoin>( outer: DbSet, inner: DbSet, outerKeySelector: (o) => Property(o, \"CustomerID\"), innerKeySelector: (c0) => Property(c0, \"CustomerID\"), resultSelector: (o, i) => new TransparentIdentifier( Outer = o, Inner = i )), predicate: (o) => ClientPredicate( t: o.Outer, _: o.Inner))"), RemoveNewLines( (await Assert.ThrowsAsync( () => AssertQuery( @@ -113,7 +113,7 @@ from od in grouping2 public virtual async Task Join_with_nav_in_orderby_in_subquery_when_client_eval(bool isAsync) { Assert.Equal( - CoreStrings.TranslationFailed("(o) => ClientOrderBy( t: o.Outer, _: o.Inner)"), + CoreStrings.TranslationFailed("OrderBy, int>( source: LeftJoin>( outer: DbSet, inner: DbSet, outerKeySelector: (o) => Property(o, \"CustomerID\"), innerKeySelector: (c0) => Property(c0, \"CustomerID\"), resultSelector: (o, i) => new TransparentIdentifier( Outer = o, Inner = i )), keySelector: (o) => ClientOrderBy( t: o.Outer, _: o.Inner))"), RemoveNewLines((await Assert.ThrowsAsync( () => AssertQuery( isAsync, @@ -226,7 +226,7 @@ public virtual async Task Select_Where_Navigation_Client(bool isAsync) { Assert.Equal( CoreStrings.TranslationFailed( - "(o) => o.Inner.IsLondon"), + "Where>( source: LeftJoin>( outer: DbSet, inner: DbSet, outerKeySelector: (o) => Property(o, \"CustomerID\"), innerKeySelector: (c) => Property(c, \"CustomerID\"), resultSelector: (o, i) => new TransparentIdentifier( Outer = o, Inner = i )), predicate: (o) => o.Inner.IsLondon)"), RemoveNewLines( (await Assert.ThrowsAsync( () => AssertQuery( @@ -741,8 +741,8 @@ public virtual Task Collection_select_nav_prop_all(bool isAsync) public virtual async Task Collection_select_nav_prop_all_client(bool isAsync) { Assert.Equal( - CoreStrings.TranslationFailed("(o0) => o0.ShipCity == \"London\""), - (await Assert.ThrowsAsync( + CoreStrings.TranslationFailed("All( source: Where( source: DbSet, predicate: (o0) => Property(EntityShaperExpression: EntityType: Customer ValueBufferExpression: ProjectionBindingExpression: EmptyProjectionMember IsNullable: False , \"CustomerID\") == Property(o0, \"CustomerID\")), predicate: (o0) => o0.ShipCity == \"London\")"), + RemoveNewLines((await Assert.ThrowsAsync( () => AssertQuery( isAsync, cs => from c in cs @@ -757,7 +757,7 @@ orderby c.CustomerID { All = (c.Orders ?? new List()).All(o => false) }, - assertOrder: true))).Message); + assertOrder: true))).Message)); } [ConditionalTheory] @@ -782,7 +782,7 @@ public virtual void Collection_where_nav_prop_all_client() { Assert.Equal( CoreStrings.TranslationFailed( - "(o) => o.ShipCity == \"London\""), + "All( source: Where( source: DbSet, predicate: (o) => Property(EntityShaperExpression: EntityType: Customer ValueBufferExpression: ProjectionBindingExpression: EmptyProjectionMember IsNullable: False , \"CustomerID\") == Property(o, \"CustomerID\")), predicate: (o) => o.ShipCity == \"London\")"), RemoveNewLines( Assert.Throws( () => (from c in context.Set() @@ -1149,7 +1149,7 @@ public virtual async Task Where_subquery_on_navigation_client_eval(bool isAsync) { Assert.Equal( CoreStrings.TranslationFailed( - "(o) => ClientMethod(o.OrderID)"), + "OrderByDescending( source: DbSet, keySelector: (o) => ClientMethod(o.OrderID))"), RemoveNewLines( (await Assert.ThrowsAsync( () => AssertQuery( diff --git a/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.ResultOperators.cs b/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.ResultOperators.cs index 340ba8c7c0b..edd13292fde 100644 --- a/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.ResultOperators.cs +++ b/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.ResultOperators.cs @@ -696,7 +696,7 @@ public virtual async Task Where_OrderBy_Count_client_eval(bool isAsync) { Assert.Equal( CoreStrings.TranslationFailed( - "(o) => ClientEvalPredicate(o)"), + "Where( source: DbSet, predicate: (o) => ClientEvalPredicate(o))"), RemoveNewLines( (await Assert.ThrowsAsync( () => AssertCount( @@ -719,7 +719,7 @@ public virtual async Task OrderBy_Where_Count_client_eval(bool isAsync) { Assert.Equal( CoreStrings.TranslationFailed( - "(o) => ClientEvalPredicate(o)"), + "Where( source: OrderBy( source: DbSet, keySelector: (o) => 42), predicate: (o) => ClientEvalPredicate(o))"), RemoveNewLines( (await Assert.ThrowsAsync( () => AssertCount( @@ -733,7 +733,7 @@ public virtual async Task OrderBy_Where_Count_client_eval_mixed(bool isAsync) { Assert.Equal( CoreStrings.TranslationFailed( - "(o) => ClientEvalPredicate(o)"), + "Where( source: OrderBy( source: DbSet, keySelector: (o) => o.OrderID), predicate: (o) => ClientEvalPredicate(o))"), RemoveNewLines( (await Assert.ThrowsAsync( () => AssertCount( @@ -747,7 +747,7 @@ public virtual async Task OrderBy_Count_with_predicate_client_eval(bool isAsync) { Assert.Equal( CoreStrings.TranslationFailed( - "(o) => ClientEvalPredicate(o)"), + "Count( source: OrderBy( source: DbSet, keySelector: (o) => 42), predicate: (o) => ClientEvalPredicate(o))"), RemoveNewLines( (await Assert.ThrowsAsync( () => AssertCount( @@ -762,7 +762,7 @@ public virtual async Task OrderBy_Count_with_predicate_client_eval_mixed(bool is { Assert.Equal( CoreStrings.TranslationFailed( - "(o) => ClientEvalPredicate(o)"), + "Count( source: OrderBy( source: DbSet, keySelector: (o) => o.OrderID), predicate: (o) => ClientEvalPredicate(o))"), RemoveNewLines( (await Assert.ThrowsAsync( () => AssertCount( @@ -777,7 +777,7 @@ public virtual async Task OrderBy_Where_Count_with_predicate_client_eval(bool is { Assert.Equal( CoreStrings.TranslationFailed( - "(o) => ClientEvalPredicate(o)"), + "Where( source: OrderBy( source: DbSet, keySelector: (o) => 42), predicate: (o) => ClientEvalPredicate(o))"), RemoveNewLines( (await Assert.ThrowsAsync( () => AssertCount( @@ -792,7 +792,7 @@ public virtual async Task OrderBy_Where_Count_with_predicate_client_eval_mixed(b { Assert.Equal( CoreStrings.TranslationFailed( - "(o) => ClientEvalPredicate(o)"), + "Where( source: OrderBy( source: DbSet, keySelector: (o) => o.OrderID), predicate: (o) => ClientEvalPredicate(o))"), RemoveNewLines( (await Assert.ThrowsAsync( () => AssertCount( @@ -1459,7 +1459,7 @@ public virtual async Task Contains_with_local_tuple_array_closure(bool isAsync) Assert.Equal( CoreStrings.TranslationFailed( - "(o) => Contains>( source: (Unhandled parameter: __ids_0), value: new Tuple( o.OrderID, o.ProductID ))"), + "Where( source: DbSet, predicate: (o) => Contains>( source: (Unhandled parameter: __ids_0), value: new Tuple( o.OrderID, o.ProductID )))"), RemoveNewLines( (await Assert.ThrowsAsync( () => AssertQuery( diff --git a/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.Select.cs b/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.Select.cs index 0ae165e37ea..19bd4c35d04 100644 --- a/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.Select.cs +++ b/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.Select.cs @@ -136,7 +136,7 @@ public virtual async Task Select_bool_closure_with_order_by_property_with_cast_t var boolean = false; Assert.Equal( - CoreStrings.TranslationFailed("(c) => (Nullable)(Unhandled parameter: __p_0).f"), + CoreStrings.TranslationFailed("OrderBy>( source: DbSet, keySelector: (c) => (Nullable)(Unhandled parameter: __p_0).f)"), RemoveNewLines( (await Assert.ThrowsAsync( () => AssertQuery( diff --git a/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.Where.cs b/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.Where.cs index 972c9602519..27f6aaae05f 100644 --- a/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.Where.cs +++ b/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.Where.cs @@ -638,7 +638,7 @@ where EF.Property(e, "Title") public virtual async Task Where_client(bool isAsync) { Assert.Equal( - CoreStrings.TranslationFailed("(c) => c.IsLondon"), + CoreStrings.TranslationFailed("Where( source: DbSet, predicate: (c) => c.IsLondon)"), RemoveNewLines( (await Assert.ThrowsAsync( () => AssertQuery( @@ -662,7 +662,7 @@ public virtual Task Where_subquery_correlated(bool isAsync) public virtual async Task Where_subquery_correlated_client_eval(bool isAsync) { Assert.Equal( - CoreStrings.TranslationFailed("(c0) => EntityShaperExpression: EntityType: Customer ValueBufferExpression: ProjectionBindingExpression: EmptyProjectionMember IsNullable: False.CustomerID == c0.CustomerID && c0.IsLondon"), + CoreStrings.TranslationFailed("Any( source: DbSet, predicate: (c0) => EntityShaperExpression: EntityType: Customer ValueBufferExpression: ProjectionBindingExpression: EmptyProjectionMember IsNullable: False .CustomerID == c0.CustomerID && c0.IsLondon)"), RemoveNewLines( (await Assert.ThrowsAsync( () => AssertQuery( @@ -677,7 +677,7 @@ public virtual async Task Where_subquery_correlated_client_eval(bool isAsync) public virtual async Task Where_client_and_server_top_level(bool isAsync) { Assert.Equal( - CoreStrings.TranslationFailed("(c) => c.IsLondon && c.CustomerID != \"AROUT\""), + CoreStrings.TranslationFailed("Where( source: DbSet, predicate: (c) => c.IsLondon && c.CustomerID != \"AROUT\")"), RemoveNewLines( (await Assert.ThrowsAsync( () => AssertQuery( @@ -691,7 +691,7 @@ public virtual async Task Where_client_and_server_top_level(bool isAsync) public virtual async Task Where_client_or_server_top_level(bool isAsync) { Assert.Equal( - CoreStrings.TranslationFailed("(c) => c.IsLondon || c.CustomerID == \"ALFKI\""), + CoreStrings.TranslationFailed("Where( source: DbSet, predicate: (c) => c.IsLondon || c.CustomerID == \"ALFKI\")"), RemoveNewLines( (await Assert.ThrowsAsync( () => AssertQuery( @@ -705,7 +705,7 @@ public virtual async Task Where_client_or_server_top_level(bool isAsync) public virtual async Task Where_client_and_server_non_top_level(bool isAsync) { Assert.Equal( - CoreStrings.TranslationFailed("(c) => c.CustomerID != \"ALFKI\" == c.IsLondon && c.CustomerID != \"AROUT\""), + CoreStrings.TranslationFailed("Where( source: DbSet, predicate: (c) => c.CustomerID != \"ALFKI\" == c.IsLondon && c.CustomerID != \"AROUT\")"), RemoveNewLines( (await Assert.ThrowsAsync( () => AssertQuery( @@ -719,7 +719,7 @@ public virtual async Task Where_client_and_server_non_top_level(bool isAsync) public virtual async Task Where_client_deep_inside_predicate_and_server_top_level(bool isAsync) { Assert.Equal( - CoreStrings.TranslationFailed("(c) => c.CustomerID != \"ALFKI\" && c.CustomerID == \"MAUMAR\" || c.CustomerID != \"AROUT\" && c.IsLondon"), + CoreStrings.TranslationFailed("Where( source: DbSet, predicate: (c) => c.CustomerID != \"ALFKI\" && c.CustomerID == \"MAUMAR\" || c.CustomerID != \"AROUT\" && c.IsLondon)"), RemoveNewLines( (await Assert.ThrowsAsync( () => AssertQuery( @@ -1331,7 +1331,7 @@ public virtual Task Where_bool_member_false(bool isAsync) public virtual async Task Where_bool_client_side_negated(bool isAsync) { Assert.Equal( - CoreStrings.TranslationFailed("(p) => !(ClientFunc(p.ProductID)) && p.Discontinued"), + CoreStrings.TranslationFailed("Where( source: DbSet, predicate: (p) => !(ClientFunc(p.ProductID)) && p.Discontinued)"), RemoveNewLines( (await Assert.ThrowsAsync( () => AssertQuery( diff --git a/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.cs index c40e717e2c0..0bab39a3534 100644 --- a/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.cs @@ -675,7 +675,7 @@ public virtual Task Queryable_simple_anonymous_subquery(bool isAsync) public virtual async Task Queryable_reprojection(bool isAsync) { Assert.Equal( - CoreStrings.TranslationFailed("(c) => c.IsLondon"), + CoreStrings.TranslationFailed("Where( source: DbSet, predicate: (c) => c.IsLondon)"), RemoveNewLines( (await Assert.ThrowsAsync( () => AssertQuery( @@ -1235,7 +1235,7 @@ public virtual Task All_top_level_subquery_ef_property(bool isAsync) public virtual async Task All_client(bool isAsync) { Assert.Equal( - CoreStrings.TranslationFailed("(c) => c.IsLondon"), + CoreStrings.TranslationFailed("All( source: DbSet, predicate: (c) => c.IsLondon)"), RemoveNewLines( (await Assert.ThrowsAsync( () => AssertAll( @@ -1249,7 +1249,7 @@ public virtual async Task All_client(bool isAsync) public virtual async Task All_client_and_server_top_level(bool isAsync) { Assert.Equal( - CoreStrings.TranslationFailed("(c) => c.CustomerID != \"Foo\" && c.IsLondon"), + CoreStrings.TranslationFailed("All( source: DbSet, predicate: (c) => c.CustomerID != \"Foo\" && c.IsLondon)"), RemoveNewLines( (await Assert.ThrowsAsync( () => AssertAll( @@ -1263,7 +1263,7 @@ public virtual async Task All_client_and_server_top_level(bool isAsync) public virtual async Task All_client_or_server_top_level(bool isAsync) { Assert.Equal( - CoreStrings.TranslationFailed("(c) => c.CustomerID != \"Foo\" || c.IsLondon"), + CoreStrings.TranslationFailed("All( source: DbSet, predicate: (c) => c.CustomerID != \"Foo\" || c.IsLondon)"), RemoveNewLines( (await Assert.ThrowsAsync( () => AssertAll( @@ -1393,7 +1393,7 @@ public virtual Task Cast_results_to_object(bool isAsync) public virtual async Task First_client_predicate(bool isAsync) { Assert.Equal( - CoreStrings.TranslationFailed("(c) => c.IsLondon"), + CoreStrings.TranslationFailed("Where( source: OrderBy( source: DbSet, keySelector: (c) => c.CustomerID), predicate: (c) => c.IsLondon)"), RemoveNewLines( (await Assert.ThrowsAsync( () => AssertFirst( @@ -2267,7 +2267,7 @@ where e1.FirstName public virtual async Task Where_query_composition3(bool isAsync) { Assert.Equal( - CoreStrings.TranslationFailed("(c0) => c0.IsLondon"), + CoreStrings.TranslationFailed("Where( source: OrderBy( source: DbSet, keySelector: (c0) => c0.CustomerID), predicate: (c0) => c0.IsLondon)"), RemoveNewLines( (await Assert.ThrowsAsync( () => AssertQuery( @@ -2284,7 +2284,7 @@ from c1 in cs public virtual async Task Where_query_composition4(bool isAsync) { Assert.Equal( - CoreStrings.TranslationFailed("(c1) => c1.IsLondon"), + CoreStrings.TranslationFailed("OrderBy( source: DbSet, keySelector: (c1) => c1.IsLondon)"), RemoveNewLines( (await Assert.ThrowsAsync( () => AssertQuery( @@ -2307,7 +2307,7 @@ public virtual async Task Where_query_composition5(bool isAsync) { Assert.Equal( CoreStrings.TranslationFailed( - "(c) => c.IsLondon == First(Select( source: OrderBy( source: DbSet, keySelector: (c0) => c0.CustomerID), selector: (c0) => c0.IsLondon))"), + "Where( source: DbSet, predicate: (c) => c.IsLondon == First(Select( source: OrderBy( source: DbSet, keySelector: (c0) => c0.CustomerID), selector: (c0) => c0.IsLondon)))"), RemoveNewLines( (await Assert.ThrowsAsync( () => AssertQuery( @@ -2324,7 +2324,7 @@ from c1 in cs public virtual async Task Where_query_composition6(bool isAsync) { Assert.Equal( - CoreStrings.TranslationFailed("(c) => c.IsLondon == First(Select( source: OrderBy( source: DbSet, keySelector: (c0) => c0.CustomerID), selector: (c0) => c0.IsLondon))"), + CoreStrings.TranslationFailed("Where( source: DbSet, predicate: (c) => c.IsLondon == First(Select( source: OrderBy( source: DbSet, keySelector: (c0) => c0.CustomerID), selector: (c0) => c0.IsLondon)))"), RemoveNewLines( (await Assert.ThrowsAsync( () => AssertQuery( @@ -3081,7 +3081,7 @@ public virtual Task OrderBy_anon2(bool isAsync) public virtual async Task OrderBy_client_mixed(bool isAsync) { Assert.Equal( - CoreStrings.TranslationFailed("(c) => c.IsLondon"), + CoreStrings.TranslationFailed("OrderBy( source: DbSet, keySelector: (c) => c.IsLondon)"), RemoveNewLines( (await Assert.ThrowsAsync( () => AssertQuery( @@ -3096,7 +3096,7 @@ public virtual async Task OrderBy_client_mixed(bool isAsync) public virtual async Task OrderBy_multiple_queries(bool isAsync) { Assert.Equal( - CoreStrings.TranslationFailed("(c) => new Foo{ Bar = c.CustomerID }; (o) => new Foo{ Bar = o.CustomerID }; (c, o) => new TransparentIdentifier( Outer = c, Inner = o)"), + CoreStrings.TranslationFailed("Join>( outer: DbSet, inner: DbSet, outerKeySelector: (c) => new Foo{ Bar = c.CustomerID } , innerKeySelector: (o) => new Foo{ Bar = o.CustomerID } , resultSelector: (c, o) => new TransparentIdentifier( Outer = c, Inner = o ))"), RemoveNewLines( (await Assert.ThrowsAsync( () => AssertQuery( @@ -4011,7 +4011,7 @@ public virtual void Random_next_is_not_funcletized_1() using (var context = CreateContext()) { Assert.Equal( - CoreStrings.TranslationFailed("(o) => o.OrderID > new Random().Next()"), + CoreStrings.TranslationFailed("Where( source: DbSet, predicate: (o) => o.OrderID > new Random().Next())"), RemoveNewLines( Assert.Throws( () => context.Orders @@ -4026,7 +4026,7 @@ public virtual void Random_next_is_not_funcletized_2() using (var context = CreateContext()) { Assert.Equal( - CoreStrings.TranslationFailed("(o) => o.OrderID > new Random().Next(5)"), + CoreStrings.TranslationFailed("Where( source: DbSet, predicate: (o) => o.OrderID > new Random().Next(5))"), RemoveNewLines( Assert.Throws( () => context.Orders @@ -4041,7 +4041,7 @@ public virtual void Random_next_is_not_funcletized_3() using (var context = CreateContext()) { Assert.Equal( - CoreStrings.TranslationFailed("(o) => o.OrderID > new Random().Next( minValue: 0, maxValue: 10)"), + CoreStrings.TranslationFailed("Where( source: DbSet, predicate: (o) => o.OrderID > new Random().Next( minValue: 0, maxValue: 10))"), RemoveNewLines( Assert.Throws( () => context.Orders @@ -4056,7 +4056,7 @@ public virtual void Random_next_is_not_funcletized_4() using (var context = CreateContext()) { Assert.Equal( - CoreStrings.TranslationFailed("(o) => o.OrderID > new Random(15).Next()"), + CoreStrings.TranslationFailed("Where( source: DbSet, predicate: (o) => o.OrderID > new Random(15).Next())"), RemoveNewLines( Assert.Throws( () => context.Orders @@ -4071,7 +4071,7 @@ public virtual void Random_next_is_not_funcletized_5() using (var context = CreateContext()) { Assert.Equal( - CoreStrings.TranslationFailed("(o) => o.OrderID > new Random(15).Next(5)"), + CoreStrings.TranslationFailed("Where( source: DbSet, predicate: (o) => o.OrderID > new Random(15).Next(5))"), RemoveNewLines( Assert.Throws( () => context.Orders @@ -4086,7 +4086,7 @@ public virtual void Random_next_is_not_funcletized_6() using (var context = CreateContext()) { Assert.Equal( - CoreStrings.TranslationFailed("(o) => o.OrderID > new Random(15).Next( minValue: 0, maxValue: 10)"), + CoreStrings.TranslationFailed("Where( source: DbSet, predicate: (o) => o.OrderID > new Random(15).Next( minValue: 0, maxValue: 10))"), RemoveNewLines( Assert.Throws( () => context.Orders @@ -6097,7 +6097,7 @@ public virtual Task Let_entity_equality_to_other_entity(bool isAsync) public virtual async Task SelectMany_after_client_method(bool isAsync) { Assert.Equal( - CoreStrings.TranslationFailed("(c) => ClientOrderBy(c)"), + CoreStrings.TranslationFailed("OrderBy( source: DbSet, keySelector: (c) => ClientOrderBy(c))"), RemoveNewLines( (await Assert.ThrowsAsync( () => AssertQueryScalar( diff --git a/test/EFCore.Sqlite.FunctionalTests/BuiltInDataTypesSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/BuiltInDataTypesSqliteTest.cs index 8490a172e1a..72b1dff0283 100644 --- a/test/EFCore.Sqlite.FunctionalTests/BuiltInDataTypesSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/BuiltInDataTypesSqliteTest.cs @@ -1129,7 +1129,7 @@ public virtual void Cant_query_Average_of_converted_types() .Average(e => e.TestNullableDecimal)); Assert.Equal( CoreStrings.TranslationFailed( - "Projection Mapping: EmptyProjectionMember -> [EntityProjectionExpression]SELECT 1FROM BuiltInNullableDataTypes AS bWHERE b.PartitionId == 202"), + "Average( source: Where( source: DbSet, predicate: (b) => b.PartitionId == 202), selector: (b) => b.TestNullableDecimal)"), RemoveNewLines(ex.Message)); } } @@ -1162,7 +1162,8 @@ public virtual void Cant_query_Sum_of_converted_types() .Where(e => e.PartitionId == 203) .Sum(e => e.TestDecimal)); Assert.Equal( - CoreStrings.TranslationFailed("Projection Mapping: EmptyProjectionMember -> [EntityProjectionExpression]SELECT 1FROM BuiltInDataTypes AS bWHERE b.PartitionId == 203"), + CoreStrings.TranslationFailed( + "Sum( source: Where( source: DbSet, predicate: (b) => b.PartitionId == 203), selector: (b) => b.TestDecimal)"), RemoveNewLines(ex.Message)); } } diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs index 515ca679368..c4b97302749 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs @@ -19,110 +19,110 @@ public GearsOfWarQuerySqliteTest(GearsOfWarQuerySqliteFixture fixture) public override async Task Where_datetimeoffset_date_component(bool isAsync) { Assert.Equal( - CoreStrings.TranslationFailed("(m) => m.Timeline.Date > (Unhandled parameter: __Date_0)"), - (await Assert.ThrowsAsync( + CoreStrings.TranslationFailed("Where( source: DbSet, predicate: (m) => m.Timeline.Date > (Unhandled parameter: __Date_0))"), + RemoveNewLines((await Assert.ThrowsAsync( () => base.Where_datetimeoffset_date_component(isAsync))) - .Message); + .Message)); } // SQLite client-eval public override async Task Where_datetimeoffset_day_component(bool isAsync) { Assert.Equal( - CoreStrings.TranslationFailed("(m) => m.Timeline.Day == 2"), - (await Assert.ThrowsAsync( + CoreStrings.TranslationFailed("Where( source: DbSet, predicate: (m) => m.Timeline.Day == 2)"), + RemoveNewLines((await Assert.ThrowsAsync( () => base.Where_datetimeoffset_day_component(isAsync))) - .Message); + .Message)); } // SQLite client-eval public override async Task Where_datetimeoffset_dayofyear_component(bool isAsync) { Assert.Equal( - CoreStrings.TranslationFailed("(m) => m.Timeline.DayOfYear == 2"), - (await Assert.ThrowsAsync( + CoreStrings.TranslationFailed("Where( source: DbSet, predicate: (m) => m.Timeline.DayOfYear == 2)"), + RemoveNewLines((await Assert.ThrowsAsync( () => base.Where_datetimeoffset_dayofyear_component(isAsync))) - .Message); + .Message)); } // SQLite client-eval public override async Task Where_datetimeoffset_hour_component(bool isAsync) { Assert.Equal( - CoreStrings.TranslationFailed("(m) => m.Timeline.Hour == 10"), - (await Assert.ThrowsAsync( + CoreStrings.TranslationFailed("Where( source: DbSet, predicate: (m) => m.Timeline.Hour == 10)"), + RemoveNewLines((await Assert.ThrowsAsync( () => base.Where_datetimeoffset_hour_component(isAsync))) - .Message); + .Message)); } // SQLite client-eval public override async Task Where_datetimeoffset_millisecond_component(bool isAsync) { Assert.Equal( - CoreStrings.TranslationFailed("(m) => m.Timeline.Millisecond == 0"), - (await Assert.ThrowsAsync( + CoreStrings.TranslationFailed("Where( source: DbSet, predicate: (m) => m.Timeline.Millisecond == 0)"), + RemoveNewLines((await Assert.ThrowsAsync( () => base.Where_datetimeoffset_millisecond_component(isAsync))) - .Message); + .Message)); } // SQLite client-eval public override async Task Where_datetimeoffset_minute_component(bool isAsync) { Assert.Equal( - CoreStrings.TranslationFailed("(m) => m.Timeline.Minute == 0"), - (await Assert.ThrowsAsync( + CoreStrings.TranslationFailed("Where( source: DbSet, predicate: (m) => m.Timeline.Minute == 0)"), + RemoveNewLines((await Assert.ThrowsAsync( () => base.Where_datetimeoffset_minute_component(isAsync))) - .Message); + .Message)); } // SQLite client-eval public override async Task Where_datetimeoffset_month_component(bool isAsync) { Assert.Equal( - CoreStrings.TranslationFailed("(m) => m.Timeline.Month == 1"), - (await Assert.ThrowsAsync( + CoreStrings.TranslationFailed("Where( source: DbSet, predicate: (m) => m.Timeline.Month == 1)"), + RemoveNewLines((await Assert.ThrowsAsync( () => base.Where_datetimeoffset_month_component(isAsync))) - .Message); + .Message)); } // SQLite client-eval public override async Task Where_datetimeoffset_now(bool isAsync) { Assert.Equal( - CoreStrings.TranslationFailed("(m) => m.Timeline != DateTimeOffset.Now"), - (await Assert.ThrowsAsync( + CoreStrings.TranslationFailed("Where( source: DbSet, predicate: (m) => m.Timeline != DateTimeOffset.Now)"), + RemoveNewLines((await Assert.ThrowsAsync( () => base.Where_datetimeoffset_now(isAsync))) - .Message); + .Message)); } // SQLite client-eval public override async Task Where_datetimeoffset_second_component(bool isAsync) { Assert.Equal( - CoreStrings.TranslationFailed("(m) => m.Timeline.Second == 0"), - (await Assert.ThrowsAsync( + CoreStrings.TranslationFailed("Where( source: DbSet, predicate: (m) => m.Timeline.Second == 0)"), + RemoveNewLines((await Assert.ThrowsAsync( () => base.Where_datetimeoffset_second_component(isAsync))) - .Message); + .Message)); } // SQLite client-eval public override async Task Where_datetimeoffset_utcnow(bool isAsync) { Assert.Equal( - CoreStrings.TranslationFailed("(m) => m.Timeline != DateTimeOffset.UtcNow"), - (await Assert.ThrowsAsync( + CoreStrings.TranslationFailed("Where( source: DbSet, predicate: (m) => m.Timeline != DateTimeOffset.UtcNow)"), + RemoveNewLines((await Assert.ThrowsAsync( () => base.Where_datetimeoffset_utcnow(isAsync))) - .Message); + .Message)); } // SQLite client-eval public override async Task Where_datetimeoffset_year_component(bool isAsync) { Assert.Equal( - CoreStrings.TranslationFailed("(m) => m.Timeline.Year == 2"), - (await Assert.ThrowsAsync( + CoreStrings.TranslationFailed("Where( source: DbSet, predicate: (m) => m.Timeline.Year == 2)"), + RemoveNewLines((await Assert.ThrowsAsync( () => base.Where_datetimeoffset_year_component(isAsync))) - .Message); + .Message)); } // SQLite client-eval @@ -130,7 +130,7 @@ public override async Task DateTimeOffset_Contains_Less_than_Greater_than(bool i { Assert.Equal( CoreStrings.TranslationFailed( - "(m) => (Unhandled parameter: __start_0) <= (DateTimeOffset)m.Timeline.Date && m.Timeline < (Unhandled parameter: __end_1) && Contains( source: (Unhandled parameter: __dates_2), value: m.Timeline)"), + "Where( source: DbSet, predicate: (m) => (Unhandled parameter: __start_0) <= (DateTimeOffset)m.Timeline.Date && m.Timeline < (Unhandled parameter: __end_1) && Contains( source: (Unhandled parameter: __dates_2), value: m.Timeline))"), RemoveNewLines( (await Assert.ThrowsAsync( () => base.DateTimeOffset_Contains_Less_than_Greater_than(isAsync))) From 8cd648e414e86e7fd2955bf35b3cc7a16a12315f Mon Sep 17 00:00:00 2001 From: Brice Lambson Date: Wed, 21 Aug 2019 13:34:42 -0700 Subject: [PATCH 04/21] Enable CI on feature branches --- azure-pipelines.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 40a8d475975..b48194e7747 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -17,6 +17,7 @@ variables: trigger: - master - release/* + - feature/* pr: ['*'] From 07bb9b8600e087cf245369ff9187e2fbeb49af1c Mon Sep 17 00:00:00 2001 From: Smit Patel Date: Wed, 21 Aug 2019 15:53:13 -0700 Subject: [PATCH 05/21] InMemory: Avoid N+1 queries for Include Don't send shapers to ServerQueryExpression Laying ground work for collection projection Part of #16963 --- ...yExpressionTranslatingExpressionVisitor.cs | 2 +- ...emoryProjectionBindingExpressionVisitor.cs | 14 +- ...nMemoryQueryExpression.ResultEnumerable.cs | 64 ++++++ .../Query/Internal/InMemoryQueryExpression.cs | 185 +++++++++--------- ...yableMethodTranslatingExpressionVisitor.cs | 2 +- ....CustomShaperCompilingExpressionVisitor.cs | 28 +-- ...jectionBindingRemovingExpressionVisitor.cs | 32 ++- ...ryShapedQueryCompilingExpressionVisitor.cs | 7 +- ...erExpressionProcessingExpressionVisitor.cs | 51 +++-- ....CustomShaperCompilingExpressionVisitor.cs | 3 +- 10 files changed, 235 insertions(+), 153 deletions(-) create mode 100644 src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.ResultEnumerable.cs diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs b/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs index f02f4a449c3..1e6a5ed6350 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs @@ -145,7 +145,7 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp return null; } - subquery.ApplyServerProjection(); + subquery.ApplyProjection(); if (subquery.Projection.Count != 1) { return null; diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryProjectionBindingExpressionVisitor.cs b/src/EFCore.InMemory/Query/Internal/InMemoryProjectionBindingExpressionVisitor.cs index d3978ca5590..e7e1c543e5e 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryProjectionBindingExpressionVisitor.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryProjectionBindingExpressionVisitor.cs @@ -89,10 +89,16 @@ public override Expression Visit(Expression expression) var translated = _queryableMethodTranslatingExpressionVisitor.TranslateSubquery( materializeCollectionNavigationExpression.Subquery); - return new ProjectionBindingExpression( - _queryExpression, - _queryExpression.AddToProjection(translated), - typeof(IEnumerable<>).MakeGenericType(materializeCollectionNavigationExpression.Navigation.GetTargetType().ClrType)); + var index = _queryExpression.AddSubqueryProjection(translated, out var innerShaper); + + return new CollectionShaperExpression( + new ProjectionBindingExpression( + _queryExpression, + index, + typeof(IEnumerable)), + innerShaper, + materializeCollectionNavigationExpression.Navigation, + materializeCollectionNavigationExpression.Navigation.GetTargetType().ClrType); case MethodCallExpression methodCallExpression: { diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.ResultEnumerable.cs b/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.ResultEnumerable.cs new file mode 100644 index 00000000000..a41d40644bf --- /dev/null +++ b/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.ResultEnumerable.cs @@ -0,0 +1,64 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Storage; + +namespace Microsoft.EntityFrameworkCore.InMemory.Query.Internal +{ + public partial class InMemoryQueryExpression + { + private sealed class ResultEnumerable : IEnumerable + { + private readonly Func _getElement; + + public ResultEnumerable(Func getElement) + { + _getElement = getElement; + } + + public IEnumerator GetEnumerator() => new ResultEnumerator(_getElement()); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + private sealed class ResultEnumerator : IEnumerator + { + private readonly ValueBuffer _value; + private bool _moved; + + public ResultEnumerator(ValueBuffer value) + { + _value = value; + _moved = _value.IsEmpty; + } + + public bool MoveNext() + { + if (!_moved) + { + _moved = true; + + return _moved; + } + + return false; + } + + public void Reset() + { + _moved = false; + } + + object IEnumerator.Current => Current; + + public ValueBuffer Current => !_moved ? ValueBuffer.Empty : _value; + + void IDisposable.Dispose() + { + } + } + } + } +} diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs b/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs index 7f95e137800..a9942db5a81 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; @@ -14,70 +13,24 @@ namespace Microsoft.EntityFrameworkCore.InMemory.Query.Internal { - public class InMemoryQueryExpression : Expression + public partial class InMemoryQueryExpression : Expression { private static readonly ConstructorInfo _valueBufferConstructor = typeof(ValueBuffer).GetConstructors().Single(ci => ci.GetParameters().Length == 1); + private static readonly PropertyInfo _valueBufferCountMemberInfo + = typeof(ValueBuffer).GetTypeInfo().GetProperty(nameof(ValueBuffer.Count)); private readonly List _valueBufferSlots = new List(); - private IDictionary _projectionMapping = new Dictionary(); - - public virtual IReadOnlyList Projection => _valueBufferSlots; private readonly IDictionary> _entityProjectionCache = new Dictionary>(); - private sealed class ResultEnumerable : IEnumerable - { - private readonly Func _getElement; - - public ResultEnumerable(Func getElement) - { - _getElement = getElement; - } - - public IEnumerator GetEnumerator() => new ResultEnumerator(_getElement()); - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - private sealed class ResultEnumerator : IEnumerator - { - private readonly ValueBuffer _value; - private bool _moved; - - public ResultEnumerator(ValueBuffer value) - { - _value = value; - _moved = _value.IsEmpty; - } - - public bool MoveNext() - { - if (!_moved) - { - _moved = true; - - return _moved; - } - - return false; - } - - public void Reset() - { - _moved = false; - } - - object IEnumerator.Current => Current; - - public ValueBuffer Current => !_moved ? ValueBuffer.Empty : _value; - - void IDisposable.Dispose() - { - } - } - } + private IDictionary _projectionMapping = new Dictionary(); - private static readonly PropertyInfo _valueBufferCountMemberInfo = typeof(ValueBuffer).GetTypeInfo().GetProperty(nameof(ValueBuffer.Count)); + public virtual IReadOnlyList Projection => _valueBufferSlots; + public virtual Expression ServerQueryExpression { get; set; } + public virtual ParameterExpression ValueBufferParameter { get; } + public override Type Type => typeof(IEnumerable); + public sealed override ExpressionType NodeType => ExpressionType.Extension; public InMemoryQueryExpression(IEntityType entityType) { @@ -174,37 +127,87 @@ public virtual int AddToProjection(Expression expression) return _valueBufferSlots.Count - 1; } + public virtual int AddSubqueryProjection(ShapedQueryExpression shapedQueryExpression, out Expression innerShaper) + { + var subquery = (InMemoryQueryExpression)shapedQueryExpression.QueryExpression; + subquery.ApplyProjection(); + + innerShaper = new ShaperRemappingExpressionVisitor(subquery._projectionMapping) + .Visit(shapedQueryExpression.ShaperExpression); + + innerShaper = Lambda(innerShaper, subquery.ValueBufferParameter); + + return AddToProjection(subquery.ServerQueryExpression); + } + + private class ShaperRemappingExpressionVisitor : ExpressionVisitor + { + private readonly IDictionary _projectionMapping; + + public ShaperRemappingExpressionVisitor(IDictionary projectionMapping) + { + _projectionMapping = projectionMapping; + } + + public override Expression Visit(Expression expression) + { + if (expression is ProjectionBindingExpression projectionBindingExpression + && projectionBindingExpression.ProjectionMember != null) + { + var mappingValue = ((ConstantExpression)_projectionMapping[projectionBindingExpression.ProjectionMember]).Value; + if (mappingValue is IDictionary indexMap) + { + return new ProjectionBindingExpression(projectionBindingExpression.QueryExpression, indexMap); + } + else if (mappingValue is int index) + { + return new ProjectionBindingExpression( + projectionBindingExpression.QueryExpression, index, projectionBindingExpression.Type); + } + else + { + throw new InvalidOperationException("Invalid ProjectionMapping."); + } + } + + return base.Visit(expression); + } + } + private IEnumerable GetAllPropertiesInHierarchy(IEntityType entityType) => entityType.GetTypesInHierarchy().SelectMany(EntityTypeExtensions.GetDeclaredProperties); public virtual Expression GetMappedProjection(ProjectionMember member) => _projectionMapping[member]; - public virtual void ApplyPendingSelector() + public virtual void PushdownIntoSubquery() { var clientProjection = _valueBufferSlots.Count != 0; - var result = new Dictionary(); - foreach (var keyValuePair in _projectionMapping) + if (!clientProjection) { - if (keyValuePair.Value is EntityProjectionExpression entityProjection) + var result = new Dictionary(); + foreach (var keyValuePair in _projectionMapping) { - var map = new Dictionary(); - foreach (var property in GetAllPropertiesInHierarchy(entityProjection.EntityType)) + if (keyValuePair.Value is EntityProjectionExpression entityProjection) { - var index = AddToProjection(entityProjection.BindProperty(property)); - map[property] = CreateReadValueExpression(property.ClrType, index, property); + var map = new Dictionary(); + foreach (var property in GetAllPropertiesInHierarchy(entityProjection.EntityType)) + { + var index = AddToProjection(entityProjection.BindProperty(property)); + map[property] = CreateReadValueExpression(property.ClrType, index, property); + } + result[keyValuePair.Key] = new EntityProjectionExpression(entityProjection.EntityType, map); + } + else + { + var index = AddToProjection(keyValuePair.Value); + result[keyValuePair.Key] = CreateReadValueExpression( + keyValuePair.Value.Type, index, InferPropertyFromInner(keyValuePair.Value)); } - result[keyValuePair.Key] = new EntityProjectionExpression(entityProjection.EntityType, map); - } - else - { - var index = AddToProjection(keyValuePair.Value); - result[keyValuePair.Key] = CreateReadValueExpression( - keyValuePair.Value.Type, index, InferPropertyFromInner(keyValuePair.Value)); } - } - _projectionMapping = result; + _projectionMapping = result; + } var selectorLambda = Lambda( New( @@ -248,27 +251,30 @@ private IPropertyBase InferPropertyFromInner(Expression expression) return null; } - public virtual void ApplyServerProjection() + public virtual void ApplyProjection() { - var result = new Dictionary(); - foreach (var keyValuePair in _projectionMapping) + if (_valueBufferSlots.Count == 0) { - if (keyValuePair.Value is EntityProjectionExpression entityProjection) + var result = new Dictionary(); + foreach (var keyValuePair in _projectionMapping) { - var map = new Dictionary(); - foreach (var property in GetAllPropertiesInHierarchy(entityProjection.EntityType)) + if (keyValuePair.Value is EntityProjectionExpression entityProjection) { - map[property] = AddToProjection(entityProjection.BindProperty(property)); + var map = new Dictionary(); + foreach (var property in GetAllPropertiesInHierarchy(entityProjection.EntityType)) + { + map[property] = AddToProjection(entityProjection.BindProperty(property)); + } + result[keyValuePair.Key] = Constant(map); + } + else + { + result[keyValuePair.Key] = Constant(AddToProjection(keyValuePair.Value)); } - result[keyValuePair.Key] = Constant(map); - } - else - { - result[keyValuePair.Key] = Constant(AddToProjection(keyValuePair.Value)); } - } - _projectionMapping = result; + _projectionMapping = result; + } var selectorLambda = Lambda( New( @@ -286,11 +292,6 @@ public virtual void ApplyServerProjection() selectorLambda); } - public virtual Expression ServerQueryExpression { get; set; } - public virtual ParameterExpression ValueBufferParameter { get; } - public override Type Type => typeof(IEnumerable); - public sealed override ExpressionType NodeType => ExpressionType.Extension; - private Expression CreateReadValueExpression( Type type, int index, diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs index 46392f752cb..d20142dec52 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs @@ -189,7 +189,7 @@ protected override ShapedQueryExpression TranslateDistinct(ShapedQueryExpression { var inMemoryQueryExpression = (InMemoryQueryExpression)source.QueryExpression; - inMemoryQueryExpression.ApplyPendingSelector(); + inMemoryQueryExpression.PushdownIntoSubquery(); inMemoryQueryExpression.ServerQueryExpression = Expression.Call( InMemoryLinqOperatorProvider.Distinct.MakeGenericMethod(typeof(ValueBuffer)), diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryShapedQueryCompilingExpressionVisitor.CustomShaperCompilingExpressionVisitor.cs b/src/EFCore.InMemory/Query/Internal/InMemoryShapedQueryCompilingExpressionVisitor.CustomShaperCompilingExpressionVisitor.cs index b62b854557c..0d287ebeae0 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryShapedQueryCompilingExpressionVisitor.CustomShaperCompilingExpressionVisitor.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryShapedQueryCompilingExpressionVisitor.CustomShaperCompilingExpressionVisitor.cs @@ -9,6 +9,7 @@ using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Query; +using Microsoft.EntityFrameworkCore.Storage; namespace Microsoft.EntityFrameworkCore.InMemory.Query.Internal { @@ -72,8 +73,9 @@ private static void IncludeReference private static void IncludeCollection( QueryContext queryContext, + IEnumerable innerValueBuffers, + Func innerShaper, TEntity entity, - IEnumerable relatedEntities, INavigation navigation, INavigation inverseNavigation, Action fixup, @@ -89,16 +91,18 @@ private static void IncludeCollection> _materializationContextBindings - = new Dictionary>(); - - public InMemoryProjectionBindingRemovingExpressionVisitor(InMemoryQueryExpression queryExpression) - { - _queryExpression = queryExpression; - } + private readonly IDictionary IndexMap, ParameterExpression valueBuffer)> + _materializationContextBindings + = new Dictionary IndexMap, ParameterExpression valueBuffer)>(); protected override Expression VisitBinary(BinaryExpression binaryExpression) { @@ -32,9 +26,11 @@ protected override Expression VisitBinary(BinaryExpression binaryExpression) var newExpression = (NewExpression)binaryExpression.Right; var projectionBindingExpression = (ProjectionBindingExpression)newExpression.Arguments[0]; + var queryExpression = (InMemoryQueryExpression)projectionBindingExpression.QueryExpression; _materializationContextBindings[parameterExpression] - = (IDictionary)GetProjectionIndex(projectionBindingExpression); + = ((IDictionary)GetProjectionIndex(queryExpression, projectionBindingExpression), + ((InMemoryQueryExpression)projectionBindingExpression.QueryExpression).ValueBufferParameter); var updatedExpression = Expression.New( newExpression.Constructor, @@ -53,13 +49,13 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp && methodCallExpression.Method.GetGenericMethodDefinition() == EntityMaterializerSource.TryReadValueMethod) { var property = (IProperty)((ConstantExpression)methodCallExpression.Arguments[2]).Value; - var indexMap = + var (indexMap, valueBuffer) = _materializationContextBindings[ (ParameterExpression)((MethodCallExpression)methodCallExpression.Arguments[0]).Object]; return Expression.Call( methodCallExpression.Method, - _queryExpression.ValueBufferParameter, + valueBuffer, Expression.Constant(indexMap[property]), methodCallExpression.Arguments[2]); } @@ -71,13 +67,15 @@ protected override Expression VisitExtension(Expression extensionExpression) { if (extensionExpression is ProjectionBindingExpression projectionBindingExpression) { - var projectionIndex = (int)GetProjectionIndex(projectionBindingExpression); + var queryExpression = (InMemoryQueryExpression)projectionBindingExpression.QueryExpression; + var projectionIndex = (int)GetProjectionIndex(queryExpression, projectionBindingExpression); + var valueBuffer = queryExpression.ValueBufferParameter; return Expression.Call( EntityMaterializerSource.TryReadValueMethod.MakeGenericMethod(projectionBindingExpression.Type), - _queryExpression.ValueBufferParameter, + valueBuffer, Expression.Constant(projectionIndex), - Expression.Constant(InferPropertyFromInner(_queryExpression.Projection[projectionIndex]), typeof(IPropertyBase))); + Expression.Constant(InferPropertyFromInner(queryExpression.Projection[projectionIndex]), typeof(IPropertyBase))); } return base.VisitExtension(extensionExpression); @@ -95,10 +93,10 @@ private IPropertyBase InferPropertyFromInner(Expression expression) return null; } - private object GetProjectionIndex(ProjectionBindingExpression projectionBindingExpression) + private object GetProjectionIndex(InMemoryQueryExpression queryExpression, ProjectionBindingExpression projectionBindingExpression) { return projectionBindingExpression.ProjectionMember != null - ? ((ConstantExpression)_queryExpression.GetMappedProjection(projectionBindingExpression.ProjectionMember)).Value + ? ((ConstantExpression)queryExpression.GetMappedProjection(projectionBindingExpression.ProjectionMember)).Value : (projectionBindingExpression.Index != null ? (object)projectionBindingExpression.Index : projectionBindingExpression.IndexMap); diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryShapedQueryCompilingExpressionVisitor.cs b/src/EFCore.InMemory/Query/Internal/InMemoryShapedQueryCompilingExpressionVisitor.cs index 8c0a6c587c9..1cdaa15668c 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryShapedQueryCompilingExpressionVisitor.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryShapedQueryCompilingExpressionVisitor.cs @@ -32,7 +32,7 @@ protected override Expression VisitExtension(Expression extensionExpression) switch (extensionExpression) { case InMemoryQueryExpression inMemoryQueryExpression: - inMemoryQueryExpression.ApplyServerProjection(); + inMemoryQueryExpression.ApplyProjection(); return Visit(inMemoryQueryExpression.ServerQueryExpression); case InMemoryTableExpression inMemoryTableExpression: @@ -49,14 +49,15 @@ protected override Expression VisitShapedQueryExpression(ShapedQueryExpression s { var inMemoryQueryExpression = (InMemoryQueryExpression)shapedQueryExpression.QueryExpression; - var shaper = new ShaperExpressionProcessingExpressionVisitor(inMemoryQueryExpression) + var shaper = new ShaperExpressionProcessingExpressionVisitor( + inMemoryQueryExpression, inMemoryQueryExpression.ValueBufferParameter) .Inject(shapedQueryExpression.ShaperExpression); shaper = InjectEntityMaterializers(shaper); var innerEnumerable = Visit(inMemoryQueryExpression); - shaper = new InMemoryProjectionBindingRemovingExpressionVisitor(inMemoryQueryExpression).Visit(shaper); + shaper = new InMemoryProjectionBindingRemovingExpressionVisitor().Visit(shaper); shaper = new CustomShaperCompilingExpressionVisitor(IsTracking).Visit(shaper); diff --git a/src/EFCore.InMemory/Query/Internal/ShaperExpressionProcessingExpressionVisitor.cs b/src/EFCore.InMemory/Query/Internal/ShaperExpressionProcessingExpressionVisitor.cs index 000bf5d72dc..619ecb76edf 100644 --- a/src/EFCore.InMemory/Query/Internal/ShaperExpressionProcessingExpressionVisitor.cs +++ b/src/EFCore.InMemory/Query/Internal/ShaperExpressionProcessingExpressionVisitor.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; +using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Query; namespace Microsoft.EntityFrameworkCore.InMemory.Query.Internal @@ -11,40 +12,33 @@ namespace Microsoft.EntityFrameworkCore.InMemory.Query.Internal public class ShaperExpressionProcessingExpressionVisitor : ExpressionVisitor { private readonly InMemoryQueryExpression _queryExpression; + private readonly ParameterExpression _valueBufferParameter; private readonly IDictionary _mapping = new Dictionary(); private readonly List _variables = new List(); private readonly List _expressions = new List(); public ShaperExpressionProcessingExpressionVisitor( - InMemoryQueryExpression queryExpression) + [CanBeNull] InMemoryQueryExpression queryExpression, [NotNull] ParameterExpression valueBufferParameter) { _queryExpression = queryExpression; + _valueBufferParameter = valueBufferParameter; } public virtual Expression Inject(Expression expression) { var result = Visit(expression); + _expressions.Add(result); + result = Expression.Block(_variables, _expressions); - if (_expressions.All(e => e.NodeType == ExpressionType.Assign)) - { - result = new ReplacingExpressionVisitor(_expressions.Cast() - .ToDictionary(e => e.Left, e => e.Right)).Visit(result); - } - else - { - _expressions.Add(result); - result = Expression.Block(_variables, _expressions); - } - - return ConvertToLambda(result, Expression.Parameter(result.Type, "result")); + return ConvertToLambda(result); } - private LambdaExpression ConvertToLambda(Expression result, ParameterExpression resultParameter) + private LambdaExpression ConvertToLambda(Expression result) => Expression.Lambda( result, QueryCompilationContext.QueryContextParameter, - _queryExpression.ValueBufferParameter); + _valueBufferParameter); protected override Expression VisitExtension(Expression extensionExpression) { @@ -81,10 +75,26 @@ protected override Expression VisitExtension(Expression extensionExpression) case IncludeExpression includeExpression: { var entity = Visit(includeExpression.EntityExpression); - _expressions.Add( - includeExpression.Update( - entity, - Visit(includeExpression.NavigationExpression))); + if (includeExpression.NavigationExpression is CollectionShaperExpression collectionShaperExpression) + { + var innerLambda = (LambdaExpression)collectionShaperExpression.InnerShaper; + var innerShaper = new ShaperExpressionProcessingExpressionVisitor(null, innerLambda.Parameters[0]) + .Inject(innerLambda.Body); + + _expressions.Add( + includeExpression.Update( + entity, + collectionShaperExpression.Update( + Visit(collectionShaperExpression.Projection), + innerShaper))); + } + else + { + _expressions.Add( + includeExpression.Update( + entity, + Visit(includeExpression.NavigationExpression))); + } return entity; } @@ -94,7 +104,8 @@ protected override Expression VisitExtension(Expression extensionExpression) } private Expression GenerateKey(ProjectionBindingExpression projectionBindingExpression) - => projectionBindingExpression.ProjectionMember != null + => _queryExpression != null + && projectionBindingExpression.ProjectionMember != null ? _queryExpression.GetMappedProjection(projectionBindingExpression.ProjectionMember) : projectionBindingExpression; } diff --git a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.CustomShaperCompilingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.CustomShaperCompilingExpressionVisitor.cs index e1fc08df398..3267afe9093 100644 --- a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.CustomShaperCompilingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.CustomShaperCompilingExpressionVisitor.cs @@ -216,8 +216,7 @@ void processCurrentElementRow() if (!trackingQuery) { fixup(entity, relatedEntity); - if (inverseNavigation != null - && !inverseNavigation.IsCollection()) + if (inverseNavigation != null) { SetIsLoadedNoTracking(relatedEntity, inverseNavigation); } From 66a3d1fa27e069f4345ffdf4b1010b9f05920997 Mon Sep 17 00:00:00 2001 From: Smit Patel Date: Thu, 22 Aug 2019 09:39:21 -0700 Subject: [PATCH 06/21] InMemory: Add support for collection projection Part of #16963 --- ...emoryProjectionBindingExpressionVisitor.cs | 53 +++++++++-------- ...yableMethodTranslatingExpressionVisitor.cs | 10 ++-- ....CustomShaperCompilingExpressionVisitor.cs | 57 +++++++++++++++---- ...erExpressionProcessingExpressionVisitor.cs | 29 ++++++++-- .../Query/CollectionShaperExpression.cs | 2 +- .../Query/AsyncGearsOfWarQueryInMemoryTest.cs | 2 +- .../Query/AsyncSimpleQueryInMemoryTest.cs | 48 ++-------------- .../Query/CompiledQueryInMemoryTest.cs | 28 +-------- .../Query/FiltersInMemoryTest.cs | 29 ---------- .../Query/IncludeInMemoryFixture.cs | 1 - 10 files changed, 115 insertions(+), 144 deletions(-) diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryProjectionBindingExpressionVisitor.cs b/src/EFCore.InMemory/Query/Internal/InMemoryProjectionBindingExpressionVisitor.cs index e7e1c543e5e..f4725f2a962 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryProjectionBindingExpressionVisitor.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryProjectionBindingExpressionVisitor.cs @@ -7,6 +7,7 @@ using System.Linq.Expressions; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Query; using Microsoft.EntityFrameworkCore.Storage; @@ -85,20 +86,11 @@ public override Expression Visit(Expression expression) return expression; case MaterializeCollectionNavigationExpression materializeCollectionNavigationExpression: - - var translated = _queryableMethodTranslatingExpressionVisitor.TranslateSubquery( - materializeCollectionNavigationExpression.Subquery); - - var index = _queryExpression.AddSubqueryProjection(translated, out var innerShaper); - - return new CollectionShaperExpression( - new ProjectionBindingExpression( - _queryExpression, - index, - typeof(IEnumerable)), - innerShaper, + return AddCollectionProjection( + _queryableMethodTranslatingExpressionVisitor.TranslateSubquery( + materializeCollectionNavigationExpression.Subquery), materializeCollectionNavigationExpression.Navigation, - materializeCollectionNavigationExpression.Navigation.GetTargetType().ClrType); + null); case MethodCallExpression methodCallExpression: { @@ -106,22 +98,21 @@ public override Expression Visit(Expression expression) && methodCallExpression.Method.DeclaringType == typeof(Enumerable) && methodCallExpression.Method.Name == nameof(Enumerable.ToList)) { - //var elementType = methodCallExpression.Method.GetGenericArguments()[0]; - - //var result = _queryableMethodTranslatingExpressionVisitor.TranslateSubquery(methodCallExpression.Arguments[0]); - - //return _selectExpression.AddCollectionProjection(result, null, elementType); - throw new InvalidOperationException(CoreStrings.QueryFailed(methodCallExpression.Print(), GetType().Name)); + return AddCollectionProjection( + _queryableMethodTranslatingExpressionVisitor.TranslateSubquery( + methodCallExpression.Arguments[0]), + null, + methodCallExpression.Method.GetGenericArguments()[0]); } var subquery = _queryableMethodTranslatingExpressionVisitor.TranslateSubquery(methodCallExpression); - if (subquery != null) { - //if (subquery.ResultType == ResultType.Enumerable) - //{ - // return _selectExpression.AddCollectionProjection(subquery, null, subquery.ShaperExpression.Type); - //} + if (subquery.ResultCardinality == ResultCardinality.Enumerable) + { + return AddCollectionProjection(subquery, null, subquery.ShaperExpression.Type); + } + throw new InvalidOperationException(CoreStrings.QueryFailed(methodCallExpression.Print(), GetType().Name)); } @@ -129,7 +120,6 @@ public override Expression Visit(Expression expression) } } - var translation = _expressionTranslatingExpressionVisitor.Translate(expression); return translation == null ? base.Visit(expression) @@ -153,6 +143,19 @@ public override Expression Visit(Expression expression) return base.Visit(expression); } + private CollectionShaperExpression AddCollectionProjection( + ShapedQueryExpression subquery, INavigation navigation, Type elementType) + => new CollectionShaperExpression( + new ProjectionBindingExpression( + _queryExpression, + _queryExpression.AddSubqueryProjection( + subquery, + out var innerShaper), + typeof(IEnumerable)), + innerShaper, + navigation, + elementType); + protected override Expression VisitExtension(Expression extensionExpression) { if (extensionExpression is EntityShaperExpression entityShaperExpression) diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs index d20142dec52..4b0feaf449d 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs @@ -635,11 +635,11 @@ private ShapedQueryExpression TranslateScalarAggregate( inMemoryQueryExpression.ServerQueryExpression = Expression.Call( - InMemoryLinqOperatorProvider - .GetAggregateMethod(methodName, selector.ReturnType, parameterCount: 1) - .MakeGenericMethod(typeof(ValueBuffer)), - inMemoryQueryExpression.ServerQueryExpression, - selector); + InMemoryLinqOperatorProvider + .GetAggregateMethod(methodName, selector.ReturnType, parameterCount: 1) + .MakeGenericMethod(typeof(ValueBuffer)), + inMemoryQueryExpression.ServerQueryExpression, + selector); source.ShaperExpression = inMemoryQueryExpression.GetSingleScalarProjection(); diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryShapedQueryCompilingExpressionVisitor.CustomShaperCompilingExpressionVisitor.cs b/src/EFCore.InMemory/Query/Internal/InMemoryShapedQueryCompilingExpressionVisitor.CustomShaperCompilingExpressionVisitor.cs index 0d287ebeae0..b9c802d8e44 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryShapedQueryCompilingExpressionVisitor.CustomShaperCompilingExpressionVisitor.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryShapedQueryCompilingExpressionVisitor.CustomShaperCompilingExpressionVisitor.cs @@ -32,6 +32,18 @@ private static readonly MethodInfo _includeCollectionMethodInfo = typeof(CustomShaperCompilingExpressionVisitor).GetTypeInfo() .GetDeclaredMethod(nameof(IncludeCollection)); + private static readonly MethodInfo _materializeCollectionMethodInfo + = typeof(CustomShaperCompilingExpressionVisitor).GetTypeInfo() + .GetDeclaredMethod(nameof(MaterializeCollection)); + + private static void SetIsLoadedNoTracking(object entity, INavigation navigation) + => ((ILazyLoader)(navigation + .DeclaringEntityType + .GetServiceProperties() + .FirstOrDefault(p => p.ClrType == typeof(ILazyLoader))) + ?.GetGetter().GetClrValue(entity)) + ?.SetLoaded(entity, navigation.Name); + private static void IncludeReference( QueryContext queryContext, TEntity entity, @@ -114,13 +126,23 @@ private static void IncludeCollection ((ILazyLoader)(navigation - .DeclaringEntityType - .GetServiceProperties() - .FirstOrDefault(p => p.ClrType == typeof(ILazyLoader))) - ?.GetGetter().GetClrValue(entity)) - ?.SetLoaded(entity, navigation.Name); + private static TCollection MaterializeCollection( + QueryContext queryContext, + IEnumerable innerValueBuffers, + Func innerShaper, + IClrCollectionAccessor clrCollectionAccessor) + where TCollection : class, ICollection + { + var collection = (TCollection)(clrCollectionAccessor?.Create() ?? new List()); + + foreach (var valueBuffer in innerValueBuffers) + { + var element = innerShaper(queryContext, valueBuffer); + collection.Add(element); + } + + return collection; + } protected override Expression VisitExtension(Expression extensionExpression) { @@ -138,12 +160,12 @@ protected override Expression VisitExtension(Expression extensionExpression) if (includeExpression.Navigation.IsCollection()) { - var collectionShaperExpression = (CollectionShaperExpression)includeExpression.NavigationExpression; + var collectionShaper = (CollectionShaperExpression)includeExpression.NavigationExpression; return Expression.Call( _includeCollectionMethodInfo.MakeGenericMethod(entityClrType, includingClrType, relatedEntityClrType), QueryCompilationContext.QueryContextParameter, - collectionShaperExpression.Projection, - Expression.Constant(((LambdaExpression)Visit(collectionShaperExpression.InnerShaper)).Compile()), + collectionShaper.Projection, + Expression.Constant(((LambdaExpression)Visit(collectionShaper.InnerShaper)).Compile()), includeExpression.EntityExpression, Expression.Constant(includeExpression.Navigation), Expression.Constant(inverseNavigation, typeof(INavigation)), @@ -166,6 +188,21 @@ protected override Expression VisitExtension(Expression extensionExpression) Expression.Constant(_tracking)); } + if (extensionExpression is CollectionShaperExpression collectionShaperExpression) + { + var elementType = collectionShaperExpression.ElementType; + var collectionType = collectionShaperExpression.Type; + + return Expression.Call( + _materializeCollectionMethodInfo.MakeGenericMethod(elementType, collectionType), + QueryCompilationContext.QueryContextParameter, + collectionShaperExpression.Projection, + Expression.Constant(((LambdaExpression)Visit(collectionShaperExpression.InnerShaper)).Compile()), + Expression.Constant( + collectionShaperExpression.Navigation?.GetCollectionAccessor(), + typeof(IClrCollectionAccessor))); + } + return base.VisitExtension(extensionExpression); } diff --git a/src/EFCore.InMemory/Query/Internal/ShaperExpressionProcessingExpressionVisitor.cs b/src/EFCore.InMemory/Query/Internal/ShaperExpressionProcessingExpressionVisitor.cs index 619ecb76edf..d0a61f7bc5a 100644 --- a/src/EFCore.InMemory/Query/Internal/ShaperExpressionProcessingExpressionVisitor.cs +++ b/src/EFCore.InMemory/Query/Internal/ShaperExpressionProcessingExpressionVisitor.cs @@ -75,17 +75,17 @@ protected override Expression VisitExtension(Expression extensionExpression) case IncludeExpression includeExpression: { var entity = Visit(includeExpression.EntityExpression); - if (includeExpression.NavigationExpression is CollectionShaperExpression collectionShaperExpression) + if (includeExpression.NavigationExpression is CollectionShaperExpression collectionShaper) { - var innerLambda = (LambdaExpression)collectionShaperExpression.InnerShaper; + var innerLambda = (LambdaExpression)collectionShaper.InnerShaper; var innerShaper = new ShaperExpressionProcessingExpressionVisitor(null, innerLambda.Parameters[0]) .Inject(innerLambda.Body); _expressions.Add( includeExpression.Update( entity, - collectionShaperExpression.Update( - Visit(collectionShaperExpression.Projection), + collectionShaper.Update( + Visit(collectionShaper.Projection), innerShaper))); } else @@ -98,6 +98,27 @@ protected override Expression VisitExtension(Expression extensionExpression) return entity; } + + case CollectionShaperExpression collectionShaperExpression: + { + var key = GenerateKey((ProjectionBindingExpression)collectionShaperExpression.Projection); + if (!_mapping.TryGetValue(key, out var variable)) + { + var projection = Visit(collectionShaperExpression.Projection); + + variable = Expression.Parameter(collectionShaperExpression.Type); + _variables.Add(variable); + + var innerLambda = (LambdaExpression)collectionShaperExpression.InnerShaper; + var innerShaper = new ShaperExpressionProcessingExpressionVisitor(null, innerLambda.Parameters[0]) + .Inject(innerLambda.Body); + + _expressions.Add(Expression.Assign(variable, collectionShaperExpression.Update(projection, innerShaper))); + _mapping[key] = variable; + } + + return variable; + } } return base.VisitExtension(extensionExpression); diff --git a/src/EFCore/Query/CollectionShaperExpression.cs b/src/EFCore/Query/CollectionShaperExpression.cs index 93248a52650..4008ff51d53 100644 --- a/src/EFCore/Query/CollectionShaperExpression.cs +++ b/src/EFCore/Query/CollectionShaperExpression.cs @@ -19,7 +19,7 @@ public CollectionShaperExpression( Projection = projection; InnerShaper = innerShaper; Navigation = navigation; - ElementType = elementType; + ElementType = elementType ?? navigation.ClrType.TryGetSequenceType(); } protected override Expression VisitChildren(ExpressionVisitor visitor) diff --git a/test/EFCore.InMemory.FunctionalTests/Query/AsyncGearsOfWarQueryInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/AsyncGearsOfWarQueryInMemoryTest.cs index a82c81a088a..951c4c7d59e 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/AsyncGearsOfWarQueryInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/AsyncGearsOfWarQueryInMemoryTest.cs @@ -14,7 +14,7 @@ public AsyncGearsOfWarQueryInMemoryTest(GearsOfWarQueryInMemoryFixture fixture, { } - [ConditionalFact(Skip = "Issue#16963")] + [ConditionalFact(Skip = "Issue#16963 Group By")] public override Task GroupBy_Select_sum() { return base.GroupBy_Select_sum(); diff --git a/test/EFCore.InMemory.FunctionalTests/Query/AsyncSimpleQueryInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/AsyncSimpleQueryInMemoryTest.cs index d8e66946572..386bec964c3 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/AsyncSimpleQueryInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/AsyncSimpleQueryInMemoryTest.cs @@ -14,76 +14,40 @@ public AsyncSimpleQueryInMemoryTest(NorthwindQueryInMemoryFixture null; - - [ConditionalFact(Skip = "See issue#13857")] - public override void DbQuery_query() - { - base.DbQuery_query(); - } - - [ConditionalFact(Skip = "See issue#13857")] - public override Task DbQuery_query_async() - { - return base.DbQuery_query_async(); - } - - [ConditionalFact(Skip = "See issue#13857")] - public override void DbQuery_query_first() - { - base.DbQuery_query_first(); - } - - [ConditionalFact(Skip = "See issue#13857")] - public override Task DbQuery_query_first_async() - { - return base.DbQuery_query_first_async(); - } } } diff --git a/test/EFCore.InMemory.FunctionalTests/Query/FiltersInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/FiltersInMemoryTest.cs index 1460bf1ecad..2533808c696 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/FiltersInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/FiltersInMemoryTest.cs @@ -1,7 +1,6 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using Xunit; using Xunit.Abstractions; namespace Microsoft.EntityFrameworkCore.Query @@ -13,33 +12,5 @@ public FiltersInMemoryTest(NorthwindQueryInMemoryFixture Date: Fri, 23 Aug 2019 15:36:29 -0700 Subject: [PATCH 07/21] InMemory: Test scrub Allow ExpressionTranslator to return null Fix bug in OfType Return null for unmapped property to fail gracefully Part of #16963 --- .../Internal/EntityProjectionExpression.cs | 16 + ...yExpressionTranslatingExpressionVisitor.cs | 70 ++- ...yableMethodTranslatingExpressionVisitor.cs | 11 + ...jectionBindingRemovingExpressionVisitor.cs | 10 + .../BuiltInDataTypesInMemoryTest.cs | 6 - .../ConvertToProviderTypesInMemoryTest.cs | 6 - .../CustomConvertersInMemoryTest.cs | 6 - .../FieldMappingInMemoryTest.cs | 66 -- .../InMemoryComplianceTest.cs | 2 +- .../LazyLoadProxyInMemoryTest.cs | 8 +- .../MonsterFixupSnapshotInMemoryTest.cs | 16 +- .../PropertyValuesInMemoryTest.cs | 90 --- .../ComplexNavigationsQueryInMemoryTest.cs | 164 ----- .../Query/GearsOfWarQueryInMemoryTest.cs | 66 -- .../Query/IncludeAsyncInMemoryTest.cs | 3 - .../Query/IncludeInMemoryTest.cs | 7 +- .../Query/InheritanceInMemoryTest.cs | 7 +- .../Query/QueryBugsInMemoryTest.cs | 5 +- .../Query/QueryNavigationsInMemoryTest.cs | 64 +- .../Query/SimpleQueryInMemoryTest.cs | 567 +++++++++--------- .../WithConstructorsInMemoryTest.cs | 7 - 21 files changed, 426 insertions(+), 771 deletions(-) diff --git a/src/EFCore.InMemory/Query/Internal/EntityProjectionExpression.cs b/src/EFCore.InMemory/Query/Internal/EntityProjectionExpression.cs index e82b8bff9ed..2f34586df3f 100644 --- a/src/EFCore.InMemory/Query/Internal/EntityProjectionExpression.cs +++ b/src/EFCore.InMemory/Query/Internal/EntityProjectionExpression.cs @@ -23,6 +23,22 @@ public EntityProjectionExpression( public override Type Type => EntityType.ClrType; public sealed override ExpressionType NodeType => ExpressionType.Extension; + public virtual EntityProjectionExpression UpdateEntityType(IEntityType derivedType) + { + var readExpressionMap = new Dictionary(); + foreach (var kvp in _readExpressionMap) + { + var property = kvp.Key; + if (derivedType.IsAssignableFrom(property.DeclaringEntityType) + || property.DeclaringEntityType.IsAssignableFrom(derivedType)) + { + readExpressionMap[property] = kvp.Value; + } + } + + return new EntityProjectionExpression(derivedType, readExpressionMap); + } + public virtual Expression BindProperty(IProperty property) { if (!EntityType.IsAssignableFrom(property.DeclaringEntityType) diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs b/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs index 1e6a5ed6350..1fb84dbd69a 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs @@ -66,23 +66,54 @@ public virtual Expression Translate(Expression expression) : result; } + protected override Expression VisitBinary(BinaryExpression binaryExpression) + { + var left = Visit(binaryExpression.Left); + var right = Visit(binaryExpression.Right); + if (left == null || right == null) + { + return null; + } + + return binaryExpression.Update(left, binaryExpression.Conversion, right); + } + + protected override Expression VisitConditional(ConditionalExpression conditionalExpression) + { + var test = Visit(conditionalExpression.Test); + var ifTrue = Visit(conditionalExpression.IfTrue); + var ifFalse = Visit(conditionalExpression.IfFalse); + if (test == null || ifTrue == null || ifFalse == null) + { + return null; + } + + return conditionalExpression.Update(test, ifTrue, ifFalse); + } + protected override Expression VisitMember(MemberExpression memberExpression) { var innerExpression = Visit(memberExpression.Expression); + if (memberExpression.Expression != null && innerExpression == null) + { + return null; + } - if (innerExpression is EntityProjectionExpression + if ((innerExpression is EntityProjectionExpression || (innerExpression is UnaryExpression innerUnaryExpression && innerUnaryExpression.NodeType == ExpressionType.Convert && innerUnaryExpression.Operand is EntityProjectionExpression)) + && TryBindMember(innerExpression, MemberIdentity.Create(memberExpression.Member), memberExpression.Type, out var result)) { - return BindProperty(innerExpression, memberExpression.Member.GetSimpleMemberName(), memberExpression.Type); + return result; } return memberExpression.Update(innerExpression); } - private Expression BindProperty(Expression source, string propertyName, Type type) + private bool TryBindMember(Expression source, MemberIdentity memberIdentity, Type type, out Expression result) { + result = null; Type convertedType = null; if (source is UnaryExpression unaryExpression && unaryExpression.NodeType == ExpressionType.Convert) @@ -105,21 +136,29 @@ private Expression BindProperty(Expression source, string propertyName, Type typ .FirstOrDefault(et => et.ClrType == convertedType); if (entityType == null) { - return null; + return false; } } - var property = entityType.GetRootType().GetDerivedTypesInclusive() - .Select(et => et.FindProperty(propertyName)) - .FirstOrDefault(p => p != null); + var property = memberIdentity.MemberInfo != null + ? entityType.FindProperty(memberIdentity.MemberInfo) + : entityType.FindProperty(memberIdentity.Name); + // If unmapped property return null + if (property == null) + { + return false; + } + + result = BindProperty(entityProjection, property); + if (result.Type != type) + { + result = Expression.Convert(result, type); + } - var result = BindProperty(entityProjection, property); - return result.Type == type - ? result - : Expression.Convert(result, type); + return true; } - throw new InvalidOperationException(CoreStrings.TranslationFailed(source.Print())); + return false; } private Expression BindProperty(EntityProjectionExpression entityProjectionExpression, IProperty property) @@ -132,7 +171,12 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp // EF.Property case if (methodCallExpression.TryGetEFPropertyArguments(out var source, out var propertyName)) { - return BindProperty(Visit(source), propertyName, methodCallExpression.Type); + if (TryBindMember(Visit(source), MemberIdentity.Create(propertyName), methodCallExpression.Type, out var result)) + { + return result; + } + + throw new InvalidOperationException("EF.Property called with wrong property name."); } // Subquery case diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs index 4b0feaf449d..1446defa48d 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Reflection; @@ -379,6 +380,16 @@ protected override ShapedQueryExpression TranslateOfType(ShapedQueryExpression s inMemoryQueryExpression.ServerQueryExpression, predicate); + var projectionBindingExpression = (ProjectionBindingExpression)entityShaperExpression.ValueBufferExpression; + var projectionMember = projectionBindingExpression.ProjectionMember; + var entityProjection = (EntityProjectionExpression)inMemoryQueryExpression.GetMappedProjection(projectionMember); + + inMemoryQueryExpression.ReplaceProjectionMapping( + new Dictionary + { + { projectionMember, entityProjection.UpdateEntityType(derivedType)} + }); + source.ShaperExpression = entityShaperExpression.WithEntityType(derivedType); return source; diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryShapedQueryCompilingExpressionVisitor.InMemoryProjectionBindingRemovingExpressionVisitor.cs b/src/EFCore.InMemory/Query/Internal/InMemoryShapedQueryCompilingExpressionVisitor.InMemoryProjectionBindingRemovingExpressionVisitor.cs index b418040f783..b8d80ac7b1f 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryShapedQueryCompilingExpressionVisitor.InMemoryProjectionBindingRemovingExpressionVisitor.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryShapedQueryCompilingExpressionVisitor.InMemoryProjectionBindingRemovingExpressionVisitor.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.Linq.Expressions; +using System.Reflection; +using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Query; using Microsoft.EntityFrameworkCore.Storage; @@ -40,6 +42,14 @@ protected override Expression VisitBinary(BinaryExpression binaryExpression) return Expression.MakeBinary(ExpressionType.Assign, binaryExpression.Left, updatedExpression); } + if (binaryExpression.NodeType == ExpressionType.Assign + && binaryExpression.Left is MemberExpression memberExpression + && memberExpression.Member is FieldInfo fieldInfo + && fieldInfo.IsInitOnly) + { + return memberExpression.Assign(Visit(binaryExpression.Right)); + } + return base.VisitBinary(binaryExpression); } diff --git a/test/EFCore.InMemory.FunctionalTests/BuiltInDataTypesInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/BuiltInDataTypesInMemoryTest.cs index 3fa64fc7c23..75476210e6a 100644 --- a/test/EFCore.InMemory.FunctionalTests/BuiltInDataTypesInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/BuiltInDataTypesInMemoryTest.cs @@ -16,12 +16,6 @@ public BuiltInDataTypesInMemoryTest(BuiltInDataTypesInMemoryFixture fixture) { } - [ConditionalFact(Skip = "Issue#16963")] - public override void Can_insert_and_read_back_with_string_key() - { - base.Can_insert_and_read_back_with_string_key(); - } - public class BuiltInDataTypesInMemoryFixture : BuiltInDataTypesFixtureBase { protected override ITestStoreFactory TestStoreFactory => InMemoryTestStoreFactory.Instance; diff --git a/test/EFCore.InMemory.FunctionalTests/ConvertToProviderTypesInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/ConvertToProviderTypesInMemoryTest.cs index c4e2cc38e52..ed2e4e2681f 100644 --- a/test/EFCore.InMemory.FunctionalTests/ConvertToProviderTypesInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/ConvertToProviderTypesInMemoryTest.cs @@ -15,12 +15,6 @@ public ConvertToProviderTypesInMemoryTest(ConvertToProviderTypesInMemoryFixture { } - [ConditionalFact(Skip = "Issue#16963")] - public override void Can_insert_and_read_back_with_string_key() - { - base.Can_insert_and_read_back_with_string_key(); - } - public class ConvertToProviderTypesInMemoryFixture : ConvertToProviderTypesFixtureBase { protected override ITestStoreFactory TestStoreFactory => InMemoryTestStoreFactory.Instance; diff --git a/test/EFCore.InMemory.FunctionalTests/CustomConvertersInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/CustomConvertersInMemoryTest.cs index b64525cfe2d..e6ab477b82c 100644 --- a/test/EFCore.InMemory.FunctionalTests/CustomConvertersInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/CustomConvertersInMemoryTest.cs @@ -19,12 +19,6 @@ public override void Can_insert_and_read_back_with_case_insensitive_string_key() { } - [ConditionalFact(Skip = "Issue#16963")] - public override void Can_insert_and_read_back_with_string_key() - { - base.Can_insert_and_read_back_with_string_key(); - } - public class CustomConvertersInMemoryFixture : CustomConvertersFixtureBase { public override bool StrictEquality => true; diff --git a/test/EFCore.InMemory.FunctionalTests/FieldMappingInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/FieldMappingInMemoryTest.cs index 5c43740b376..e4d9cef8b17 100644 --- a/test/EFCore.InMemory.FunctionalTests/FieldMappingInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/FieldMappingInMemoryTest.cs @@ -14,72 +14,6 @@ public FieldMappingInMemoryTest(FieldMappingInMemoryFixture fixture) { } - [ConditionalFact(Skip = "Issue#16963")] - public override void Field_mapping_with_conversion_does_not_throw() - { - base.Field_mapping_with_conversion_does_not_throw(); - } - - [ConditionalTheory(Skip = "Issue#16963")] - public override void Include_collection_auto_props(bool tracking) - { - base.Include_collection_auto_props(tracking); - } - - [ConditionalTheory(Skip = "Issue#16963")] - public override void Include_collection_fields_only(bool tracking) - { - base.Include_collection_fields_only(tracking); - } - - [ConditionalTheory(Skip = "Issue#16963")] - public override void Include_collection_fields_only_for_navs_too(bool tracking) - { - base.Include_collection_fields_only_for_navs_too(tracking); - } - - [ConditionalTheory(Skip = "Issue#16963")] - public override void Include_collection_full_props(bool tracking) - { - base.Include_collection_full_props(tracking); - } - - [ConditionalTheory(Skip = "Issue#16963")] - public override void Include_collection_full_props_with_named_fields(bool tracking) - { - base.Include_collection_full_props_with_named_fields(tracking); - } - - [ConditionalTheory(Skip = "Issue#16963")] - public override void Include_collection_hiding_props(bool tracking) - { - base.Include_collection_hiding_props(tracking); - } - - [ConditionalTheory(Skip = "Issue#16963")] - public override void Include_collection_read_only_props(bool tracking) - { - base.Include_collection_read_only_props(tracking); - } - - [ConditionalTheory(Skip = "Issue#16963")] - public override void Include_collection_read_only_props_with_named_fields(bool tracking) - { - base.Include_collection_read_only_props_with_named_fields(tracking); - } - - [ConditionalTheory(Skip = "Issue#16963")] - public override void Include_collection_write_only_props(bool tracking) - { - base.Include_collection_write_only_props(tracking); - } - - [ConditionalTheory(Skip = "Issue#16963")] - public override void Include_collection_write_only_props_with_named_fields(bool tracking) - { - base.Include_collection_write_only_props_with_named_fields(tracking); - } - protected override void Update(string navigation) { base.Update(navigation); diff --git a/test/EFCore.InMemory.FunctionalTests/InMemoryComplianceTest.cs b/test/EFCore.InMemory.FunctionalTests/InMemoryComplianceTest.cs index ea5d07c1467..c47cbf14784 100644 --- a/test/EFCore.InMemory.FunctionalTests/InMemoryComplianceTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/InMemoryComplianceTest.cs @@ -19,7 +19,6 @@ public class InMemoryComplianceTest : ComplianceTestBase typeof(ConferencePlannerTestBase<>), // Remaining Issue #16963 3.0 query tests: typeof(ComplexNavigationsWeakQueryTestBase<>), - typeof(FiltersInheritanceTestBase<>), typeof(OwnedQueryTestBase<>), typeof(GroupByQueryTestBase<>), typeof(ComplexNavigationsQueryTestBase<>), @@ -30,3 +29,4 @@ public class InMemoryComplianceTest : ComplianceTestBase protected override Assembly TargetAssembly { get; } = typeof(InMemoryComplianceTest).Assembly; } } + diff --git a/test/EFCore.InMemory.FunctionalTests/LazyLoadProxyInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/LazyLoadProxyInMemoryTest.cs index efe535cda0c..0fb3f7c3cf7 100644 --- a/test/EFCore.InMemory.FunctionalTests/LazyLoadProxyInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/LazyLoadProxyInMemoryTest.cs @@ -14,25 +14,25 @@ public LazyLoadProxyInMemoryTest(LoadInMemoryFixture fixture) { } - [ConditionalFact(Skip = "Issue#16963")] + [ConditionalFact(Skip = "Issue#16963 Owned")] public override void Lazy_loading_finds_correct_entity_type_with_already_loaded_owned_types() { base.Lazy_loading_finds_correct_entity_type_with_already_loaded_owned_types(); } - [ConditionalFact(Skip = "Issue#16963")] + [ConditionalFact(Skip = "Issue#16963 Owned")] public override void Lazy_loading_finds_correct_entity_type_with_alternate_model() { base.Lazy_loading_finds_correct_entity_type_with_alternate_model(); } - [ConditionalFact(Skip = "Issue#16963")] + [ConditionalFact(Skip = "Issue#16963 Owned")] public override void Lazy_loading_finds_correct_entity_type_with_multiple_queries() { base.Lazy_loading_finds_correct_entity_type_with_multiple_queries(); } - [ConditionalFact(Skip = "Issue#16963")] + [ConditionalFact(Skip = "Issue#16963 Owned")] public override void Lazy_loading_finds_correct_entity_type_with_opaque_predicate_and_multiple_queries() { base.Lazy_loading_finds_correct_entity_type_with_opaque_predicate_and_multiple_queries(); diff --git a/test/EFCore.InMemory.FunctionalTests/MonsterFixupSnapshotInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/MonsterFixupSnapshotInMemoryTest.cs index 7225c16f09d..82fdc30be9e 100644 --- a/test/EFCore.InMemory.FunctionalTests/MonsterFixupSnapshotInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/MonsterFixupSnapshotInMemoryTest.cs @@ -14,49 +14,49 @@ public MonsterFixupSnapshotInMemoryTest(MonsterFixupSnapshotInMemoryFixture fixt { } - [ConditionalFact(Skip = "Issue#16963")] + [ConditionalFact(Skip = "Issue#16963 Owned")] public override void Can_build_monster_model_and_seed_data_using_all_navigations() { base.Can_build_monster_model_and_seed_data_using_all_navigations(); } - [ConditionalFact(Skip = "Issue#16963")] + [ConditionalFact(Skip = "Issue#16963 Owned")] public override void Can_build_monster_model_and_seed_data_using_dependent_navigations() { base.Can_build_monster_model_and_seed_data_using_dependent_navigations(); } - [ConditionalFact(Skip = "Issue#16963")] + [ConditionalFact(Skip = "Issue#16963 Owned")] public override void Can_build_monster_model_and_seed_data_using_FKs() { base.Can_build_monster_model_and_seed_data_using_FKs(); } - [ConditionalFact(Skip = "Issue#16963")] + [ConditionalFact(Skip = "Issue#16963 Owned")] public override void Can_build_monster_model_and_seed_data_using_navigations_with_deferred_add() { base.Can_build_monster_model_and_seed_data_using_navigations_with_deferred_add(); } - [ConditionalFact(Skip = "Issue#16963")] + [ConditionalFact(Skip = "Issue#16963 Owned")] public override void Can_build_monster_model_and_seed_data_using_principal_navigations() { base.Can_build_monster_model_and_seed_data_using_principal_navigations(); } - [ConditionalFact(Skip = "Issue#16963")] + [ConditionalFact(Skip = "Issue#16963 Owned")] public override void One_to_one_fixup_happens_when_FKs_change_test() { base.One_to_one_fixup_happens_when_FKs_change_test(); } - [ConditionalFact(Skip = "Issue#16963")] + [ConditionalFact(Skip = "Issue#16963 Owned")] public override void One_to_one_fixup_happens_when_reference_change_test() { base.One_to_one_fixup_happens_when_reference_change_test(); } - [ConditionalFact(Skip = "Issue#16963")] + [ConditionalFact(Skip = "Issue#16963 Owned")] public override void Composite_fixup_happens_when_FKs_change_test() { base.Composite_fixup_happens_when_FKs_change_test(); diff --git a/test/EFCore.InMemory.FunctionalTests/PropertyValuesInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/PropertyValuesInMemoryTest.cs index 9027c306ea4..00253161ad8 100644 --- a/test/EFCore.InMemory.FunctionalTests/PropertyValuesInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/PropertyValuesInMemoryTest.cs @@ -14,96 +14,6 @@ public PropertyValuesInMemoryTest(PropertyValuesInMemoryFixture fixture) { } - [ConditionalFact(Skip = "Issue #16963")] - public override Task Current_values_for_derived_object_can_be_copied_into_an_object() - => Task.CompletedTask; - - [ConditionalFact(Skip = "Issue #16963")] - public override Task GetDatabaseValuesAsync_for_derived_entity_not_in_the_store_returns_null() - => Task.CompletedTask; - - [ConditionalFact(Skip = "Issue #16963")] - public override Task GetDatabaseValuesAsync_for_the_wrong_type_in_the_store_returns_null() - => Task.CompletedTask; - - [ConditionalFact(Skip = "Issue #16963")] - public override Task GetDatabaseValues_for_derived_entity_not_in_the_store_returns_null() - => Task.CompletedTask; - - [ConditionalFact(Skip = "Issue #16963")] - public override Task GetDatabaseValues_for_the_wrong_type_in_the_store_returns_null() - => Task.CompletedTask; - - [ConditionalFact(Skip = "Issue #16963")] - public override Task NonGeneric_GetDatabaseValuesAsync_for_derived_entity_not_in_the_store_returns_null() - => Task.CompletedTask; - - [ConditionalFact(Skip = "Issue #16963")] - public override Task NonGeneric_GetDatabaseValuesAsync_for_the_wrong_type_in_the_store_throws() - => Task.CompletedTask; - - [ConditionalFact(Skip = "Issue #16963")] - public override Task NonGeneric_GetDatabaseValues_for_derived_entity_not_in_the_store_returns_null() - => Task.CompletedTask; - - [ConditionalFact(Skip = "Issue #16963")] - public override Task NonGeneric_GetDatabaseValues_for_the_wrong_type_in_the_store_throws() - => Task.CompletedTask; - - [ConditionalFact(Skip = "Issue #16963")] - public override Task Original_values_for_derived_object_can_be_copied_into_an_object() - => Task.CompletedTask; - - [ConditionalFact(Skip = "Issue #16963")] - public override Task Scalar_current_values_of_a_derived_object_can_be_accessed_as_a_non_generic_property_dictionary() - => Task.CompletedTask; - - [ConditionalFact(Skip = "Issue #16963")] - public override Task Scalar_current_values_of_a_derived_object_can_be_accessed_as_a_property_dictionary() - => Task.CompletedTask; - - [ConditionalFact(Skip = "Issue #16963")] - public override Task Scalar_original_values_of_a_derived_object_can_be_accessed_as_a_non_generic_property_dictionary() - => Task.CompletedTask; - - [ConditionalFact(Skip = "Issue #16963")] - public override Task Scalar_original_values_of_a_derived_object_can_be_accessed_as_a_property_dictionary() - => Task.CompletedTask; - - [ConditionalFact(Skip = "Issue #16963")] - public override Task Scalar_store_values_of_a_derived_object_can_be_accessed_as_a_non_generic_property_dictionary() - => Task.CompletedTask; - - [ConditionalFact(Skip = "Issue #16963")] - public override Task Scalar_store_values_of_a_derived_object_can_be_accessed_as_a_property_dictionary() - => Task.CompletedTask; - - [ConditionalFact(Skip = "Issue #16963")] - public override Task Scalar_store_values_of_a_derived_object_can_be_accessed_asynchronously_as_a_non_generic_property_dictionary() - => Task.CompletedTask; - - [ConditionalFact(Skip = "Issue #16963")] - public override Task Scalar_store_values_of_a_derived_object_can_be_accessed_asynchronously_as_a_property_dictionary() - => Task.CompletedTask; - - [ConditionalFact(Skip = "Issue #16963")] - public override Task Store_values_for_derived_object_can_be_copied_into_an_object() - => Task.CompletedTask; - - [ConditionalFact(Skip = "Issue #16963")] - public override Task Store_values_for_derived_object_can_be_copied_into_an_object_asynchronously() - => Task.CompletedTask; - - [ConditionalFact(Skip = "Issue #16963")] - public override void Using_bad_IProperty_instances_throws_derived() - { - } - - [ConditionalFact(Skip = "Issue #16963")] - public override void Using_bad_property_names_throws_derived() - { - } - public class PropertyValuesInMemoryFixture : PropertyValuesFixtureBase { protected override ITestStoreFactory TestStoreFactory => InMemoryTestStoreFactory.Instance; diff --git a/test/EFCore.InMemory.FunctionalTests/Query/ComplexNavigationsQueryInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/ComplexNavigationsQueryInMemoryTest.cs index f9f98205c6a..86b164eb948 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/ComplexNavigationsQueryInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/ComplexNavigationsQueryInMemoryTest.cs @@ -14,169 +14,5 @@ public ComplexNavigationsQueryInMemoryTest(ComplexNavigationsQueryInMemoryFixtur { //TestLoggerFactory.TestOutputHelper = testOutputHelper; } - - [ConditionalTheory(Skip = "issue #4311")] - public override Task Nested_group_join_with_take(bool IsAsync) - { - return base.Nested_group_join_with_take(IsAsync); - } - - [ConditionalTheory(Skip = "issue #9591")] - public override Task Multi_include_with_groupby_in_subquery(bool IsAsync) - { - return base.Multi_include_with_groupby_in_subquery(IsAsync); - } - - [ConditionalTheory(Skip = "issue #13561")] - public override Task - Complex_SelectMany_with_nested_navigations_and_explicit_DefaultIfEmpty_with_other_query_operators_composed_on_top(bool isAsync) - { - return base.Complex_SelectMany_with_nested_navigations_and_explicit_DefaultIfEmpty_with_other_query_operators_composed_on_top( - isAsync); - } - - [ConditionalTheory(Skip = "issue #16963")] - public override Task Multiple_SelectMany_with_nested_navigations_and_explicit_DefaultIfEmpty_joined_together(bool isAsync) - { - return base.Multiple_SelectMany_with_nested_navigations_and_explicit_DefaultIfEmpty_joined_together(isAsync); - } - - [ConditionalTheory(Skip = "issue #16963")] - public override Task Key_equality_using_property_method_nested(bool isAsync) - { - return base.Key_equality_using_property_method_nested(isAsync); - } - - [ConditionalTheory(Skip = "issue #16963")] - public override Task Key_equality_using_property_method_required(bool isAsync) - { - return base.Key_equality_using_property_method_required(isAsync); - } - - [ConditionalTheory(Skip = "issue #16963")] - public override Task Key_equality_when_sentinel_ef_property(bool isAsync) - { - return base.Key_equality_when_sentinel_ef_property(isAsync); - } - - [ConditionalTheory(Skip = "issue #16963")] - public override Task Navigation_inside_method_call_translated_to_join(bool isAsync) - { - return base.Navigation_inside_method_call_translated_to_join(isAsync); - } - - [ConditionalTheory(Skip = "issue #16963")] - public override Task Entity_equality_empty(bool isAsync) - { - return base.Entity_equality_empty(isAsync); - } - - [ConditionalTheory(Skip = "issue #16963")] - public override Task Optional_navigation_inside_property_method_translated_to_join(bool isAsync) - { - return base.Optional_navigation_inside_property_method_translated_to_join(isAsync); - } - - [ConditionalTheory(Skip = "issue #16963")] - public override Task Navigation_key_access_optional_comparison(bool isAsync) - { - return base.Navigation_key_access_optional_comparison(isAsync); - } - - [ConditionalTheory(Skip = "issue #16963")] - public override Task Optional_navigation_inside_method_call_translated_to_join(bool isAsync) - { - return base.Optional_navigation_inside_method_call_translated_to_join(isAsync); - } - - [ConditionalTheory(Skip = "issue #16963")] - public override Task SelectMany_with_nested_navigations_explicit_DefaultIfEmpty_and_additional_joins_outside_of_SelectMany3(bool isAsync) - { - return base.SelectMany_with_nested_navigations_explicit_DefaultIfEmpty_and_additional_joins_outside_of_SelectMany3(isAsync); - } - - [ConditionalTheory(Skip = "issue #16963")] - public override Task Optional_navigation_inside_method_call_translated_to_join_keeps_original_nullability(bool isAsync) - { - return base.Optional_navigation_inside_method_call_translated_to_join_keeps_original_nullability(isAsync); - } - - [ConditionalTheory(Skip = "issue #16963")] - public override Task Key_equality_two_conditions_on_same_navigation(bool isAsync) - { - return base.Key_equality_two_conditions_on_same_navigation(isAsync); - } - - [ConditionalTheory(Skip = "issue #16963")] - public override Task Method_call_on_optional_navigation_translates_to_null_conditional_properly_for_arguments(bool isAsync) - { - return base.Method_call_on_optional_navigation_translates_to_null_conditional_properly_for_arguments(isAsync); - } - - [ConditionalTheory(Skip = "issue #16963")] - public override Task Optional_navigation_propagates_nullability_to_manually_created_left_join1(bool isAsync) - { - return base.Optional_navigation_propagates_nullability_to_manually_created_left_join1(isAsync); - } - - [ConditionalTheory(Skip = "issue #16963")] - public override Task Key_equality_using_property_method_and_member_expression1(bool isAsync) - { - return base.Key_equality_using_property_method_and_member_expression1(isAsync); - } - - [ConditionalTheory(Skip = "issue #16963")] - public override Task Key_equality_using_property_method_and_member_expression2(bool isAsync) - { - return base.Key_equality_using_property_method_and_member_expression2(isAsync); - } - - [ConditionalTheory(Skip = "issue #16963")] - public override Task Complex_navigations_with_predicate_projected_into_anonymous_type(bool isAsync) - { - return base.Complex_navigations_with_predicate_projected_into_anonymous_type(isAsync); - } - - [ConditionalTheory(Skip = "issue #16963")] - public override Task Optional_navigation_inside_nested_method_call_translated_to_join(bool isAsync) - { - return base.Optional_navigation_inside_nested_method_call_translated_to_join(isAsync); - } - - [ConditionalTheory(Skip = "issue #16963")] - public override Task Join_navigation_in_outer_selector_translated_to_extra_join_nested2(bool isAsync) - { - return base.Join_navigation_in_outer_selector_translated_to_extra_join_nested2(isAsync); - } - - [ConditionalTheory(Skip = "issue #16963")] - public override Task Join_navigation_in_outer_selector_translated_to_extra_join(bool isAsync) - { - return base.Join_navigation_in_outer_selector_translated_to_extra_join(isAsync); - } - - [ConditionalTheory(Skip = "issue #16963")] - public override Task SelectMany_with_nested_navigations_explicit_DefaultIfEmpty_and_additional_joins_outside_of_SelectMany4(bool isAsync) - { - return base.SelectMany_with_nested_navigations_explicit_DefaultIfEmpty_and_additional_joins_outside_of_SelectMany4(isAsync); - } - - [ConditionalTheory(Skip = "issue #16963")] - public override Task Join_navigation_in_outer_selector_translated_to_extra_join_nested(bool isAsync) - { - return base.Join_navigation_in_outer_selector_translated_to_extra_join_nested(isAsync); - } - - [ConditionalTheory(Skip = "issue #16963")] - public override Task Optional_navigation_inside_nested_method_call_translated_to_join_keeps_original_nullability(bool isAsync) - { - return base.Optional_navigation_inside_nested_method_call_translated_to_join_keeps_original_nullability(isAsync); - } - - [ConditionalTheory(Skip = "issue #16963")] - public override Task Optional_navigation_inside_nested_method_call_translated_to_join_keeps_original_nullability_also_for_arguments(bool isAsync) - { - return base.Optional_navigation_inside_nested_method_call_translated_to_join_keeps_original_nullability_also_for_arguments(isAsync); - } } } diff --git a/test/EFCore.InMemory.FunctionalTests/Query/GearsOfWarQueryInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/GearsOfWarQueryInMemoryTest.cs index 1d45aaca666..75f037b9809 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/GearsOfWarQueryInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/GearsOfWarQueryInMemoryTest.cs @@ -14,71 +14,5 @@ public GearsOfWarQueryInMemoryTest(GearsOfWarQueryInMemoryFixture fixture, ITest { //TestLoggerFactory.TestOutputHelper = testOutputHelper; } - - [ConditionalTheory(Skip = "issue #12295")] - public override Task Double_order_by_on_nullable_bool_coming_from_optional_navigation(bool isAsync) - { - return base.Double_order_by_on_nullable_bool_coming_from_optional_navigation(isAsync); - } - - [ConditionalTheory(Skip = "issue #13746")] - public override Task Null_semantics_is_correctly_applied_for_function_comparisons_that_take_arguments_from_optional_navigation_complex(bool isAsync) - { - return base.Null_semantics_is_correctly_applied_for_function_comparisons_that_take_arguments_from_optional_navigation_complex(isAsync); - } - - [ConditionalTheory(Skip = "issue #16963")] - public override Task Negated_bool_ternary_inside_anonymous_type_in_projection(bool isAsync) - { - return base.Negated_bool_ternary_inside_anonymous_type_in_projection(isAsync); - } - - [ConditionalTheory(Skip = "issue #16963")] - public override Task Optional_navigation_type_compensation_works_with_binary_expression(bool isAsync) - { - return base.Optional_navigation_type_compensation_works_with_binary_expression(isAsync); - } - - [ConditionalTheory(Skip = "issue #16963")] - public override Task Complex_predicate_with_AndAlso_and_nullable_bool_property(bool isAsync) - { - return base.Complex_predicate_with_AndAlso_and_nullable_bool_property(isAsync); - } - - [ConditionalTheory(Skip = "issue #16963")] - public override Task Optional_navigation_type_compensation_works_with_conditional_expression(bool isAsync) - { - return base.Optional_navigation_type_compensation_works_with_conditional_expression(isAsync); - } - - [ConditionalTheory(Skip = "issue #16963")] - public override Task Optional_navigation_type_compensation_works_with_predicate_negated(bool isAsync) - { - return base.Optional_navigation_type_compensation_works_with_predicate_negated(isAsync); - } - - [ConditionalTheory(Skip = "issue #16963")] - public override Task String_compare_with_null_conditional_argument(bool isAsync) - { - return base.String_compare_with_null_conditional_argument(isAsync); - } - - [ConditionalTheory(Skip = "issue #16963")] - public override Task Optional_navigation_type_compensation_works_with_predicate2(bool isAsync) - { - return base.Optional_navigation_type_compensation_works_with_predicate2(isAsync); - } - - [ConditionalTheory(Skip = "issue #16963")] - public override Task Where_required_navigation_on_derived_type(bool isAsync) - { - return base.Where_required_navigation_on_derived_type(isAsync); - } - - [ConditionalTheory(Skip = "issue #16963")] - public override Task Optional_navigation_type_compensation_works_with_binary_and_expression(bool isAsync) - { - return base.Optional_navigation_type_compensation_works_with_binary_and_expression(isAsync); - } } } diff --git a/test/EFCore.InMemory.FunctionalTests/Query/IncludeAsyncInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/IncludeAsyncInMemoryTest.cs index 1c7805e6eba..59496e945dd 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/IncludeAsyncInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/IncludeAsyncInMemoryTest.cs @@ -12,8 +12,5 @@ public IncludeAsyncInMemoryTest(IncludeInMemoryFixture fixture) : base(fixture) { } - - [ConditionalFact(Skip = "Issue #16963")] - public override Task Include_collection_with_client_filter() => null; } } diff --git a/test/EFCore.InMemory.FunctionalTests/Query/IncludeInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/IncludeInMemoryTest.cs index ebd8b21042f..a76a6fbae84 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/IncludeInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/IncludeInMemoryTest.cs @@ -14,12 +14,7 @@ public IncludeInMemoryTest(IncludeInMemoryFixture fixture, ITestOutputHelper tes //TestLoggerFactory.TestOutputHelper = testOutputHelper; } - [ConditionalTheory(Skip = "Issue #16963")] - public override void Include_collection_with_client_filter(bool useString) - { - } - - [ConditionalTheory(Skip = "Issue #16963")] + [ConditionalTheory(Skip = "Issue#17386")] public override void Include_collection_with_last_no_orderby(bool useString) { base.Include_collection_with_last_no_orderby(useString); diff --git a/test/EFCore.InMemory.FunctionalTests/Query/InheritanceInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/InheritanceInMemoryTest.cs index 6411050ba02..c1042ddbe7b 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/InheritanceInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/InheritanceInMemoryTest.cs @@ -14,12 +14,7 @@ public InheritanceInMemoryTest(InheritanceInMemoryFixture fixture, ITestOutputHe //TestLoggerFactory.TestOutputHelper = testOutputHelper; } - [ConditionalFact(Skip = "Issue #16963")] - public override void Subquery_OfType() - { - } - - [ConditionalFact(Skip = "See issue#13857")] // Defining query + [ConditionalFact(Skip = "See issue#16963 Cannot compose when using client method in defining query")] // Defining query public override void Can_query_all_animal_views() { } diff --git a/test/EFCore.InMemory.FunctionalTests/Query/QueryBugsInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/QueryBugsInMemoryTest.cs index 1fbefc28c57..2a8ff48b6cc 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/QueryBugsInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/QueryBugsInMemoryTest.cs @@ -14,8 +14,7 @@ // ReSharper disable InconsistentNaming namespace Microsoft.EntityFrameworkCore.Query { - // Issue #16963 - internal class QueryBugsInMemoryTest : IClassFixture + public class QueryBugsInMemoryTest : IClassFixture { #region Bug9849 @@ -151,7 +150,7 @@ public class Motor #region Bug3595 - [ConditionalFact] + [ConditionalFact(Skip = "Issue#16963 groupBy")] public void GroupBy_with_uninitialized_datetime_projection_3595() { using (CreateScratch(Seed3595, "3595")) diff --git a/test/EFCore.InMemory.FunctionalTests/Query/QueryNavigationsInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/QueryNavigationsInMemoryTest.cs index 35b19881983..8aa45cc98ba 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/QueryNavigationsInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/QueryNavigationsInMemoryTest.cs @@ -17,81 +17,49 @@ public QueryNavigationsInMemoryTest( //TestLoggerFactory.TestOutputHelper = testOutputHelper; } - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Where_subquery_on_navigation_client_eval(bool isAsync) => null; + #region SingleResultProjection - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Select_Where_Navigation_Client(bool isAsync) => null; - - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Join_with_nav_in_predicate_in_subquery_when_client_eval(bool isAsync) => null; - - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Join_with_nav_projected_in_subquery_when_client_eval(bool isAsync) => null; - - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Join_with_nav_in_orderby_in_subquery_when_client_eval(bool isAsync) => null; - - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Collection_select_nav_prop_all_client(bool isAsync) => null; - - [ConditionalTheory(Skip = "Issue #16963")] - public override void Collection_where_nav_prop_all_client() - { - } - - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Select_collection_navigation_simple(bool isAsync) => null; - - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Select_collection_navigation_simple_followed_by_ordering_by_scalar(bool isAsync) => null; - - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Select_collection_navigation_multi_part(bool isAsync) => null; - - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Select_collection_navigation_multi_part2(bool isAsync) => null; - - [ConditionalTheory(Skip = "Issue #16963")] public override Task Collection_select_nav_prop_first_or_default(bool isAsync) { - return base.Collection_select_nav_prop_first_or_default(isAsync); + return Task.CompletedTask; } - [ConditionalTheory(Skip = "Issue #16963")] public override Task Collection_select_nav_prop_first_or_default_then_nav_prop(bool isAsync) { - return base.Collection_select_nav_prop_first_or_default_then_nav_prop(isAsync); + return Task.CompletedTask; } - [ConditionalTheory(Skip = "Issue #16963")] public override Task Project_single_entity_value_subquery_works(bool isAsync) { - return base.Project_single_entity_value_subquery_works(isAsync); + return Task.CompletedTask; } - [ConditionalTheory(Skip = "Issue #16963")] public override Task Select_collection_FirstOrDefault_project_anonymous_type(bool isAsync) { - return base.Select_collection_FirstOrDefault_project_anonymous_type(isAsync); + return Task.CompletedTask; } - [ConditionalTheory(Skip = "Issue #16963")] public override Task Select_collection_FirstOrDefault_project_entity(bool isAsync) { - return base.Select_collection_FirstOrDefault_project_entity(isAsync); + return Task.CompletedTask; } - [ConditionalTheory(Skip = "Issue #16963")] public override Task Skip_Select_Navigation(bool isAsync) { - return base.Skip_Select_Navigation(isAsync); + return Task.CompletedTask; } - [ConditionalTheory(Skip = "Issue #16963")] public override Task Take_Select_Navigation(bool isAsync) { - return base.Take_Select_Navigation(isAsync); + return Task.CompletedTask; + } + + #endregion + + [ConditionalTheory(Skip = "Issue#17386")] + public override Task Where_subquery_on_navigation_client_eval(bool isAsync) + { + return Task.CompletedTask; } } } diff --git a/test/EFCore.InMemory.FunctionalTests/Query/SimpleQueryInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/SimpleQueryInMemoryTest.cs index 9f3c9875f83..b701e4bfdee 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/SimpleQueryInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/SimpleQueryInMemoryTest.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Runtime.InteropServices.WindowsRuntime; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore.TestUtilities; using Xunit; @@ -21,25 +22,15 @@ public SimpleQueryInMemoryTest( //TestLoggerFactory.TestOutputHelper = testOutputHelper; } - [ConditionalFact(Skip = "See issue#13857")] - public override void Auto_initialized_view_set() - { - } - - [ConditionalTheory(Skip = "See issue#13857")] - public override Task KeylessEntity_simple(bool isAsync) => null; - - [ConditionalTheory(Skip = "See issue#13857")] - public override Task KeylessEntity_where_simple(bool isAsync) => null; - - [ConditionalFact(Skip = "See issue#13857")] - public override void KeylessEntity_by_database_view() + // InMemory can throw server side exception + public override void Average_no_data_subquery() { + Assert.Throws(() => base.Average_no_data_subquery()); } - // InMemory can mimic throw behavior for subquery - public override void Average_no_data_subquery() + public override void Max_no_data_subquery() { + Assert.Throws(() => base.Max_no_data_subquery()); } public override void Min_no_data_subquery() @@ -47,387 +38,427 @@ public override void Min_no_data_subquery() Assert.Throws(() => base.Min_no_data_subquery()); } - public override void Max_no_data_subquery() + public override Task Where_query_composition_entity_equality_one_element_Single(bool isAsync) { - Assert.Throws(() => base.Max_no_data_subquery()); + return Assert.ThrowsAsync(() => base.Where_query_composition_entity_equality_one_element_Single(isAsync)); } - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Concat(bool isAsync) => null; - - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Concat_nested(bool isAsync) => null; - - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Concat_non_entity(bool isAsync) => null; - - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Except(bool isAsync) => null; - - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Except_nested(bool isAsync) => null; - - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Except_non_entity(bool isAsync) => null; - - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Except_simple_followed_by_projecting_constant(bool isAsync) => null; - - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Intersect(bool isAsync) => null; - - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Intersect_nested(bool isAsync) => null; - - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Intersect_non_entity(bool isAsync) => null; - - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Union_Intersect(bool isAsync) => null; - - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Union(bool isAsync) => null; - - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Union_Include(bool isAsync) => null; - - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Union_nested(bool isAsync) => null; - - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Union_OrderBy_Skip_Take(bool isAsync) => null; - - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Union_Select(bool isAsync) => null; - - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Union_Skip_Take_OrderBy_ThenBy_Where(bool isAsync) => null; - - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Union_Take_Union_Take(bool isAsync) => null; - - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Union_Union(bool isAsync) => null; - - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Union_Where(bool isAsync) => null; - - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Union_with_anonymous_type_projection(bool isAsync) => null; - - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Client_eval_Union_FirstOrDefault(bool isAsync) => null; - - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Include_Union(bool isAsync) => null; - - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Select_Union(bool isAsync) => null; - - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Select_Union_different_fields_in_anonymous_with_subquery(bool isAsync) => null; - - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Select_Union_unrelated(bool isAsync) => null; - - [ConditionalTheory(Skip = "Issue #16963")] - public override Task SubSelect_Union(bool isAsync) => null; - - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Select_Except_reference_projection(bool isAsync) => null; - - [ConditionalTheory(Skip = "Issue #16963")] - public override Task GroupBy_Select_Union(bool isAsync) => null; - - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Union_over_different_projection_types(bool isAsync, string leftType, string rightType) => null; - - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Include_with_orderby_skip_preserves_ordering(bool isAsync) => null; - - [ConditionalFact(Skip = "Issue #16963")] - public override void Select_nested_collection_multi_level() + public override Task Where_query_composition_entity_equality_one_element_First(bool isAsync) { + return Assert.ThrowsAsync(() => base.Where_query_composition_entity_equality_one_element_First(isAsync)); } - [ConditionalTheory(Skip = "Issue #16963")] - public override Task DefaultIfEmpty_in_subquery(bool isAsync) => null; - - [ConditionalTheory(Skip = "Issue #16963")] - public override Task DefaultIfEmpty_in_subquery_nested(bool isAsync) => null; - - [ConditionalTheory(Skip = "Issue #16963")] - public override Task DefaultIfEmpty_in_subquery_not_correlated(bool isAsync) => null; + public override Task Where_query_composition_entity_equality_no_elements_Single(bool isAsync) + { + return Assert.ThrowsAsync(() => base.Where_query_composition_entity_equality_no_elements_Single(isAsync)); + } - [ConditionalFact(Skip = "Issue #16963")] - public override void DefaultIfEmpty_without_group_join() + public override Task Where_query_composition_entity_equality_no_elements_First(bool isAsync) { + return Assert.ThrowsAsync(() => base.Where_query_composition_entity_equality_no_elements_First(isAsync)); } - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Default_if_empty_top_level(bool isAsync) => null; + public override Task Where_query_composition_entity_equality_multiple_elements_SingleOrDefault(bool isAsync) + { + return Assert.ThrowsAsync(() => base.Where_query_composition_entity_equality_multiple_elements_SingleOrDefault(isAsync)); + } - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Default_if_empty_top_level_followed_by_projecting_constant(bool isAsync) => null; + public override Task Where_query_composition_entity_equality_multiple_elements_Single(bool isAsync) + { + return Assert.ThrowsAsync(() => base.Where_query_composition_entity_equality_multiple_elements_Single(isAsync)); + } - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Default_if_empty_top_level_positive(bool isAsync) => null; + // Sending client code to server + [ConditionalFact(Skip = "Issue#17050")] + public override void Client_code_using_instance_in_anonymous_type() + { + base.Client_code_using_instance_in_anonymous_type(); + } - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Default_if_empty_top_level_projection(bool isAsync) => null; + [ConditionalFact(Skip = "Issue#17050")] + public override void Client_code_using_instance_in_static_method() + { + base.Client_code_using_instance_in_static_method(); + } - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Join_with_default_if_empty_on_both_sources(bool isAsync) => null; + [ConditionalFact(Skip = "Issue#17050")] + public override void Client_code_using_instance_method_throws() + { + base.Client_code_using_instance_method_throws(); + } - [ConditionalFact(Skip = "Issue #16963")] - public override void OfType_Select() + #region Set Operations + public override Task Concat(bool isAsync) { + return Task.CompletedTask; } - [ConditionalFact(Skip = "Issue #16963")] - public override void OfType_Select_OfType_Select() + public override Task Concat_nested(bool isAsync) { + return Task.CompletedTask; } - [ConditionalTheory(Skip = "Issue#16575")] - public override Task Project_single_element_from_collection_with_OrderBy_Distinct_and_FirstOrDefault_followed_by_projecting_length(bool isAsync) => null; + public override Task Concat_non_entity(bool isAsync) + { + return Task.CompletedTask; + } - [ConditionalTheory(Skip = "Issue #16963")] - public override Task KeylessEntity_with_included_nav(bool isAsync) => null; + public override Task Except(bool isAsync) + { + return Task.CompletedTask; + } - [ConditionalTheory(Skip = "Issue #16963")] - public override Task KeylessEntity_with_included_navs_multi_level(bool isAsync) => null; + public override Task Except_nested(bool isAsync) + { + return Task.CompletedTask; + } - [ConditionalTheory(Skip = "Issue#17050")] - public override void Client_code_using_instance_in_static_method() + public override Task Except_non_entity(bool isAsync) { + return Task.CompletedTask; } - [ConditionalTheory(Skip = "Issue#17050")] - public override void Client_code_using_instance_method_throws() + public override Task Except_simple_followed_by_projecting_constant(bool isAsync) { + return Task.CompletedTask; } - [ConditionalTheory(Skip = "Issue#17050")] - public override void Client_code_using_instance_in_anonymous_type() + public override Task Intersect(bool isAsync) { + return Task.CompletedTask; } - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Multiple_select_many_with_predicate(bool isAsync) => null; + public override Task Intersect_nested(bool isAsync) + { + return Task.CompletedTask; + } - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Select_DTO_with_member_init_distinct_in_subquery_translated_to_server(bool isAsync) => null; + public override Task Intersect_non_entity(bool isAsync) + { + return Task.CompletedTask; + } - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Select_DTO_with_member_init_distinct_in_subquery_translated_to_server_2(bool isAsync) => null; + public override Task Union(bool isAsync) + { + return Task.CompletedTask; + } - [ConditionalTheory(Skip = "Issue #16963")] - public override Task SelectMany_correlated_subquery_simple(bool isAsync) => null; + public override Task Union_Include(bool isAsync) + { + return Task.CompletedTask; + } - [ConditionalTheory(Skip = "Issue #16963")] - public override Task SelectMany_Joined(bool isAsync) => null; + public override Task Union_Intersect(bool isAsync) + { + return Task.CompletedTask; + } - [ConditionalTheory(Skip = "Issue #16963")] - public override Task SelectMany_Joined_DefaultIfEmpty(bool isAsync) => null; + public override Task Union_nested(bool isAsync) + { + return Task.CompletedTask; + } - [ConditionalTheory(Skip = "Issue #16963")] - public override Task SelectMany_Joined_DefaultIfEmpty2(bool isAsync) => null; + public override void Union_non_entity(bool isAsync) + { + } - [ConditionalTheory(Skip = "Issue #16963")] - public override Task SelectMany_Joined_Take(bool isAsync) => null; + public override Task Union_OrderBy_Skip_Take(bool isAsync) + { + return Task.CompletedTask; + } - [ConditionalTheory(Skip = "Issue #16963")] - public override Task SelectMany_without_result_selector_naked_collection_navigation(bool isAsync) => null; + public override Task Union_over_different_projection_types(bool isAsync, string leftType, string rightType) + { + return Task.CompletedTask; + } - [ConditionalTheory(Skip = "Issue #16963")] - public override Task SelectMany_without_result_selector_collection_navigation_composed(bool isAsync) => null; + public override Task Union_Select(bool isAsync) + { + return Task.CompletedTask; + } - [ConditionalTheory(Skip = "Issue #16963")] - public override Task SelectMany_correlated_with_outer_1(bool isAsync) => null; + public override Task Union_Skip_Take_OrderBy_ThenBy_Where(bool isAsync) + { + return Task.CompletedTask; + } - [ConditionalTheory(Skip = "Issue #16963")] - public override Task SelectMany_correlated_with_outer_2(bool isAsync) => null; + public override Task Union_Take_Union_Take(bool isAsync) + { + return Task.CompletedTask; + } - [ConditionalTheory(Skip = "Issue #16963")] - public override Task SelectMany_correlated_with_outer_3(bool isAsync) => null; + public override Task Union_Union(bool isAsync) + { + return Task.CompletedTask; + } - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Contains_with_local_tuple_array_closure(bool isAsync) => null; + public override Task Union_Where(bool isAsync) + { + return Task.CompletedTask; + } - [ConditionalTheory(Skip = "Issue #16963")] - public override Task OrderBy_Count_with_predicate_client_eval(bool isAsync) => null; + public override Task Union_with_anonymous_type_projection(bool isAsync) + { + return Task.CompletedTask; + } - [ConditionalTheory(Skip = "Issue #16963")] - public override Task OrderBy_Count_with_predicate_client_eval_mixed(bool isAsync) => null; + public override Task Include_Union(bool isAsync) + { + return Task.CompletedTask; + } - [ConditionalTheory(Skip = "Issue #16963")] - public override Task OrderBy_Where_Count_client_eval(bool isAsync) => null; + public override Task Client_eval_Union_FirstOrDefault(bool isAsync) + { + return Task.CompletedTask; + } - [ConditionalTheory(Skip = "Issue #16963")] - public override Task OrderBy_Where_Count_client_eval_mixed(bool isAsync) => null; + public override Task GroupBy_Select_Union(bool isAsync) + { + return Task.CompletedTask; + } - [ConditionalTheory(Skip = "Issue #16963")] - public override Task OrderBy_Where_Count_with_predicate_client_eval(bool isAsync) => null; + public override void Include_Union_different_includes_throws() + { + } - [ConditionalTheory(Skip = "Issue #16963")] - public override Task OrderBy_Where_Count_with_predicate_client_eval_mixed(bool isAsync) => null; + public override void Include_Union_only_on_one_side_throws() + { + } - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Where_OrderBy_Count_client_eval(bool isAsync) => null; + public override Task Select_Union(bool isAsync) + { + return Task.CompletedTask; + } - [ConditionalTheory(Skip = "Issue #16963")] - public override Task SelectMany_correlated_with_outer_4(bool isAsync) => null; + public override Task Select_Union_different_fields_in_anonymous_with_subquery(bool isAsync) + { + return Task.CompletedTask; + } - [ConditionalTheory(Skip = "Issue #16963")] - public override Task All_client(bool isAsync) => null; + public override Task Select_Union_unrelated(bool isAsync) + { + return Task.CompletedTask; + } - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Client_OrderBy_GroupBy_Group_ordering_works(bool isAsync) => null; + public override Task SubSelect_Union(bool isAsync) + { + return Task.CompletedTask; + } - [ConditionalTheory(Skip = "Issue #16963")] - public override Task First_client_predicate(bool isAsync) => null; + public override Task Select_Except_reference_projection(bool isAsync) + { + return Task.CompletedTask; + } - [ConditionalTheory(Skip = "Issue #16963")] - public override Task OrderBy_client_mixed(bool isAsync) => null; + #endregion - [ConditionalTheory(Skip = "Issue #16963")] - public override Task OrderBy_multiple_queries(bool isAsync) => null; + [ConditionalTheory(Skip = "Issue#17386")] + public override Task Contains_with_local_tuple_array_closure(bool isAsync) + { + return Task.CompletedTask; + } - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Projection_when_arithmetic_mixed_subqueries(bool isAsync) => null; + [ConditionalTheory(Skip = "Issue#17386")] + public override Task Last_when_no_order_by(bool isAsync) + { + return Task.CompletedTask; + } - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Queryable_reprojection(bool isAsync) => null; + [ConditionalTheory(Skip = "Issue#17386")] + public override Task OrderBy_multiple_queries(bool isAsync) + { + return Task.CompletedTask; + } - [ConditionalFact(Skip = "Issue #16963")] + [ConditionalFact(Skip = "Issue#17386")] public override void Random_next_is_not_funcletized_1() { + base.Random_next_is_not_funcletized_1(); } - [ConditionalFact(Skip = "Issue #16963")] + [ConditionalFact(Skip = "Issue#17386")] public override void Random_next_is_not_funcletized_2() { + base.Random_next_is_not_funcletized_2(); } - [ConditionalFact(Skip = "Issue #16963")] + [ConditionalFact(Skip = "Issue#17386")] public override void Random_next_is_not_funcletized_3() { + base.Random_next_is_not_funcletized_3(); } - [ConditionalFact(Skip = "Issue #16963")] + [ConditionalFact(Skip = "Issue#17386")] public override void Random_next_is_not_funcletized_4() { + base.Random_next_is_not_funcletized_4(); } - [ConditionalFact(Skip = "Issue #16963")] + [ConditionalFact(Skip = "Issue#17386")] public override void Random_next_is_not_funcletized_5() { + base.Random_next_is_not_funcletized_5(); } - [ConditionalFact(Skip = "Issue #16963")] + [ConditionalFact(Skip = "Issue#17386")] public override void Random_next_is_not_funcletized_6() { + base.Random_next_is_not_funcletized_6(); } + [ConditionalTheory(Skip = "Issue#17386")] + public override Task Select_bool_closure_with_order_by_property_with_cast_to_nullable(bool isAsync) + { + return Task.CompletedTask; + } - [ConditionalTheory(Skip = "Issue#16963")] - public override Task Projection_when_client_evald_subquery(bool isAsync) + [ConditionalTheory(Skip = "Issue#17386")] + public override Task Where_bool_client_side_negated(bool isAsync) { - return base.Projection_when_client_evald_subquery(isAsync); + return base.Where_bool_client_side_negated(isAsync); } - [ConditionalTheory(Skip = "Issue #16963")] - public override Task SelectMany_after_client_method(bool isAsync) => null; + [ConditionalTheory(Skip = "Issue#17386")] + public override Task Projection_when_arithmetic_mixed_subqueries(bool isAsync) + { + return base.Projection_when_arithmetic_mixed_subqueries(isAsync); + } - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Where_bool_client_side_negated(bool isAsync) => null; + #region DefaultIfEmpty - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Where_client(bool isAsync) => null; + public override Task DefaultIfEmpty_in_subquery(bool isAsync) + { + return Task.CompletedTask; + } - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Where_client_and_server_non_top_level(bool isAsync) => null; + public override Task DefaultIfEmpty_in_subquery_nested(bool isAsync) + { + return Task.CompletedTask; + } - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Where_client_and_server_top_level(bool isAsync) => null; + public override Task DefaultIfEmpty_in_subquery_not_correlated(bool isAsync) + { + return Task.CompletedTask; + } - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Where_client_deep_inside_predicate_and_server_top_level(bool isAsync) => null; + public override void DefaultIfEmpty_without_group_join() + { + } - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Where_client_or_server_top_level(bool isAsync) => null; + public override Task Default_if_empty_top_level(bool isAsync) + { + return Task.CompletedTask; + } - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Where_query_composition3(bool isAsync) => null; + public override Task Default_if_empty_top_level_followed_by_projecting_constant(bool isAsync) + { + return Task.CompletedTask; + } - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Where_query_composition4(bool isAsync) => null; + public override Task Default_if_empty_top_level_positive(bool isAsync) + { + return Task.CompletedTask; + } - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Where_query_composition5(bool isAsync) => null; + public override Task Default_if_empty_top_level_projection(bool isAsync) + { + return Task.CompletedTask; + } - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Where_query_composition6(bool isAsync) => null; + public override Task Join_with_default_if_empty_on_both_sources(bool isAsync) + { + return Task.CompletedTask; + } + #endregion - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Where_subquery_correlated_client_eval(bool isAsync) => null; + #region SelectMany - [ConditionalTheory(Skip = "Issue #16963")] - public override Task All_client_and_server_top_level(bool isAsync) => null; + public override Task Multiple_select_many_with_predicate(bool isAsync) + { + return Task.CompletedTask; + } - [ConditionalTheory(Skip = "Issue #16963")] - public override Task All_client_or_server_top_level(bool isAsync) => null; + public override Task SelectMany_Joined(bool isAsync) + { + return Task.CompletedTask; + } - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Select_bool_closure_with_order_by_property_with_cast_to_nullable(bool isAsync) => null; + public override Task SelectMany_Joined_DefaultIfEmpty(bool isAsync) + { + return Task.CompletedTask; + } - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Where_query_composition_entity_equality_one_element_Single(bool isAsync) + public override Task SelectMany_Joined_DefaultIfEmpty2(bool isAsync) { - return base.Where_query_composition_entity_equality_one_element_Single(isAsync); + return Task.CompletedTask; } - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Where_query_composition_entity_equality_one_element_First(bool isAsync) + public override Task SelectMany_Joined_Take(bool isAsync) { - return base.Where_query_composition_entity_equality_one_element_First(isAsync); + return Task.CompletedTask; } - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Where_query_composition_entity_equality_no_elements_Single(bool isAsync) + public override Task SelectMany_correlated_subquery_simple(bool isAsync) { - return base.Where_query_composition_entity_equality_no_elements_Single(isAsync); + return Task.CompletedTask; } - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Where_query_composition_entity_equality_no_elements_First(bool isAsync) + public override Task SelectMany_correlated_with_outer_1(bool isAsync) { - return base.Where_query_composition_entity_equality_no_elements_First(isAsync); + return Task.CompletedTask; } - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Where_query_composition_entity_equality_multiple_elements_SingleOrDefault(bool isAsync) + public override Task SelectMany_correlated_with_outer_2(bool isAsync) { - return base.Where_query_composition_entity_equality_multiple_elements_SingleOrDefault(isAsync); + return Task.CompletedTask; } - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Where_query_composition_entity_equality_multiple_elements_Single(bool isAsync) + public override Task SelectMany_correlated_with_outer_3(bool isAsync) { - return base.Where_query_composition_entity_equality_multiple_elements_Single(isAsync); + return Task.CompletedTask; } - [ConditionalTheory(Skip = "Issue #16963")] - public override Task Last_when_no_order_by(bool isAsync) + public override Task SelectMany_correlated_with_outer_4(bool isAsync) + { + return Task.CompletedTask; + } + + public override Task SelectMany_without_result_selector_collection_navigation_composed(bool isAsync) + { + return Task.CompletedTask; + } + + public override Task SelectMany_without_result_selector_naked_collection_navigation(bool isAsync) { - return base.Last_when_no_order_by(isAsync); + return Task.CompletedTask; } - [ConditionalTheory(Skip = "Issue #16963")] + public override Task Select_DTO_with_member_init_distinct_in_subquery_translated_to_server(bool isAsync) + { + return Task.CompletedTask; + } + + public override Task Select_DTO_with_member_init_distinct_in_subquery_translated_to_server_2(bool isAsync) + { + return Task.CompletedTask; + } + + #endregion + + #region SingleResultProjection public override Task Project_single_element_from_collection_with_OrderBy_over_navigation_Take_and_FirstOrDefault_2(bool isAsync) { - return base.Project_single_element_from_collection_with_OrderBy_over_navigation_Take_and_FirstOrDefault_2(isAsync); + return Task.CompletedTask; } + #endregion + + #region NullableError + + public override Task Project_single_element_from_collection_with_OrderBy_Distinct_and_FirstOrDefault_followed_by_projecting_length(bool isAsync) + { + return Task.CompletedTask; + } + + #endregion } } diff --git a/test/EFCore.InMemory.FunctionalTests/WithConstructorsInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/WithConstructorsInMemoryTest.cs index 167374aee45..af4cff6ce24 100644 --- a/test/EFCore.InMemory.FunctionalTests/WithConstructorsInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/WithConstructorsInMemoryTest.cs @@ -16,13 +16,6 @@ public WithConstructorsInMemoryTest(WithConstructorsInMemoryFixture fixture) { } - [ConditionalFact(Skip = "See issue#13857")] - public override void Query_with_keyless_type() - { - base.Query_with_keyless_type(); - } - - [ConditionalFact(Skip = "Issue#16963")] public override void Query_and_update_using_constructors_with_property_parameters() { base.Query_and_update_using_constructors_with_property_parameters(); From c10244115357d127c69755e9d903b151ef7e0b01 Mon Sep 17 00:00:00 2001 From: Smit Patel Date: Fri, 23 Aug 2019 16:55:35 -0700 Subject: [PATCH 08/21] InMemory: Support for non-scalar single result in projection Part of #16963 --- .../Internal/InMemoryLinqOperatorProvider.cs | 7 +++ ...emoryProjectionBindingExpressionVisitor.cs | 8 ++- .../Query/Internal/InMemoryQueryExpression.cs | 12 ++++- ...yableMethodTranslatingExpressionVisitor.cs | 27 +++++----- ....CustomShaperCompilingExpressionVisitor.cs | 22 ++++++++ ...erExpressionProcessingExpressionVisitor.cs | 21 ++++++++ .../Internal/SingleResultShaperExpression.cs | 54 +++++++++++++++++++ .../Query/QueryNavigationsInMemoryTest.cs | 39 -------------- .../Query/SimpleQueryInMemoryTest.cs | 7 --- 9 files changed, 135 insertions(+), 62 deletions(-) create mode 100644 src/EFCore.InMemory/Query/Internal/SingleResultShaperExpression.cs diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryLinqOperatorProvider.cs b/src/EFCore.InMemory/Query/Internal/InMemoryLinqOperatorProvider.cs index 1123e91f34f..1d7b234eb7b 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryLinqOperatorProvider.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryLinqOperatorProvider.cs @@ -52,6 +52,13 @@ private static IEnumerable GetMethods(string name, int parameterCoun public static MethodInfo SinglePredicate = GetMethod(nameof(Enumerable.Single), 1); public static MethodInfo SingleOrDefaultPredicate = GetMethod(nameof(Enumerable.SingleOrDefault), 1); + public static MethodInfo First = GetMethod(nameof(Enumerable.First), 0); + public static MethodInfo FirstOrDefault = GetMethod(nameof(Enumerable.FirstOrDefault), 0); + public static MethodInfo Last = GetMethod(nameof(Enumerable.Last), 0); + public static MethodInfo LastOrDefault = GetMethod(nameof(Enumerable.LastOrDefault), 0); + public static MethodInfo Single = GetMethod(nameof(Enumerable.Single), 0); + public static MethodInfo SingleOrDefault = GetMethod(nameof(Enumerable.SingleOrDefault), 0); + public static MethodInfo GetAggregateMethod(string methodName, Type elementType, int parameterCount = 0) { Check.NotEmpty(methodName, nameof(methodName)); diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryProjectionBindingExpressionVisitor.cs b/src/EFCore.InMemory/Query/Internal/InMemoryProjectionBindingExpressionVisitor.cs index f4725f2a962..6456db627cd 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryProjectionBindingExpressionVisitor.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryProjectionBindingExpressionVisitor.cs @@ -113,7 +113,13 @@ public override Expression Visit(Expression expression) return AddCollectionProjection(subquery, null, subquery.ShaperExpression.Type); } - throw new InvalidOperationException(CoreStrings.QueryFailed(methodCallExpression.Print(), GetType().Name)); + return new SingleResultShaperExpression( + new ProjectionBindingExpression( + _queryExpression, + _queryExpression.AddSubqueryProjection(subquery, out var innerShaper), + typeof(ValueBuffer)), + innerShaper, + subquery.ShaperExpression.Type); } break; diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs b/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs index a9942db5a81..a4858168717 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs @@ -131,13 +131,23 @@ public virtual int AddSubqueryProjection(ShapedQueryExpression shapedQueryExpres { var subquery = (InMemoryQueryExpression)shapedQueryExpression.QueryExpression; subquery.ApplyProjection(); + var serverQueryExpression = subquery.ServerQueryExpression; + + if (serverQueryExpression is MethodCallExpression selectMethodCall + && selectMethodCall.Arguments[0].Type == typeof(ResultEnumerable)) + { + var terminatingMethodCall = (MethodCallExpression)((LambdaExpression)((NewExpression)selectMethodCall.Arguments[0]).Arguments[0]).Body; + selectMethodCall = selectMethodCall.Update( + null, new[] { terminatingMethodCall.Arguments[0], selectMethodCall.Arguments[1] }); + serverQueryExpression = terminatingMethodCall.Update(null, new[] { selectMethodCall }); + } innerShaper = new ShaperRemappingExpressionVisitor(subquery._projectionMapping) .Visit(shapedQueryExpression.ShaperExpression); innerShaper = Lambda(innerShaper, subquery.ValueBufferParameter); - return AddToProjection(subquery.ServerQueryExpression); + return AddToProjection(serverQueryExpression); } private class ShaperRemappingExpressionVisitor : ExpressionVisitor diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs index 1446defa48d..bcd471504ca 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs @@ -212,8 +212,8 @@ protected override ShapedQueryExpression TranslateFirstOrDefault(ShapedQueryExpr predicate, returnType, returnDefault - ? InMemoryLinqOperatorProvider.FirstOrDefaultPredicate - : InMemoryLinqOperatorProvider.FirstPredicate); + ? InMemoryLinqOperatorProvider.FirstOrDefault + : InMemoryLinqOperatorProvider.First); } protected override ShapedQueryExpression TranslateGroupBy(ShapedQueryExpression source, LambdaExpression keySelector, LambdaExpression elementSelector, LambdaExpression resultSelector) @@ -258,8 +258,8 @@ protected override ShapedQueryExpression TranslateLastOrDefault(ShapedQueryExpre predicate, returnType, returnDefault - ? InMemoryLinqOperatorProvider.LastOrDefaultPredicate - : InMemoryLinqOperatorProvider.LastPredicate); + ? InMemoryLinqOperatorProvider.LastOrDefault + : InMemoryLinqOperatorProvider.Last); } protected override ShapedQueryExpression TranslateLeftJoin(ShapedQueryExpression outer, ShapedQueryExpression inner, LambdaExpression outerKeySelector, LambdaExpression innerKeySelector, LambdaExpression resultSelector) @@ -521,8 +521,8 @@ protected override ShapedQueryExpression TranslateSingleOrDefault(ShapedQueryExp predicate, returnType, returnDefault - ? InMemoryLinqOperatorProvider.SingleOrDefaultPredicate - : InMemoryLinqOperatorProvider.SinglePredicate); + ? InMemoryLinqOperatorProvider.SingleOrDefault + : InMemoryLinqOperatorProvider.Single); } protected override ShapedQueryExpression TranslateSkip(ShapedQueryExpression source, Expression count) @@ -662,20 +662,19 @@ private ShapedQueryExpression TranslateSingleResultOperator( { var inMemoryQueryExpression = (InMemoryQueryExpression)source.QueryExpression; - predicate = predicate == null - ? Expression.Lambda(Expression.Constant(true), Expression.Parameter(typeof(ValueBuffer))) - : TranslateLambdaExpression(source, predicate); - - if (predicate == null) + if (predicate != null) { - return null; + source = TranslateWhere(source, predicate); + if (source == null) + { + return null; + } } inMemoryQueryExpression.ServerQueryExpression = Expression.Call( method.MakeGenericMethod(typeof(ValueBuffer)), - inMemoryQueryExpression.ServerQueryExpression, - predicate); + inMemoryQueryExpression.ServerQueryExpression); inMemoryQueryExpression.ConvertToEnumerable(); diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryShapedQueryCompilingExpressionVisitor.CustomShaperCompilingExpressionVisitor.cs b/src/EFCore.InMemory/Query/Internal/InMemoryShapedQueryCompilingExpressionVisitor.CustomShaperCompilingExpressionVisitor.cs index b9c802d8e44..7160de6c6a4 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryShapedQueryCompilingExpressionVisitor.CustomShaperCompilingExpressionVisitor.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryShapedQueryCompilingExpressionVisitor.CustomShaperCompilingExpressionVisitor.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Linq.Expressions; using System.Reflection; @@ -36,6 +37,10 @@ private static readonly MethodInfo _materializeCollectionMethodInfo = typeof(CustomShaperCompilingExpressionVisitor).GetTypeInfo() .GetDeclaredMethod(nameof(MaterializeCollection)); + private static readonly MethodInfo _materializeSingleResultMethodInfo + = typeof(CustomShaperCompilingExpressionVisitor).GetTypeInfo() + .GetDeclaredMethod(nameof(MaterializeSingleResult)); + private static void SetIsLoadedNoTracking(object entity, INavigation navigation) => ((ILazyLoader)(navigation .DeclaringEntityType @@ -144,6 +149,14 @@ private static TCollection MaterializeCollection( return collection; } + private static TResult MaterializeSingleResult( + QueryContext queryContext, + ValueBuffer valueBuffer, + Func innerShaper) + => valueBuffer.IsEmpty + ? default + : innerShaper(queryContext, valueBuffer); + protected override Expression VisitExtension(Expression extensionExpression) { if (extensionExpression is IncludeExpression includeExpression) @@ -203,6 +216,15 @@ protected override Expression VisitExtension(Expression extensionExpression) typeof(IClrCollectionAccessor))); } + if (extensionExpression is SingleResultShaperExpression singleResultShaperExpression) + { + return Expression.Call( + _materializeSingleResultMethodInfo.MakeGenericMethod(singleResultShaperExpression.Type), + QueryCompilationContext.QueryContextParameter, + singleResultShaperExpression.Projection, + Expression.Constant(((LambdaExpression)Visit(singleResultShaperExpression.InnerShaper)).Compile())); + } + return base.VisitExtension(extensionExpression); } diff --git a/src/EFCore.InMemory/Query/Internal/ShaperExpressionProcessingExpressionVisitor.cs b/src/EFCore.InMemory/Query/Internal/ShaperExpressionProcessingExpressionVisitor.cs index d0a61f7bc5a..688007a71c2 100644 --- a/src/EFCore.InMemory/Query/Internal/ShaperExpressionProcessingExpressionVisitor.cs +++ b/src/EFCore.InMemory/Query/Internal/ShaperExpressionProcessingExpressionVisitor.cs @@ -119,6 +119,27 @@ protected override Expression VisitExtension(Expression extensionExpression) return variable; } + + case SingleResultShaperExpression singleResultShaperExpression: + { + var key = GenerateKey((ProjectionBindingExpression)singleResultShaperExpression.Projection); + if (!_mapping.TryGetValue(key, out var variable)) + { + var projection = Visit(singleResultShaperExpression.Projection); + + variable = Expression.Parameter(singleResultShaperExpression.Type); + _variables.Add(variable); + + var innerLambda = (LambdaExpression)singleResultShaperExpression.InnerShaper; + var innerShaper = new ShaperExpressionProcessingExpressionVisitor(null, innerLambda.Parameters[0]) + .Inject(innerLambda.Body); + + _expressions.Add(Expression.Assign(variable, singleResultShaperExpression.Update(projection, innerShaper))); + _mapping[key] = variable; + } + + return variable; + } } return base.VisitExtension(extensionExpression); diff --git a/src/EFCore.InMemory/Query/Internal/SingleResultShaperExpression.cs b/src/EFCore.InMemory/Query/Internal/SingleResultShaperExpression.cs new file mode 100644 index 00000000000..db4e3a291a1 --- /dev/null +++ b/src/EFCore.InMemory/Query/Internal/SingleResultShaperExpression.cs @@ -0,0 +1,54 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore.Query; + +namespace Microsoft.EntityFrameworkCore.InMemory.Query.Internal +{ + public class SingleResultShaperExpression : Expression, IPrintableExpression + { + public SingleResultShaperExpression( + Expression projection, + Expression innerShaper, + Type type) + { + Projection = projection; + InnerShaper = innerShaper; + Type = type; + } + + protected override Expression VisitChildren(ExpressionVisitor visitor) + { + var projection = visitor.Visit(Projection); + var innerShaper = visitor.Visit(InnerShaper); + + return Update(projection, innerShaper); + } + + public virtual SingleResultShaperExpression Update(Expression projection, Expression innerShaper) + => projection != Projection || innerShaper != InnerShaper + ? new SingleResultShaperExpression(projection, innerShaper, Type) + : this; + + public sealed override ExpressionType NodeType => ExpressionType.Extension; + public override Type Type { get; } + + public virtual Expression Projection { get; } + public virtual Expression InnerShaper { get; } + + public virtual void Print(ExpressionPrinter expressionPrinter) + { + expressionPrinter.AppendLine($"{nameof(SingleResultShaperExpression)}:"); + using (expressionPrinter.Indent()) + { + expressionPrinter.Append("("); + expressionPrinter.Visit(Projection); + expressionPrinter.Append(", "); + expressionPrinter.Visit(InnerShaper); + expressionPrinter.AppendLine($")"); + } + } + } +} diff --git a/test/EFCore.InMemory.FunctionalTests/Query/QueryNavigationsInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/QueryNavigationsInMemoryTest.cs index 8aa45cc98ba..972cd6aa1ca 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/QueryNavigationsInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/QueryNavigationsInMemoryTest.cs @@ -17,45 +17,6 @@ public QueryNavigationsInMemoryTest( //TestLoggerFactory.TestOutputHelper = testOutputHelper; } - #region SingleResultProjection - - public override Task Collection_select_nav_prop_first_or_default(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Collection_select_nav_prop_first_or_default_then_nav_prop(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Project_single_entity_value_subquery_works(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Select_collection_FirstOrDefault_project_anonymous_type(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Select_collection_FirstOrDefault_project_entity(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Skip_Select_Navigation(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Take_Select_Navigation(bool isAsync) - { - return Task.CompletedTask; - } - - #endregion - [ConditionalTheory(Skip = "Issue#17386")] public override Task Where_subquery_on_navigation_client_eval(bool isAsync) { diff --git a/test/EFCore.InMemory.FunctionalTests/Query/SimpleQueryInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/SimpleQueryInMemoryTest.cs index b701e4bfdee..04e9a2e07b1 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/SimpleQueryInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/SimpleQueryInMemoryTest.cs @@ -445,13 +445,6 @@ public override Task Select_DTO_with_member_init_distinct_in_subquery_translated #endregion - #region SingleResultProjection - public override Task Project_single_element_from_collection_with_OrderBy_over_navigation_Take_and_FirstOrDefault_2(bool isAsync) - { - return Task.CompletedTask; - } - #endregion - #region NullableError public override Task Project_single_element_from_collection_with_OrderBy_Distinct_and_FirstOrDefault_followed_by_projecting_length(bool isAsync) From b66d34fcb7613e7074175b24ba547fd02a4e168b Mon Sep 17 00:00:00 2001 From: Smit Patel Date: Mon, 26 Aug 2019 16:09:55 -0700 Subject: [PATCH 09/21] InMemory: Implement SelectMany Part of #16963 --- .../Query/Internal/InMemoryQueryExpression.cs | 21 +++- ...yableMethodTranslatingExpressionVisitor.cs | 104 +++++++++--------- .../Query/SimpleQueryInMemoryTest.cs | 50 --------- 3 files changed, 66 insertions(+), 109 deletions(-) diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs b/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs index a4858168717..da5f81b9c14 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs @@ -527,7 +527,7 @@ public virtual void AddLeftJoin( _projectionMapping = projectionMapping; } - public virtual void AddCrossJoin(InMemoryQueryExpression innerQueryExpression, Type transparentIdentifierType) + public virtual void AddSelectMany(InMemoryQueryExpression innerQueryExpression, Type transparentIdentifierType, bool innerNullable) { var outerParameter = Parameter(typeof(ValueBuffer), "outer"); var innerParameter = Parameter(typeof(ValueBuffer), "inner"); @@ -564,6 +564,7 @@ public virtual void AddCrossJoin(InMemoryQueryExpression innerQueryExpression, T } var innerMemberInfo = transparentIdentifierType.GetTypeInfo().GetDeclaredField("Inner"); + var nullableReadValueExpressionVisitor = new NullableReadValueExpressionVisitor(); foreach (var projection in innerQueryExpression._projectionMapping) { if (projection.Value is EntityProjectionExpression entityProjection) @@ -571,17 +572,27 @@ public virtual void AddCrossJoin(InMemoryQueryExpression innerQueryExpression, T var readExpressionMap = new Dictionary(); foreach (var property in GetAllPropertiesInHierarchy(entityProjection.EntityType)) { - resultValueBufferExpressions.Add(replacingVisitor.Visit(entityProjection.BindProperty(property))); - readExpressionMap[property] = CreateReadValueExpression(property.ClrType, index++, property); + var replacedExpression = replacingVisitor.Visit(entityProjection.BindProperty(property)); + if (innerNullable) + { + replacedExpression = nullableReadValueExpressionVisitor.Visit(replacedExpression); + } + resultValueBufferExpressions.Add(replacedExpression); + readExpressionMap[property] = CreateReadValueExpression(replacedExpression.Type, index++, property); } projectionMapping[projection.Key.Prepend(innerMemberInfo)] = new EntityProjectionExpression(entityProjection.EntityType, readExpressionMap); } else { - resultValueBufferExpressions.Add(replacingVisitor.Visit(projection.Value)); + var replacedExpression = replacingVisitor.Visit(projection.Value); + if (innerNullable) + { + replacedExpression = nullableReadValueExpressionVisitor.Visit(replacedExpression); + } + resultValueBufferExpressions.Add(replacedExpression); projectionMapping[projection.Key.Prepend(innerMemberInfo)] - = CreateReadValueExpression(projection.Value.Type, index++, InferPropertyFromInner(projection.Value)); + = CreateReadValueExpression(replacedExpression.Type, index++, InferPropertyFromInner(projection.Value)); } } diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs index bcd471504ca..be86e1e5367 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs @@ -438,81 +438,73 @@ protected override ShapedQueryExpression TranslateSelect(ShapedQueryExpression s return source; } - private static readonly MethodInfo _defaultIfEmptyWithoutArgMethodInfo = typeof(Enumerable).GetTypeInfo() - .GetDeclaredMethods(nameof(Enumerable.DefaultIfEmpty)).Single(mi => mi.GetParameters().Length == 1); - protected override ShapedQueryExpression TranslateSelectMany( ShapedQueryExpression source, LambdaExpression collectionSelector, LambdaExpression resultSelector) { - var collectionSelectorBody = collectionSelector.Body; - //var defaultIfEmpty = false; + var defaultIfEmpty = new DefaultIfEmptyFindingExpressionVisitor().IsOptional(collectionSelector); + var collectionSelectorBody = RemapLambdaBody(source, collectionSelector); - if (collectionSelectorBody is MethodCallExpression collectionEndingMethod - && collectionEndingMethod.Method.IsGenericMethod - && collectionEndingMethod.Method.GetGenericMethodDefinition() == _defaultIfEmptyWithoutArgMethodInfo) + if (Visit(collectionSelectorBody) is ShapedQueryExpression inner) { - //defaultIfEmpty = true; - collectionSelectorBody = collectionEndingMethod.Arguments[0]; - } - - var correlated = new CorrelationFindingExpressionVisitor().IsCorrelated(collectionSelectorBody, collectionSelector.Parameters[0]); - if (correlated) - { - // TODO visit inner with outer parameter; - // See #17236 - throw new InvalidOperationException(CoreStrings.TranslationFailed( - collectionSelector.Print() + "; " + resultSelector.Print())); - - } - else - { - if (Visit(collectionSelectorBody) is ShapedQueryExpression inner) - { - var transparentIdentifierType = TransparentIdentifierFactory.Create( - resultSelector.Parameters[0].Type, - resultSelector.Parameters[1].Type); - - ((InMemoryQueryExpression)source.QueryExpression).AddCrossJoin( - (InMemoryQueryExpression)inner.QueryExpression, transparentIdentifierType); - - return TranslateResultSelectorForJoin( - source, - resultSelector, - inner.ShaperExpression, - transparentIdentifierType); - } + var transparentIdentifierType = TransparentIdentifierFactory.Create( + resultSelector.Parameters[0].Type, + resultSelector.Parameters[1].Type); + + var innerShaperExpression = defaultIfEmpty + ? MarkShaperNullable(inner.ShaperExpression) + : inner.ShaperExpression; + + ((InMemoryQueryExpression)source.QueryExpression).AddSelectMany( + (InMemoryQueryExpression)inner.QueryExpression, transparentIdentifierType, defaultIfEmpty); + + return TranslateResultSelectorForJoin( + source, + resultSelector, + innerShaperExpression, + transparentIdentifierType); } return null; } - private class CorrelationFindingExpressionVisitor : ExpressionVisitor + private class DefaultIfEmptyFindingExpressionVisitor : ExpressionVisitor { - private ParameterExpression _outerParameter; - private bool _isCorrelated; - public bool IsCorrelated(Expression tree, ParameterExpression outerParameter) + private bool _defaultIfEmpty; + + public bool IsOptional(LambdaExpression lambdaExpression) { - _isCorrelated = false; - _outerParameter = outerParameter; + _defaultIfEmpty = false; - Visit(tree); + Visit(lambdaExpression.Body); - return _isCorrelated; + return _defaultIfEmpty; } - protected override Expression VisitParameter(ParameterExpression parameterExpression) + protected override Expression VisitMethodCall(MethodCallExpression methodCallExpression) { - if (parameterExpression == _outerParameter) + if (methodCallExpression.Method.IsGenericMethod + && methodCallExpression.Method.GetGenericMethodDefinition() == QueryableMethods.DefaultIfEmptyWithoutArgument) { - _isCorrelated = true; + _defaultIfEmpty = true; } - return base.VisitParameter(parameterExpression); + return base.VisitMethodCall(methodCallExpression); } } protected override ShapedQueryExpression TranslateSelectMany(ShapedQueryExpression source, LambdaExpression selector) - => null; + { + var innerParameter = Expression.Parameter(selector.ReturnType.TryGetSequenceType(), "i"); + var resultSelector = Expression.Lambda( + innerParameter, + new[] + { + Expression.Parameter(source.Type.TryGetSequenceType()), + innerParameter + }); + + return TranslateSelectMany(source, selector, resultSelector); + } protected override ShapedQueryExpression TranslateSingleOrDefault(ShapedQueryExpression source, LambdaExpression predicate, Type returnType, bool returnDefault) { @@ -617,9 +609,7 @@ private Expression TranslateExpression(Expression expression) private LambdaExpression TranslateLambdaExpression( ShapedQueryExpression shapedQueryExpression, LambdaExpression lambdaExpression) { - var lambdaBody = ReplacingExpressionVisitor.Replace( - lambdaExpression.Parameters.Single(), shapedQueryExpression.ShaperExpression, lambdaExpression.Body); - lambdaBody = TranslateExpression(lambdaBody); + var lambdaBody = TranslateExpression(RemapLambdaBody(shapedQueryExpression, lambdaExpression)); return lambdaBody != null ? Expression.Lambda(lambdaBody, @@ -627,6 +617,12 @@ private LambdaExpression TranslateLambdaExpression( : null; } + private static Expression RemapLambdaBody(ShapedQueryExpression shapedQueryExpression, LambdaExpression lambdaExpression) + { + return ReplacingExpressionVisitor.Replace( + lambdaExpression.Parameters.Single(), shapedQueryExpression.ShaperExpression, lambdaExpression.Body); + } + private ShapedQueryExpression TranslateScalarAggregate( ShapedQueryExpression source, LambdaExpression selector, string methodName) { diff --git a/test/EFCore.InMemory.FunctionalTests/Query/SimpleQueryInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/SimpleQueryInMemoryTest.cs index 04e9a2e07b1..4c951aac29d 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/SimpleQueryInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/SimpleQueryInMemoryTest.cs @@ -373,16 +373,6 @@ public override Task Join_with_default_if_empty_on_both_sources(bool isAsync) #region SelectMany - public override Task Multiple_select_many_with_predicate(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task SelectMany_Joined(bool isAsync) - { - return Task.CompletedTask; - } - public override Task SelectMany_Joined_DefaultIfEmpty(bool isAsync) { return Task.CompletedTask; @@ -393,26 +383,6 @@ public override Task SelectMany_Joined_DefaultIfEmpty2(bool isAsync) return Task.CompletedTask; } - public override Task SelectMany_Joined_Take(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task SelectMany_correlated_subquery_simple(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task SelectMany_correlated_with_outer_1(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task SelectMany_correlated_with_outer_2(bool isAsync) - { - return Task.CompletedTask; - } - public override Task SelectMany_correlated_with_outer_3(bool isAsync) { return Task.CompletedTask; @@ -423,26 +393,6 @@ public override Task SelectMany_correlated_with_outer_4(bool isAsync) return Task.CompletedTask; } - public override Task SelectMany_without_result_selector_collection_navigation_composed(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task SelectMany_without_result_selector_naked_collection_navigation(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Select_DTO_with_member_init_distinct_in_subquery_translated_to_server(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Select_DTO_with_member_init_distinct_in_subquery_translated_to_server_2(bool isAsync) - { - return Task.CompletedTask; - } - #endregion #region NullableError From d7359545d25de075e5e103031f10bb028c4fcec2 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Tue, 27 Aug 2019 14:24:57 +0200 Subject: [PATCH 10/21] InMemory: Set operations Part of #16963 --- .../Internal/InMemoryLinqOperatorProvider.cs | 5 + ...yableMethodTranslatingExpressionVisitor.cs | 29 +++- .../Query/SimpleQueryInMemoryTest.cs | 161 +----------------- 3 files changed, 31 insertions(+), 164 deletions(-) diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryLinqOperatorProvider.cs b/src/EFCore.InMemory/Query/Internal/InMemoryLinqOperatorProvider.cs index 1d7b234eb7b..0d588543b4a 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryLinqOperatorProvider.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryLinqOperatorProvider.cs @@ -59,6 +59,11 @@ private static IEnumerable GetMethods(string name, int parameterCoun public static MethodInfo Single = GetMethod(nameof(Enumerable.Single), 0); public static MethodInfo SingleOrDefault = GetMethod(nameof(Enumerable.SingleOrDefault), 0); + public static MethodInfo Concat = GetMethod(nameof(Enumerable.Concat), 1); + public static MethodInfo Except = GetMethod(nameof(Enumerable.Except), 1); + public static MethodInfo Intersect = GetMethod(nameof(Enumerable.Intersect), 1); + public static MethodInfo Union = GetMethod(nameof(Enumerable.Union), 1); + public static MethodInfo GetAggregateMethod(string methodName, Type elementType, int parameterCount = 0) { Check.NotEmpty(methodName, nameof(methodName)); diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs index be86e1e5367..66543828fb1 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs @@ -126,7 +126,7 @@ protected override ShapedQueryExpression TranslateCast(ShapedQueryExpression sou } protected override ShapedQueryExpression TranslateConcat(ShapedQueryExpression source1, ShapedQueryExpression source2) - => null; + => TranslateSetOperation(InMemoryLinqOperatorProvider.Concat, source1, source2); protected override ShapedQueryExpression TranslateContains(ShapedQueryExpression source, Expression item) { @@ -203,7 +203,7 @@ protected override ShapedQueryExpression TranslateElementAtOrDefault(ShapedQuery => null; protected override ShapedQueryExpression TranslateExcept(ShapedQueryExpression source1, ShapedQueryExpression source2) - => null; + => TranslateSetOperation(InMemoryLinqOperatorProvider.Except, source1, source2); protected override ShapedQueryExpression TranslateFirstOrDefault(ShapedQueryExpression source, LambdaExpression predicate, Type returnType, bool returnDefault) { @@ -223,7 +223,7 @@ protected override ShapedQueryExpression TranslateGroupJoin(ShapedQueryExpressio => null; protected override ShapedQueryExpression TranslateIntersect(ShapedQueryExpression source1, ShapedQueryExpression source2) - => null; + => TranslateSetOperation(InMemoryLinqOperatorProvider.Intersect, source1, source2); protected override ShapedQueryExpression TranslateJoin(ShapedQueryExpression outer, ShapedQueryExpression inner, LambdaExpression outerKeySelector, LambdaExpression innerKeySelector, LambdaExpression resultSelector) { @@ -582,7 +582,7 @@ protected override ShapedQueryExpression TranslateThenBy(ShapedQueryExpression s } protected override ShapedQueryExpression TranslateUnion(ShapedQueryExpression source1, ShapedQueryExpression source2) - => null; + => TranslateSetOperation(InMemoryLinqOperatorProvider.Union, source1, source2); protected override ShapedQueryExpression TranslateWhere(ShapedQueryExpression source, LambdaExpression predicate) { @@ -681,5 +681,26 @@ private ShapedQueryExpression TranslateSingleResultOperator( return source; } + + private ShapedQueryExpression TranslateSetOperation( + MethodInfo setOperationMethodInfo, + ShapedQueryExpression source1, + ShapedQueryExpression source2) + { + var inMemoryQueryExpression1 = (InMemoryQueryExpression)source1.QueryExpression; + var inMemoryQueryExpression2 = (InMemoryQueryExpression)source2.QueryExpression; + + // Apply any pending selectors, ensuring that the shape of both expressions is identical + // prior to applying the set operation. + inMemoryQueryExpression1.PushdownIntoSubquery(); + inMemoryQueryExpression2.PushdownIntoSubquery(); + + inMemoryQueryExpression1.ServerQueryExpression = Expression.Call( + setOperationMethodInfo.MakeGenericMethod(typeof(ValueBuffer)), + inMemoryQueryExpression1.ServerQueryExpression, + inMemoryQueryExpression2.ServerQueryExpression); + + return source1; + } } } diff --git a/test/EFCore.InMemory.FunctionalTests/Query/SimpleQueryInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/SimpleQueryInMemoryTest.cs index 4c951aac29d..a0d76376ddc 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/SimpleQueryInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/SimpleQueryInMemoryTest.cs @@ -87,171 +87,12 @@ public override void Client_code_using_instance_method_throws() base.Client_code_using_instance_method_throws(); } - #region Set Operations - public override Task Concat(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Concat_nested(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Concat_non_entity(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Except(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Except_nested(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Except_non_entity(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Except_simple_followed_by_projecting_constant(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Intersect(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Intersect_nested(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Intersect_non_entity(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Union(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Union_Include(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Union_Intersect(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Union_nested(bool isAsync) - { - return Task.CompletedTask; - } - - public override void Union_non_entity(bool isAsync) - { - } - - public override Task Union_OrderBy_Skip_Take(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Union_over_different_projection_types(bool isAsync, string leftType, string rightType) - { - return Task.CompletedTask; - } - - public override Task Union_Select(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Union_Skip_Take_OrderBy_ThenBy_Where(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Union_Take_Union_Take(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Union_Union(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Union_Where(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Union_with_anonymous_type_projection(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Include_Union(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Client_eval_Union_FirstOrDefault(bool isAsync) - { - return Task.CompletedTask; - } - + [ConditionalTheory(Skip = "Issue#16963 (GroupBy)")] public override Task GroupBy_Select_Union(bool isAsync) { return Task.CompletedTask; } - public override void Include_Union_different_includes_throws() - { - } - - public override void Include_Union_only_on_one_side_throws() - { - } - - public override Task Select_Union(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Select_Union_different_fields_in_anonymous_with_subquery(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Select_Union_unrelated(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task SubSelect_Union(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task Select_Except_reference_projection(bool isAsync) - { - return Task.CompletedTask; - } - - #endregion - [ConditionalTheory(Skip = "Issue#17386")] public override Task Contains_with_local_tuple_array_closure(bool isAsync) { From 1567b2b37fb98714deaaba5bf555ec422b9fd11c Mon Sep 17 00:00:00 2001 From: Brice Lambson Date: Tue, 27 Aug 2019 15:10:57 -0700 Subject: [PATCH 11/21] InMemory Spatial: Enable passing tests --- .../Query/SpatialQueryInMemoryTest.cs | 125 +++++++++++++++++- 1 file changed, 124 insertions(+), 1 deletion(-) diff --git a/test/EFCore.InMemory.FunctionalTests/Query/SpatialQueryInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/SpatialQueryInMemoryTest.cs index df6e83f98d8..a8a7f1c5219 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/SpatialQueryInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/SpatialQueryInMemoryTest.cs @@ -1,13 +1,136 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Threading.Tasks; +using Xunit; + namespace Microsoft.EntityFrameworkCore.Query { - internal class SpatialQueryInMemoryTest : SpatialQueryTestBase + public class SpatialQueryInMemoryTest : SpatialQueryTestBase { public SpatialQueryInMemoryTest(SpatialQueryInMemoryFixture fixture) : base(fixture) { } + + [ConditionalTheory(Skip = "Issue #16963. Nullable error")] + public override Task Area(bool isAsync) + => base.Area(isAsync); + + [ConditionalTheory(Skip = "Issue #16963. Nullable error")] + public override Task Boundary(bool isAsync) + => base.Boundary(isAsync); + + [ConditionalTheory(Skip = "Issue #16963. Nullable error")] + public override Task Centroid(bool isAsync) + => base.Centroid(isAsync); + + [ConditionalTheory(Skip = "Issue #16963. Nullable error")] + public override Task Dimension(bool isAsync) + => base.Dimension(isAsync); + + [ConditionalTheory(Skip = "Issue #16963. Nullable error")] + public override Task EndPoint(bool isAsync) + => base.EndPoint(isAsync); + + [ConditionalTheory(Skip = "Issue #16963. Nullable error")] + public override Task Envelope(bool isAsync) + => base.Envelope(isAsync); + + [ConditionalTheory(Skip = "Issue #16963. Nullable error")] + public override Task ExteriorRing(bool isAsync) + => base.ExteriorRing(isAsync); + + [ConditionalTheory(Skip = "Issue #16963. Nullable error")] + public override Task GeometryType(bool isAsync) + => base.GeometryType(isAsync); + + [ConditionalTheory(Skip = "Issue #16963. Nullable error")] + public override Task ICurve_IsClosed(bool isAsync) + => base.ICurve_IsClosed(isAsync); + + [ConditionalTheory(Skip = "Issue #16963. Nullable error")] + public override Task IGeometryCollection_Count(bool isAsync) + => base.IGeometryCollection_Count(isAsync); + + [ConditionalTheory(Skip = "Issue #16963. Nullable error")] + public override Task IMultiCurve_IsClosed(bool isAsync) + => base.IMultiCurve_IsClosed(isAsync); + + [ConditionalTheory(Skip = "Issue #16963. Nullable error")] + public override Task InteriorPoint(bool isAsync) + => base.InteriorPoint(isAsync); + + [ConditionalTheory(Skip = "Issue #16963. Nullable error")] + public override Task IsEmpty(bool isAsync) + => base.IsEmpty(isAsync); + + [ConditionalTheory(Skip = "Issue #16963. Nullable error")] + public override Task IsRing(bool isAsync) + => base.IsRing(isAsync); + + [ConditionalTheory(Skip = "Issue #16963. Nullable error")] + public override Task IsSimple(bool isAsync) + => base.IsSimple(isAsync); + + [ConditionalTheory(Skip = "Issue #16963. Nullable error")] + public override Task IsValid(bool isAsync) + => base.IsValid(isAsync); + + [ConditionalTheory(Skip = "Issue #16963. Nullable error")] + public override Task Length(bool isAsync) + => base.Length(isAsync); + + [ConditionalTheory(Skip = "Issue #16963. Nullable error")] + public override Task LineString_Count(bool isAsync) + => base.LineString_Count(isAsync); + + [ConditionalTheory(Skip = "Issue #16963. Nullable error")] + public override Task M(bool isAsync) + => base.M(isAsync); + + [ConditionalTheory(Skip = "Issue #16963. Nullable error")] + public override Task NumGeometries(bool isAsync) + => base.NumGeometries(isAsync); + + [ConditionalTheory(Skip = "Issue #16963. Nullable error")] + public override Task NumInteriorRings(bool isAsync) + => base.NumInteriorRings(isAsync); + + [ConditionalTheory(Skip = "Issue #16963. Nullable error")] + public override Task NumPoints(bool isAsync) + => base.NumPoints(isAsync); + + [ConditionalTheory(Skip = "Issue #16963. Nullable error")] + public override Task OgcGeometryType(bool isAsync) + => base.OgcGeometryType(isAsync); + + [ConditionalTheory(Skip = "Issue #16963. Nullable error")] + public override Task PointOnSurface(bool isAsync) + => base.PointOnSurface(isAsync); + + [ConditionalTheory(Skip = "Issue #16963. Nullable error")] + public override Task SRID(bool isAsync) + => base.SRID(isAsync); + + [ConditionalTheory(Skip = "Issue #16963. Nullable error")] + public override Task SRID_geometry(bool isAsync) + => base.SRID_geometry(isAsync); + + [ConditionalTheory(Skip = "Issue #16963. Nullable error")] + public override Task StartPoint(bool isAsync) + => base.StartPoint(isAsync); + + [ConditionalTheory(Skip = "Issue #16963. Nullable error")] + public override Task X(bool isAsync) + => base.X(isAsync); + + [ConditionalTheory(Skip = "Issue #16963. Nullable error")] + public override Task Y(bool isAsync) + => base.Y(isAsync); + + [ConditionalTheory(Skip = "Issue #16963. Nullable error")] + public override Task Z(bool isAsync) + => base.Z(isAsync); } } From 0a95eccd84044867ce1680bfd7384a1f61f97a37 Mon Sep 17 00:00:00 2001 From: Smit Patel Date: Wed, 28 Aug 2019 11:35:11 -0700 Subject: [PATCH 12/21] InMemory: Support for GroupBy aggregate Part of #16963 Resolves #9591 --- ...yExpressionTranslatingExpressionVisitor.cs | 74 +++- .../InMemoryGroupByShaperExpression.cs | 25 ++ .../Internal/InMemoryLinqOperatorProvider.cs | 413 +++++++++++++++--- ...emoryProjectionBindingExpressionVisitor.cs | 1 - .../Query/Internal/InMemoryQueryExpression.cs | 128 +++++- ...yableMethodTranslatingExpressionVisitor.cs | 179 ++++++-- ...jectionBindingRemovingExpressionVisitor.cs | 4 +- ...ryShapedQueryCompilingExpressionVisitor.cs | 2 +- .../Query/Internal/InMemoryTableExpression.cs | 1 - src/EFCore/Query/GroupByShaperExpression.cs | 4 +- src/EFCore/Query/QueryableMethods.cs | 1 - .../InMemoryComplianceTest.cs | 1 - .../Query/AsyncGearsOfWarQueryInMemoryTest.cs | 8 - .../Query/GroupByQueryInMemoryTest.cs | 8 +- .../Query/QueryBugsInMemoryTest.cs | 2 +- .../Query/SimpleQueryInMemoryTest.cs | 6 - .../BuiltInDataTypesSqliteTest.cs | 40 +- .../ChangeTracking/Internal/FixupTest.cs | 2 +- 18 files changed, 723 insertions(+), 176 deletions(-) create mode 100644 src/EFCore.InMemory/Query/Internal/InMemoryGroupByShaperExpression.cs diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs b/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs index 1fb84dbd69a..be3564af6b2 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs @@ -8,6 +8,7 @@ using System.Reflection; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Query; using Microsoft.EntityFrameworkCore.Storage; @@ -161,13 +162,38 @@ private bool TryBindMember(Expression source, MemberIdentity memberIdentity, Typ return false; } - private Expression BindProperty(EntityProjectionExpression entityProjectionExpression, IProperty property) + private static Expression BindProperty(EntityProjectionExpression entityProjectionExpression, IProperty property) { return entityProjectionExpression.BindProperty(property); } + private static Expression GetSelector(MethodCallExpression methodCallExpression, GroupByShaperExpression groupByShaperExpression) + { + if (methodCallExpression.Arguments.Count == 1) + { + return groupByShaperExpression.ElementSelector; + } + + if (methodCallExpression.Arguments.Count == 2) + { + var selectorLambda = methodCallExpression.Arguments[1].UnwrapLambdaFromQuote(); + return ReplacingExpressionVisitor.Replace( + selectorLambda.Parameters[0], + groupByShaperExpression.ElementSelector, + selectorLambda.Body); + } + + throw new InvalidOperationException(CoreStrings.TranslationFailed(methodCallExpression.Print())); + } + protected override Expression VisitMethodCall(MethodCallExpression methodCallExpression) { + if (methodCallExpression.Method.IsGenericMethod + && methodCallExpression.Method.GetGenericMethodDefinition() == EntityMaterializerSource.TryReadValueMethod) + { + return methodCallExpression; + } + // EF.Property case if (methodCallExpression.TryGetEFPropertyArguments(out var source, out var propertyName)) { @@ -179,6 +205,52 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp throw new InvalidOperationException("EF.Property called with wrong property name."); } + // GroupBy Aggregate case + if (methodCallExpression.Object == null + && methodCallExpression.Method.DeclaringType == typeof(Enumerable) + && methodCallExpression.Arguments.Count > 0 + && methodCallExpression.Arguments[0] is InMemoryGroupByShaperExpression groupByShaperExpression) + { + switch (methodCallExpression.Method.Name) + { + case nameof(Enumerable.Average): + case nameof(Enumerable.Max): + case nameof(Enumerable.Min): + case nameof(Enumerable.Sum): + var translation = Translate(GetSelector(methodCallExpression, groupByShaperExpression)); + var selector = Expression.Lambda(translation, groupByShaperExpression.ValueBufferParameter); + MethodInfo getMethod() + => methodCallExpression.Method.Name switch + { + nameof(Enumerable.Average) => InMemoryLinqOperatorProvider.GetAverageWithSelector(selector.ReturnType), + nameof(Enumerable.Max) => InMemoryLinqOperatorProvider.GetMaxWithSelector(selector.ReturnType), + nameof(Enumerable.Min) => InMemoryLinqOperatorProvider.GetMinWithSelector(selector.ReturnType), + nameof(Enumerable.Sum) => InMemoryLinqOperatorProvider.GetSumWithSelector(selector.ReturnType), + _ => throw new InvalidOperationException("Invalid Aggregate Operator encountered."), + }; + var method = getMethod(); + method = method.GetGenericArguments().Length == 2 + ? method.MakeGenericMethod(typeof(ValueBuffer), selector.ReturnType) + : method.MakeGenericMethod(typeof(ValueBuffer)); + + return Expression.Call(method, + groupByShaperExpression.GroupingParameter, + selector); + + case nameof(Enumerable.Count): + return Expression.Call( + InMemoryLinqOperatorProvider.CountWithoutPredicate.MakeGenericMethod(typeof(ValueBuffer)), + groupByShaperExpression.GroupingParameter); + case nameof(Enumerable.LongCount): + return Expression.Call( + InMemoryLinqOperatorProvider.LongCountWithoutPredicate.MakeGenericMethod(typeof(ValueBuffer)), + groupByShaperExpression.GroupingParameter); + + default: + throw new InvalidOperationException(CoreStrings.TranslationFailed(methodCallExpression.Print())); + } + } + // Subquery case var subqueryTranslation = _queryableMethodTranslatingExpressionVisitor.TranslateSubquery(methodCallExpression); if (subqueryTranslation != null) diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryGroupByShaperExpression.cs b/src/EFCore.InMemory/Query/Internal/InMemoryGroupByShaperExpression.cs new file mode 100644 index 00000000000..4dfefd23622 --- /dev/null +++ b/src/EFCore.InMemory/Query/Internal/InMemoryGroupByShaperExpression.cs @@ -0,0 +1,25 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore.Query; + +namespace Microsoft.EntityFrameworkCore.InMemory.Query.Internal +{ + public class InMemoryGroupByShaperExpression : GroupByShaperExpression + { + public InMemoryGroupByShaperExpression( + Expression keySelector, + Expression elementSelector, + ParameterExpression groupingParameter, + ParameterExpression valueBufferParameter) + : base(keySelector, elementSelector) + { + GroupingParameter = groupingParameter; + ValueBufferParameter = valueBufferParameter; + } + + public virtual ParameterExpression GroupingParameter { get; } + public virtual ParameterExpression ValueBufferParameter { get; } + } +} diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryLinqOperatorProvider.cs b/src/EFCore.InMemory/Query/Internal/InMemoryLinqOperatorProvider.cs index 0d588543b4a..3d7f53aee47 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryLinqOperatorProvider.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryLinqOperatorProvider.cs @@ -11,73 +11,358 @@ namespace Microsoft.EntityFrameworkCore.InMemory.Query.Internal { public static class InMemoryLinqOperatorProvider { - private static MethodInfo GetMethod(string name, int parameterCount = 0) - => GetMethods(name, parameterCount).Single(); - - private static IEnumerable GetMethods(string name, int parameterCount = 0) - => typeof(Enumerable).GetTypeInfo().GetDeclaredMethods(name) - .Where(mi => mi.GetParameters().Length == parameterCount + 1); - - public static MethodInfo Where = GetMethods(nameof(Enumerable.Where), 1) - .Single(mi => mi.GetParameters()[1].ParameterType.GetGenericArguments().Length == 2); - public static MethodInfo Select = GetMethods(nameof(Enumerable.Select), 1) - .Single(mi => mi.GetParameters()[1].ParameterType.GetGenericArguments().Length == 2); - - public static MethodInfo Join = GetMethod(nameof(Enumerable.Join), 4); - public static MethodInfo GroupJoin = GetMethod(nameof(Enumerable.GroupJoin), 4); - public static MethodInfo DefaultIfEmptyWithArg = GetMethod(nameof(Enumerable.DefaultIfEmpty), 1); - public static MethodInfo SelectMany = GetMethods(nameof(Enumerable.SelectMany), 2) - .Single(mi => mi.GetParameters()[1].ParameterType.GetGenericArguments().Length == 2); - public static MethodInfo Contains = GetMethod(nameof(Enumerable.Contains), 1); - - public static MethodInfo OrderBy = GetMethod(nameof(Enumerable.OrderBy), 1); - public static MethodInfo OrderByDescending = GetMethod(nameof(Enumerable.OrderByDescending), 1); - public static MethodInfo ThenBy = GetMethod(nameof(Enumerable.ThenBy), 1); - public static MethodInfo ThenByDescending = GetMethod(nameof(Enumerable.ThenByDescending), 1); - public static MethodInfo All = GetMethod(nameof(Enumerable.All), 1); - public static MethodInfo Any = GetMethod(nameof(Enumerable.Any)); - public static MethodInfo AnyPredicate = GetMethod(nameof(Enumerable.Any), 1); - public static MethodInfo Count = GetMethod(nameof(Enumerable.Count)); - public static MethodInfo LongCount = GetMethod(nameof(Enumerable.LongCount)); - public static MethodInfo CountPredicate = GetMethod(nameof(Enumerable.Count), 1); - public static MethodInfo LongCountPredicate = GetMethod(nameof(Enumerable.LongCount), 1); - public static MethodInfo Distinct = GetMethod(nameof(Enumerable.Distinct)); - public static MethodInfo Take = GetMethod(nameof(Enumerable.Take), 1); - public static MethodInfo Skip = GetMethod(nameof(Enumerable.Skip), 1); - - public static MethodInfo FirstPredicate = GetMethod(nameof(Enumerable.First), 1); - public static MethodInfo FirstOrDefaultPredicate = GetMethod(nameof(Enumerable.FirstOrDefault), 1); - public static MethodInfo LastPredicate = GetMethod(nameof(Enumerable.Last), 1); - public static MethodInfo LastOrDefaultPredicate = GetMethod(nameof(Enumerable.LastOrDefault), 1); - public static MethodInfo SinglePredicate = GetMethod(nameof(Enumerable.Single), 1); - public static MethodInfo SingleOrDefaultPredicate = GetMethod(nameof(Enumerable.SingleOrDefault), 1); - - public static MethodInfo First = GetMethod(nameof(Enumerable.First), 0); - public static MethodInfo FirstOrDefault = GetMethod(nameof(Enumerable.FirstOrDefault), 0); - public static MethodInfo Last = GetMethod(nameof(Enumerable.Last), 0); - public static MethodInfo LastOrDefault = GetMethod(nameof(Enumerable.LastOrDefault), 0); - public static MethodInfo Single = GetMethod(nameof(Enumerable.Single), 0); - public static MethodInfo SingleOrDefault = GetMethod(nameof(Enumerable.SingleOrDefault), 0); - - public static MethodInfo Concat = GetMethod(nameof(Enumerable.Concat), 1); - public static MethodInfo Except = GetMethod(nameof(Enumerable.Except), 1); - public static MethodInfo Intersect = GetMethod(nameof(Enumerable.Intersect), 1); - public static MethodInfo Union = GetMethod(nameof(Enumerable.Union), 1); - - public static MethodInfo GetAggregateMethod(string methodName, Type elementType, int parameterCount = 0) + public static MethodInfo AsEnumerable { get; } + public static MethodInfo Cast { get; } + public static MethodInfo OfType { get; } + + public static MethodInfo All { get; } + public static MethodInfo AnyWithoutPredicate { get; } + public static MethodInfo AnyWithPredicate { get; } + public static MethodInfo Contains { get; } + + public static MethodInfo Concat { get; } + public static MethodInfo Except { get; } + public static MethodInfo Intersect { get; } + public static MethodInfo Union { get; } + + public static MethodInfo CountWithoutPredicate { get; } + public static MethodInfo CountWithPredicate { get; } + public static MethodInfo LongCountWithoutPredicate { get; } + public static MethodInfo LongCountWithPredicate { get; } + public static MethodInfo MinWithSelector { get; } + public static MethodInfo MinWithoutSelector { get; } + public static MethodInfo MaxWithSelector { get; } + public static MethodInfo MaxWithoutSelector { get; } + + public static MethodInfo ElementAt { get; } + public static MethodInfo ElementAtOrDefault { get; } + public static MethodInfo FirstWithoutPredicate { get; } + public static MethodInfo FirstWithPredicate { get; } + public static MethodInfo FirstOrDefaultWithoutPredicate { get; } + public static MethodInfo FirstOrDefaultWithPredicate { get; } + public static MethodInfo SingleWithoutPredicate { get; } + public static MethodInfo SingleWithPredicate { get; } + public static MethodInfo SingleOrDefaultWithoutPredicate { get; } + public static MethodInfo SingleOrDefaultWithPredicate { get; } + public static MethodInfo LastWithoutPredicate { get; } + public static MethodInfo LastWithPredicate { get; } + public static MethodInfo LastOrDefaultWithoutPredicate { get; } + public static MethodInfo LastOrDefaultWithPredicate { get; } + + public static MethodInfo Distinct { get; } + public static MethodInfo Reverse { get; } + public static MethodInfo Where { get; } + public static MethodInfo Select { get; } + public static MethodInfo Skip { get; } + public static MethodInfo Take { get; } + public static MethodInfo SkipWhile { get; } + public static MethodInfo TakeWhile { get; } + public static MethodInfo OrderBy { get; } + public static MethodInfo OrderByDescending { get; } + public static MethodInfo ThenBy { get; } + public static MethodInfo ThenByDescending { get; } + public static MethodInfo DefaultIfEmptyWithoutArgument { get; } + public static MethodInfo DefaultIfEmptyWithArgument { get; } + + public static MethodInfo Join { get; } + public static MethodInfo GroupJoin { get; } + public static MethodInfo SelectManyWithCollectionSelector { get; } + public static MethodInfo SelectManyWithoutCollectionSelector { get; } + + public static MethodInfo GroupByWithKeySelector { get; } + public static MethodInfo GroupByWithKeyElementSelector { get; } + public static MethodInfo GroupByWithKeyElementResultSelector { get; } + public static MethodInfo GroupByWithKeyResultSelector { get; } + + public static MethodInfo GetAverageWithoutSelector(Type type) => AverageWithoutSelectorMethods[type]; + public static MethodInfo GetAverageWithSelector(Type type) => AverageWithSelectorMethods[type]; + public static MethodInfo GetMaxWithoutSelector(Type type) + => MaxWithoutSelectorMethods.TryGetValue(type, out var method) + ? method + : MaxWithoutSelector; + + public static MethodInfo GetMaxWithSelector(Type type) + => MaxWithSelectorMethods.TryGetValue(type, out var method) + ? method + : MaxWithSelector; + + public static MethodInfo GetMinWithoutSelector(Type type) + => MinWithoutSelectorMethods.TryGetValue(type, out var method) + ? method + : MinWithoutSelector; + + public static MethodInfo GetMinWithSelector(Type type) + => MinWithSelectorMethods.TryGetValue(type, out var method) + ? method + : MinWithSelector; + + public static MethodInfo GetSumWithoutSelector(Type type) => SumWithoutSelectorMethods[type]; + public static MethodInfo GetSumWithSelector(Type type) => SumWithSelectorMethods[type]; + + private static Dictionary AverageWithoutSelectorMethods { get; } + private static Dictionary AverageWithSelectorMethods { get; } + private static Dictionary MaxWithoutSelectorMethods { get; } + private static Dictionary MaxWithSelectorMethods { get; } + private static Dictionary MinWithoutSelectorMethods { get; } + private static Dictionary MinWithSelectorMethods { get; } + private static Dictionary SumWithoutSelectorMethods { get; } + private static Dictionary SumWithSelectorMethods { get; } + + private static bool IsFunc(Type type, int funcGenericArgs = 2) + => type.IsGenericType + && type.GetGenericArguments().Length == funcGenericArgs; + + static InMemoryLinqOperatorProvider() { - Check.NotEmpty(methodName, nameof(methodName)); - Check.NotNull(elementType, nameof(elementType)); + var enumerableMethods = typeof(Enumerable).GetTypeInfo() + .GetMethods(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly).ToList(); + + AsEnumerable = enumerableMethods.Single( + mi => mi.Name == nameof(Enumerable.AsEnumerable) && mi.IsGenericMethod && mi.GetParameters().Length == 1); + Cast = enumerableMethods.Single( + mi => mi.Name == nameof(Enumerable.Cast) && mi.GetParameters().Length == 1); + OfType = enumerableMethods.Single( + mi => mi.Name == nameof(Enumerable.OfType) && mi.GetParameters().Length == 1); + + All = enumerableMethods.Single( + mi => mi.Name == nameof(Enumerable.All) && mi.GetParameters().Length == 2 && IsFunc(mi.GetParameters()[1].ParameterType)); + AnyWithoutPredicate = enumerableMethods.Single( + mi => mi.Name == nameof(Enumerable.Any) && mi.GetParameters().Length == 1); + AnyWithPredicate = enumerableMethods.Single( + mi => mi.Name == nameof(Enumerable.Any) && mi.GetParameters().Length == 2 && IsFunc(mi.GetParameters()[1].ParameterType)); + Contains = enumerableMethods.Single( + mi => mi.Name == nameof(Enumerable.Contains) && mi.GetParameters().Length == 2); - var aggregateMethods = GetMethods(methodName, parameterCount).ToList(); + Concat = enumerableMethods.Single( + mi => mi.Name == nameof(Enumerable.Concat) && mi.GetParameters().Length == 2); + Except = enumerableMethods.Single( + mi => mi.Name == nameof(Enumerable.Except) && mi.GetParameters().Length == 2); + Intersect = enumerableMethods.Single( + mi => mi.Name == nameof(Enumerable.Intersect) && mi.GetParameters().Length == 2); + Union = enumerableMethods.Single( + mi => mi.Name == nameof(Enumerable.Union) && mi.GetParameters().Length == 2); - return - aggregateMethods - .Single( - mi => mi.GetParameters().Last().ParameterType.GetGenericArguments().Last() == elementType); - //?? aggregateMethods.Single(mi => mi.IsGenericMethod) - // .MakeGenericMethod(elementType); + CountWithoutPredicate = enumerableMethods.Single( + mi => mi.Name == nameof(Enumerable.Count) && mi.GetParameters().Length == 1); + CountWithPredicate = enumerableMethods.Single( + mi => mi.Name == nameof(Enumerable.Count) && mi.GetParameters().Length == 2 && IsFunc(mi.GetParameters()[1].ParameterType)); + LongCountWithoutPredicate = enumerableMethods.Single( + mi => mi.Name == nameof(Enumerable.LongCount) && mi.GetParameters().Length == 1); + LongCountWithPredicate = enumerableMethods.Single( + mi => mi.Name == nameof(Enumerable.LongCount) && mi.GetParameters().Length == 2 && IsFunc(mi.GetParameters()[1].ParameterType)); + MinWithSelector = enumerableMethods.Single( + mi => mi.Name == nameof(Enumerable.Min) && mi.IsGenericMethod && mi.GetGenericArguments().Length == 2 && mi.GetParameters().Length == 2 && IsFunc(mi.GetParameters()[1].ParameterType)); + MinWithoutSelector = enumerableMethods.Single( + mi => mi.Name == nameof(Enumerable.Min) && mi.IsGenericMethod && mi.GetGenericArguments().Length == 1 && mi.GetParameters().Length == 1); + MaxWithSelector = enumerableMethods.Single( + mi => mi.Name == nameof(Enumerable.Max) && mi.IsGenericMethod && mi.GetGenericArguments().Length == 2 && mi.GetParameters().Length == 2 && IsFunc(mi.GetParameters()[1].ParameterType)); + MaxWithoutSelector = enumerableMethods.Single( + mi => mi.Name == nameof(Enumerable.Max) && mi.IsGenericMethod && mi.GetGenericArguments().Length == 1 && mi.GetParameters().Length == 1); + + ElementAt = enumerableMethods.Single( + mi => mi.Name == nameof(Enumerable.ElementAt) && mi.GetParameters().Length == 2); + ElementAtOrDefault = enumerableMethods.Single( + mi => mi.Name == nameof(Enumerable.ElementAtOrDefault) && mi.GetParameters().Length == 2); + FirstWithoutPredicate = enumerableMethods.Single( + mi => mi.Name == nameof(Enumerable.First) && mi.GetParameters().Length == 1); + FirstWithPredicate = enumerableMethods.Single( + mi => mi.Name == nameof(Enumerable.First) && mi.GetParameters().Length == 2 && IsFunc(mi.GetParameters()[1].ParameterType)); + FirstOrDefaultWithoutPredicate = enumerableMethods.Single( + mi => mi.Name == nameof(Enumerable.FirstOrDefault) && mi.GetParameters().Length == 1); + FirstOrDefaultWithPredicate = enumerableMethods.Single( + mi => mi.Name == nameof(Enumerable.FirstOrDefault) && mi.GetParameters().Length == 2 && IsFunc(mi.GetParameters()[1].ParameterType)); + SingleWithoutPredicate = enumerableMethods.Single( + mi => mi.Name == nameof(Enumerable.Single) && mi.GetParameters().Length == 1); + SingleWithPredicate = enumerableMethods.Single( + mi => mi.Name == nameof(Enumerable.Single) && mi.GetParameters().Length == 2 && IsFunc(mi.GetParameters()[1].ParameterType)); + SingleOrDefaultWithoutPredicate = enumerableMethods.Single( + mi => mi.Name == nameof(Enumerable.SingleOrDefault) && mi.GetParameters().Length == 1); + SingleOrDefaultWithPredicate = enumerableMethods.Single( + mi => mi.Name == nameof(Enumerable.SingleOrDefault) && mi.GetParameters().Length == 2 && IsFunc(mi.GetParameters()[1].ParameterType)); + LastWithoutPredicate = enumerableMethods.Single( + mi => mi.Name == nameof(Enumerable.Last) && mi.GetParameters().Length == 1); + LastWithPredicate = enumerableMethods.Single( + mi => mi.Name == nameof(Enumerable.Last) && mi.GetParameters().Length == 2 && IsFunc(mi.GetParameters()[1].ParameterType)); + LastOrDefaultWithoutPredicate = enumerableMethods.Single( + mi => mi.Name == nameof(Enumerable.LastOrDefault) && mi.GetParameters().Length == 1); + LastOrDefaultWithPredicate = enumerableMethods.Single( + mi => mi.Name == nameof(Enumerable.LastOrDefault) && mi.GetParameters().Length == 2 && IsFunc(mi.GetParameters()[1].ParameterType)); + + Distinct = enumerableMethods.Single( + mi => mi.Name == nameof(Enumerable.Distinct) && mi.GetParameters().Length == 1); + Reverse = enumerableMethods.Single( + mi => mi.Name == nameof(Enumerable.Reverse) && mi.GetParameters().Length == 1); + Where = enumerableMethods.Single( + mi => mi.Name == nameof(Enumerable.Where) && mi.GetParameters().Length == 2 && IsFunc(mi.GetParameters()[1].ParameterType)); + Select = enumerableMethods.Single( + mi => mi.Name == nameof(Enumerable.Select) && mi.GetParameters().Length == 2 && IsFunc(mi.GetParameters()[1].ParameterType)); + Skip = enumerableMethods.Single( + mi => mi.Name == nameof(Enumerable.Skip) && mi.GetParameters().Length == 2); + Take = enumerableMethods.Single( + mi => mi.Name == nameof(Enumerable.Take) && mi.GetParameters().Length == 2); + SkipWhile = enumerableMethods.Single( + mi => mi.Name == nameof(Enumerable.SkipWhile) && mi.GetParameters().Length == 2 && IsFunc(mi.GetParameters()[1].ParameterType)); + TakeWhile = enumerableMethods.Single( + mi => mi.Name == nameof(Enumerable.TakeWhile) && mi.GetParameters().Length == 2 && IsFunc(mi.GetParameters()[1].ParameterType)); + OrderBy = enumerableMethods.Single( + mi => mi.Name == nameof(Enumerable.OrderBy) && mi.GetParameters().Length == 2 && IsFunc(mi.GetParameters()[1].ParameterType)); + OrderByDescending = enumerableMethods.Single( + mi => mi.Name == nameof(Enumerable.OrderByDescending) && mi.GetParameters().Length == 2 && IsFunc(mi.GetParameters()[1].ParameterType)); + ThenBy = enumerableMethods.Single( + mi => mi.Name == nameof(Enumerable.ThenBy) && mi.GetParameters().Length == 2 && IsFunc(mi.GetParameters()[1].ParameterType)); + ThenByDescending = enumerableMethods.Single( + mi => mi.Name == nameof(Enumerable.ThenByDescending) && mi.GetParameters().Length == 2 && IsFunc(mi.GetParameters()[1].ParameterType)); + DefaultIfEmptyWithoutArgument = enumerableMethods.Single( + mi => mi.Name == nameof(Enumerable.DefaultIfEmpty) && mi.GetParameters().Length == 1); + DefaultIfEmptyWithArgument = enumerableMethods.Single( + mi => mi.Name == nameof(Enumerable.DefaultIfEmpty) && mi.GetParameters().Length == 2); + + Join = enumerableMethods.Single( + mi => mi.Name == nameof(Enumerable.Join) && mi.GetParameters().Length == 5); + GroupJoin = enumerableMethods.Single( + mi => mi.Name == nameof(Enumerable.GroupJoin) && mi.GetParameters().Length == 5); + SelectManyWithCollectionSelector = enumerableMethods.Single( + mi => mi.Name == nameof(Enumerable.SelectMany) && mi.GetParameters().Length == 3 && IsFunc(mi.GetParameters()[1].ParameterType)); + SelectManyWithoutCollectionSelector = enumerableMethods.Single( + mi => mi.Name == nameof(Enumerable.SelectMany) && mi.GetParameters().Length == 2 && IsFunc(mi.GetParameters()[1].ParameterType)); + + GroupByWithKeySelector = enumerableMethods.Single( + mi => mi.Name == nameof(Enumerable.GroupBy) && mi.GetParameters().Length == 2 && IsFunc(mi.GetParameters()[1].ParameterType)); + GroupByWithKeyElementSelector = enumerableMethods.Single( + mi => mi.Name == nameof(Enumerable.GroupBy) && mi.GetParameters().Length == 3 && IsFunc(mi.GetParameters()[1].ParameterType) && IsFunc(mi.GetParameters()[2].ParameterType)); + GroupByWithKeyElementResultSelector = enumerableMethods.Single( + mi => mi.Name == nameof(Enumerable.GroupBy) && mi.GetParameters().Length == 4 && IsFunc(mi.GetParameters()[1].ParameterType) && IsFunc(mi.GetParameters()[2].ParameterType) && IsFunc(mi.GetParameters()[3].ParameterType, 3)); + GroupByWithKeyResultSelector = enumerableMethods.Single( + mi => mi.Name == nameof(Enumerable.GroupBy) && mi.GetParameters().Length == 3 && IsFunc(mi.GetParameters()[1].ParameterType) && IsFunc(mi.GetParameters()[2].ParameterType, 3)); + + MethodInfo getSumOrAverageWithoutSelector(string methodName) + => enumerableMethods.Single( + mi => mi.Name == methodName + && mi.GetParameters().Length == 1 + && mi.GetParameters()[0].ParameterType.GetGenericArguments()[0] == typeof(T)); + + static bool hasSelector(Type type) + => type.IsGenericType + && type.GetGenericArguments().Length == 2 + && type.GetGenericArguments()[1] == typeof(T); + + MethodInfo getSumOrAverageWithSelector(string methodName) + => enumerableMethods.Single( + mi => mi.Name == methodName + && mi.GetParameters().Length == 2 + && hasSelector(mi.GetParameters()[1].ParameterType)); + + AverageWithoutSelectorMethods = new Dictionary + { + { typeof(decimal), getSumOrAverageWithoutSelector(nameof(Queryable.Average)) }, + { typeof(long), getSumOrAverageWithoutSelector(nameof(Queryable.Average)) }, + { typeof(int), getSumOrAverageWithoutSelector(nameof(Queryable.Average)) }, + { typeof(double), getSumOrAverageWithoutSelector(nameof(Queryable.Average)) }, + { typeof(float), getSumOrAverageWithoutSelector(nameof(Queryable.Average)) }, + { typeof(decimal?), getSumOrAverageWithoutSelector(nameof(Queryable.Average)) }, + { typeof(long?), getSumOrAverageWithoutSelector(nameof(Queryable.Average)) }, + { typeof(int?), getSumOrAverageWithoutSelector(nameof(Queryable.Average)) }, + { typeof(double?), getSumOrAverageWithoutSelector(nameof(Queryable.Average)) }, + { typeof(float?), getSumOrAverageWithoutSelector(nameof(Queryable.Average)) } + }; + + AverageWithSelectorMethods = new Dictionary + { + { typeof(decimal), getSumOrAverageWithSelector(nameof(Queryable.Average)) }, + { typeof(long), getSumOrAverageWithSelector(nameof(Queryable.Average)) }, + { typeof(int), getSumOrAverageWithSelector(nameof(Queryable.Average)) }, + { typeof(double), getSumOrAverageWithSelector(nameof(Queryable.Average)) }, + { typeof(float), getSumOrAverageWithSelector(nameof(Queryable.Average)) }, + { typeof(decimal?), getSumOrAverageWithSelector(nameof(Queryable.Average)) }, + { typeof(long?), getSumOrAverageWithSelector(nameof(Queryable.Average)) }, + { typeof(int?), getSumOrAverageWithSelector(nameof(Queryable.Average)) }, + { typeof(double?), getSumOrAverageWithSelector(nameof(Queryable.Average)) }, + { typeof(float?), getSumOrAverageWithSelector(nameof(Queryable.Average)) } + }; + + MaxWithoutSelectorMethods = new Dictionary + { + { typeof(decimal), getSumOrAverageWithoutSelector(nameof(Queryable.Max)) }, + { typeof(long), getSumOrAverageWithoutSelector(nameof(Queryable.Max)) }, + { typeof(int), getSumOrAverageWithoutSelector(nameof(Queryable.Max)) }, + { typeof(double), getSumOrAverageWithoutSelector(nameof(Queryable.Max)) }, + { typeof(float), getSumOrAverageWithoutSelector(nameof(Queryable.Max)) }, + { typeof(decimal?), getSumOrAverageWithoutSelector(nameof(Queryable.Max)) }, + { typeof(long?), getSumOrAverageWithoutSelector(nameof(Queryable.Max)) }, + { typeof(int?), getSumOrAverageWithoutSelector(nameof(Queryable.Max)) }, + { typeof(double?), getSumOrAverageWithoutSelector(nameof(Queryable.Max)) }, + { typeof(float?), getSumOrAverageWithoutSelector(nameof(Queryable.Max)) } + }; + + MaxWithSelectorMethods = new Dictionary + { + { typeof(decimal), getSumOrAverageWithSelector(nameof(Queryable.Max)) }, + { typeof(long), getSumOrAverageWithSelector(nameof(Queryable.Max)) }, + { typeof(int), getSumOrAverageWithSelector(nameof(Queryable.Max)) }, + { typeof(double), getSumOrAverageWithSelector(nameof(Queryable.Max)) }, + { typeof(float), getSumOrAverageWithSelector(nameof(Queryable.Max)) }, + { typeof(decimal?), getSumOrAverageWithSelector(nameof(Queryable.Max)) }, + { typeof(long?), getSumOrAverageWithSelector(nameof(Queryable.Max)) }, + { typeof(int?), getSumOrAverageWithSelector(nameof(Queryable.Max)) }, + { typeof(double?), getSumOrAverageWithSelector(nameof(Queryable.Max)) }, + { typeof(float?), getSumOrAverageWithSelector(nameof(Queryable.Max)) } + }; + + MinWithoutSelectorMethods = new Dictionary + { + { typeof(decimal), getSumOrAverageWithoutSelector(nameof(Queryable.Min)) }, + { typeof(long), getSumOrAverageWithoutSelector(nameof(Queryable.Min)) }, + { typeof(int), getSumOrAverageWithoutSelector(nameof(Queryable.Min)) }, + { typeof(double), getSumOrAverageWithoutSelector(nameof(Queryable.Min)) }, + { typeof(float), getSumOrAverageWithoutSelector(nameof(Queryable.Min)) }, + { typeof(decimal?), getSumOrAverageWithoutSelector(nameof(Queryable.Min)) }, + { typeof(long?), getSumOrAverageWithoutSelector(nameof(Queryable.Min)) }, + { typeof(int?), getSumOrAverageWithoutSelector(nameof(Queryable.Min)) }, + { typeof(double?), getSumOrAverageWithoutSelector(nameof(Queryable.Min)) }, + { typeof(float?), getSumOrAverageWithoutSelector(nameof(Queryable.Min)) } + }; + + MinWithSelectorMethods = new Dictionary + { + { typeof(decimal), getSumOrAverageWithSelector(nameof(Queryable.Min)) }, + { typeof(long), getSumOrAverageWithSelector(nameof(Queryable.Min)) }, + { typeof(int), getSumOrAverageWithSelector(nameof(Queryable.Min)) }, + { typeof(double), getSumOrAverageWithSelector(nameof(Queryable.Min)) }, + { typeof(float), getSumOrAverageWithSelector(nameof(Queryable.Min)) }, + { typeof(decimal?), getSumOrAverageWithSelector(nameof(Queryable.Min)) }, + { typeof(long?), getSumOrAverageWithSelector(nameof(Queryable.Min)) }, + { typeof(int?), getSumOrAverageWithSelector(nameof(Queryable.Min)) }, + { typeof(double?), getSumOrAverageWithSelector(nameof(Queryable.Min)) }, + { typeof(float?), getSumOrAverageWithSelector(nameof(Queryable.Min)) } + }; + + SumWithoutSelectorMethods = new Dictionary + { + { typeof(decimal), getSumOrAverageWithoutSelector(nameof(Queryable.Sum)) }, + { typeof(long), getSumOrAverageWithoutSelector(nameof(Queryable.Sum)) }, + { typeof(int), getSumOrAverageWithoutSelector(nameof(Queryable.Sum)) }, + { typeof(double), getSumOrAverageWithoutSelector(nameof(Queryable.Sum)) }, + { typeof(float), getSumOrAverageWithoutSelector(nameof(Queryable.Sum)) }, + { typeof(decimal?), getSumOrAverageWithoutSelector(nameof(Queryable.Sum)) }, + { typeof(long?), getSumOrAverageWithoutSelector(nameof(Queryable.Sum)) }, + { typeof(int?), getSumOrAverageWithoutSelector(nameof(Queryable.Sum)) }, + { typeof(double?), getSumOrAverageWithoutSelector(nameof(Queryable.Sum)) }, + { typeof(float?), getSumOrAverageWithoutSelector(nameof(Queryable.Sum)) } + }; + + SumWithSelectorMethods = new Dictionary + { + { typeof(decimal), getSumOrAverageWithSelector(nameof(Queryable.Sum)) }, + { typeof(long), getSumOrAverageWithSelector(nameof(Queryable.Sum)) }, + { typeof(int), getSumOrAverageWithSelector(nameof(Queryable.Sum)) }, + { typeof(double), getSumOrAverageWithSelector(nameof(Queryable.Sum)) }, + { typeof(float), getSumOrAverageWithSelector(nameof(Queryable.Sum)) }, + { typeof(decimal?), getSumOrAverageWithSelector(nameof(Queryable.Sum)) }, + { typeof(long?), getSumOrAverageWithSelector(nameof(Queryable.Sum)) }, + { typeof(int?), getSumOrAverageWithSelector(nameof(Queryable.Sum)) }, + { typeof(double?), getSumOrAverageWithSelector(nameof(Queryable.Sum)) }, + { typeof(float?), getSumOrAverageWithSelector(nameof(Queryable.Sum)) } + }; } } - } diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryProjectionBindingExpressionVisitor.cs b/src/EFCore.InMemory/Query/Internal/InMemoryProjectionBindingExpressionVisitor.cs index 6456db627cd..ded09cef614 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryProjectionBindingExpressionVisitor.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryProjectionBindingExpressionVisitor.cs @@ -250,7 +250,6 @@ protected override Expression VisitMemberInit(MemberInitExpression memberInitExp } newBindings[i] = VisitMemberBinding(memberInitExpression.Bindings[i]); - if (newBindings[i] == null) { return null; diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs b/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs index da5f81b9c14..542d58dcc93 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs @@ -23,18 +23,20 @@ private static readonly PropertyInfo _valueBufferCountMemberInfo private readonly List _valueBufferSlots = new List(); private readonly IDictionary> _entityProjectionCache = new Dictionary>(); + private readonly ParameterExpression _valueBufferParameter; private IDictionary _projectionMapping = new Dictionary(); + private ParameterExpression _groupingParameter; public virtual IReadOnlyList Projection => _valueBufferSlots; public virtual Expression ServerQueryExpression { get; set; } - public virtual ParameterExpression ValueBufferParameter { get; } + public virtual ParameterExpression CurrentParameter => _groupingParameter ?? _valueBufferParameter; public override Type Type => typeof(IEnumerable); public sealed override ExpressionType NodeType => ExpressionType.Extension; public InMemoryQueryExpression(IEntityType entityType) { - ValueBufferParameter = Parameter(typeof(ValueBuffer), "valueBuffer"); + _valueBufferParameter = Parameter(typeof(ValueBuffer), "valueBuffer"); ServerQueryExpression = new InMemoryTableExpression(entityType); var readExpressionMap = new Dictionary(); foreach (var property in entityType.GetAllBaseTypesInclusive().SelectMany(et => et.GetDeclaredProperties())) @@ -47,7 +49,7 @@ public InMemoryQueryExpression(IEntityType entityType) readExpressionMap[property] = Condition( LessThan( Constant(property.GetIndex()), - MakeMemberAccess(ValueBufferParameter, + MakeMemberAccess(_valueBufferParameter, _valueBufferCountMemberInfo)), CreateReadValueExpression(typeof(object), property.GetIndex(), property), Default(typeof(object))); @@ -145,7 +147,7 @@ public virtual int AddSubqueryProjection(ShapedQueryExpression shapedQueryExpres innerShaper = new ShaperRemappingExpressionVisitor(subquery._projectionMapping) .Visit(shapedQueryExpression.ShaperExpression); - innerShaper = Lambda(innerShaper, subquery.ValueBufferParameter); + innerShaper = Lambda(innerShaper, subquery.CurrentParameter); return AddToProjection(serverQueryExpression); } @@ -225,12 +227,13 @@ public virtual void PushdownIntoSubquery() NewArrayInit( typeof(object), _valueBufferSlots - .Select(e => e.Type.IsValueType ? Convert(e, typeof(object)) : e) - .ToArray())), - ValueBufferParameter); + .Select(e => e.Type.IsValueType ? Convert(e, typeof(object)) : e))), + CurrentParameter); + + _groupingParameter = null; ServerQueryExpression = Call( - InMemoryLinqOperatorProvider.Select.MakeGenericMethod(typeof(ValueBuffer), typeof(ValueBuffer)), + InMemoryLinqOperatorProvider.Select.MakeGenericMethod(ServerQueryExpression.Type.TryGetSequenceType(), typeof(ValueBuffer)), ServerQueryExpression, selectorLambda); @@ -294,7 +297,7 @@ public virtual void ApplyProjection() _valueBufferSlots .Select(e => e.Type.IsValueType ? Convert(e, typeof(object)) : e) .ToArray())), - ValueBufferParameter); + CurrentParameter); ServerQueryExpression = Call( InMemoryLinqOperatorProvider.Select.MakeGenericMethod(typeof(ValueBuffer), typeof(ValueBuffer)), @@ -302,16 +305,93 @@ public virtual void ApplyProjection() selectorLambda); } - private Expression CreateReadValueExpression( - Type type, - int index, - IPropertyBase property) + public virtual InMemoryGroupByShaperExpression ApplyGrouping(Expression groupingKey, Expression shaperExpression) + { + PushdownIntoSubquery(); + + var selectMethod = (MethodCallExpression)ServerQueryExpression; + var groupBySource = selectMethod.Arguments[0]; + var elementSelector = selectMethod.Arguments[1]; + _groupingParameter = Parameter(typeof(IGrouping), "grouping"); + var groupingKeyAccessExpression = PropertyOrField(_groupingParameter, nameof(IGrouping.Key)); + var groupingKeyExpressions = new List(); + groupingKey = GetGroupingKey(groupingKey, groupingKeyExpressions, groupingKeyAccessExpression); + var keySelector = Lambda( + New( + _valueBufferConstructor, + NewArrayInit( + typeof(object), + groupingKeyExpressions.Select(e => e.Type.IsValueType ? Convert(e, typeof(object)) : e))), + _valueBufferParameter); + + ServerQueryExpression = Call( + InMemoryLinqOperatorProvider.GroupByWithKeyElementSelector.MakeGenericMethod( + typeof(ValueBuffer), typeof(ValueBuffer), typeof(ValueBuffer)), + selectMethod.Arguments[0], + keySelector, + selectMethod.Arguments[1]); + + return new InMemoryGroupByShaperExpression( + groupingKey, + shaperExpression, + _groupingParameter, + _valueBufferParameter); + } + + private Expression GetGroupingKey(Expression key, List groupingExpressions, Expression groupingKeyAccessExpression) + { + switch (key) + { + case NewExpression newExpression: + var arguments = new Expression[newExpression.Arguments.Count]; + for (var i = 0; i < arguments.Length; i++) + { + arguments[i] = GetGroupingKey(newExpression.Arguments[i], groupingExpressions, groupingKeyAccessExpression); + } + return newExpression.Update(arguments); + + case MemberInitExpression memberInitExpression: + if (memberInitExpression.Bindings.Any(mb => !(mb is MemberAssignment))) + { + goto default; + } + + var updatedNewExpression = (NewExpression)GetGroupingKey( + memberInitExpression.NewExpression, groupingExpressions, groupingKeyAccessExpression); + var memberBindings = new MemberAssignment[memberInitExpression.Bindings.Count]; + for (var i = 0; i < memberBindings.Length; i++) + { + var memberAssignment = (MemberAssignment)memberInitExpression.Bindings[i]; + memberBindings[i] = memberAssignment.Update( + GetGroupingKey( + memberAssignment.Expression, + groupingExpressions, + groupingKeyAccessExpression)); + } + return memberInitExpression.Update(updatedNewExpression, memberBindings); + + default: + var index = groupingExpressions.Count; + groupingExpressions.Add(key); + return CreateReadValueExpression( + groupingKeyAccessExpression, + key.Type, + index, + InferPropertyFromInner(key)); + } + } + + private static Expression CreateReadValueExpression( + Expression valueBufferParameter, Type type, int index, IPropertyBase property) => Call( EntityMaterializerSource.TryReadValueMethod.MakeGenericMethod(type), - ValueBufferParameter, + valueBufferParameter, Constant(index), Constant(property, typeof(IPropertyBase))); + private Expression CreateReadValueExpression(Type type, int index, IPropertyBase property) + => CreateReadValueExpression(_valueBufferParameter, type, index, property); + public virtual void AddInnerJoin( InMemoryQueryExpression innerQueryExpression, LambdaExpression outerKeySelector, @@ -325,8 +405,8 @@ public virtual void AddInnerJoin( var replacingVisitor = new ReplacingExpressionVisitor( new Dictionary { - { ValueBufferParameter, outerParameter }, - { innerQueryExpression.ValueBufferParameter, innerParameter } + { CurrentParameter, outerParameter }, + { innerQueryExpression.CurrentParameter, innerParameter } }); var index = 0; @@ -438,8 +518,8 @@ public virtual void AddLeftJoin( var replacingVisitor = new ReplacingExpressionVisitor( new Dictionary { - { ValueBufferParameter, MakeMemberAccess(outerParameter, outerMemberInfo) }, - { innerQueryExpression.ValueBufferParameter, innerParameter } + { CurrentParameter, MakeMemberAccess(outerParameter, outerMemberInfo) }, + { innerQueryExpression.CurrentParameter, innerParameter } }); var index = 0; @@ -497,7 +577,7 @@ public virtual void AddLeftJoin( var collectionSelector = Lambda( Call( - InMemoryLinqOperatorProvider.DefaultIfEmptyWithArg.MakeGenericMethod(typeof(ValueBuffer)), + InMemoryLinqOperatorProvider.DefaultIfEmptyWithArgument.MakeGenericMethod(typeof(ValueBuffer)), collection, New( _valueBufferConstructor, @@ -518,7 +598,7 @@ public virtual void AddLeftJoin( innerParameter); ServerQueryExpression = Call( - InMemoryLinqOperatorProvider.SelectMany.MakeGenericMethod( + InMemoryLinqOperatorProvider.SelectManyWithCollectionSelector.MakeGenericMethod( groupTransparentIdentifierType, typeof(ValueBuffer), typeof(ValueBuffer)), groupJoinExpression, collectionSelector, @@ -536,8 +616,8 @@ public virtual void AddSelectMany(InMemoryQueryExpression innerQueryExpression, var replacingVisitor = new ReplacingExpressionVisitor( new Dictionary { - { ValueBufferParameter, outerParameter }, - { innerQueryExpression.ValueBufferParameter, innerParameter } + { CurrentParameter, outerParameter }, + { innerQueryExpression.CurrentParameter, innerParameter } }); var index = 0; @@ -608,10 +688,10 @@ public virtual void AddSelectMany(InMemoryQueryExpression innerQueryExpression, innerParameter); ServerQueryExpression = Call( - InMemoryLinqOperatorProvider.SelectMany.MakeGenericMethod( + InMemoryLinqOperatorProvider.SelectManyWithCollectionSelector.MakeGenericMethod( typeof(ValueBuffer), typeof(ValueBuffer), typeof(ValueBuffer)), ServerQueryExpression, - Lambda(innerQueryExpression.ServerQueryExpression, ValueBufferParameter), + Lambda(innerQueryExpression.ServerQueryExpression, CurrentParameter), resultSelector); _projectionMapping = projectionMapping; diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs index 66543828fb1..7329a3e2b16 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs @@ -72,7 +72,7 @@ protected override ShapedQueryExpression TranslateAll(ShapedQueryExpression sour inMemoryQueryExpression.ServerQueryExpression = Expression.Call( - InMemoryLinqOperatorProvider.All.MakeGenericMethod(typeof(ValueBuffer)), + InMemoryLinqOperatorProvider.All.MakeGenericMethod(inMemoryQueryExpression.CurrentParameter.Type), inMemoryQueryExpression.ServerQueryExpression, predicate); @@ -88,7 +88,7 @@ protected override ShapedQueryExpression TranslateAny(ShapedQueryExpression sour if (predicate == null) { inMemoryQueryExpression.ServerQueryExpression = Expression.Call( - InMemoryLinqOperatorProvider.Any.MakeGenericMethod(typeof(ValueBuffer)), + InMemoryLinqOperatorProvider.AnyWithoutPredicate.MakeGenericMethod(inMemoryQueryExpression.CurrentParameter.Type), inMemoryQueryExpression.ServerQueryExpression); } else @@ -100,7 +100,7 @@ protected override ShapedQueryExpression TranslateAny(ShapedQueryExpression sour } inMemoryQueryExpression.ServerQueryExpression = Expression.Call( - InMemoryLinqOperatorProvider.AnyPredicate.MakeGenericMethod(typeof(ValueBuffer)), + InMemoryLinqOperatorProvider.AnyWithPredicate.MakeGenericMethod(inMemoryQueryExpression.CurrentParameter.Type), inMemoryQueryExpression.ServerQueryExpression, predicate); } @@ -141,10 +141,10 @@ protected override ShapedQueryExpression TranslateContains(ShapedQueryExpression Expression.Call( InMemoryLinqOperatorProvider.Contains.MakeGenericMethod(item.Type), Expression.Call( - InMemoryLinqOperatorProvider.Select.MakeGenericMethod(typeof(ValueBuffer), item.Type), + InMemoryLinqOperatorProvider.Select.MakeGenericMethod(inMemoryQueryExpression.CurrentParameter.Type, item.Type), inMemoryQueryExpression.ServerQueryExpression, Expression.Lambda( - inMemoryQueryExpression.GetMappedProjection(new ProjectionMember()), inMemoryQueryExpression.ValueBufferParameter)), + inMemoryQueryExpression.GetMappedProjection(new ProjectionMember()), inMemoryQueryExpression.CurrentParameter)), item); source.ShaperExpression = inMemoryQueryExpression.GetSingleScalarProjection(); @@ -160,7 +160,7 @@ protected override ShapedQueryExpression TranslateCount(ShapedQueryExpression so { inMemoryQueryExpression.ServerQueryExpression = Expression.Call( - InMemoryLinqOperatorProvider.Count.MakeGenericMethod(typeof(ValueBuffer)), + InMemoryLinqOperatorProvider.CountWithoutPredicate.MakeGenericMethod(inMemoryQueryExpression.CurrentParameter.Type), inMemoryQueryExpression.ServerQueryExpression); } else @@ -173,7 +173,7 @@ protected override ShapedQueryExpression TranslateCount(ShapedQueryExpression so inMemoryQueryExpression.ServerQueryExpression = Expression.Call( - InMemoryLinqOperatorProvider.CountPredicate.MakeGenericMethod(typeof(ValueBuffer)), + InMemoryLinqOperatorProvider.CountWithPredicate.MakeGenericMethod(inMemoryQueryExpression.CurrentParameter.Type), inMemoryQueryExpression.ServerQueryExpression, predicate); } @@ -193,7 +193,7 @@ protected override ShapedQueryExpression TranslateDistinct(ShapedQueryExpression inMemoryQueryExpression.PushdownIntoSubquery(); inMemoryQueryExpression.ServerQueryExpression = Expression.Call( - InMemoryLinqOperatorProvider.Distinct.MakeGenericMethod(typeof(ValueBuffer)), + InMemoryLinqOperatorProvider.Distinct.MakeGenericMethod(inMemoryQueryExpression.CurrentParameter.Type), inMemoryQueryExpression.ServerQueryExpression); return source; @@ -212,12 +212,111 @@ protected override ShapedQueryExpression TranslateFirstOrDefault(ShapedQueryExpr predicate, returnType, returnDefault - ? InMemoryLinqOperatorProvider.FirstOrDefault - : InMemoryLinqOperatorProvider.First); + ? InMemoryLinqOperatorProvider.FirstOrDefaultWithoutPredicate + : InMemoryLinqOperatorProvider.FirstWithoutPredicate); } protected override ShapedQueryExpression TranslateGroupBy(ShapedQueryExpression source, LambdaExpression keySelector, LambdaExpression elementSelector, LambdaExpression resultSelector) - => null; + { + var remappedKeySelector = RemapLambdaBody(source, keySelector); + + var translatedKey = TranslateGroupingKey(remappedKeySelector); + if (translatedKey != null) + { + if (elementSelector != null) + { + source = TranslateSelect(source, elementSelector); + } + + var inMemoryQueryExpression = (InMemoryQueryExpression)source.QueryExpression; + source.ShaperExpression = inMemoryQueryExpression.ApplyGrouping(translatedKey, source.ShaperExpression); + + if (resultSelector == null) + { + return source; + } + + var keyAccessExpression = Expression.MakeMemberAccess( + source.ShaperExpression, + source.ShaperExpression.Type.GetTypeInfo().GetMember(nameof(IGrouping.Key))[0]); + + var original1 = resultSelector.Parameters[0]; + var original2 = resultSelector.Parameters[1]; + + var newResultSelectorBody = new ReplacingExpressionVisitor( + new Dictionary { + { original1, keyAccessExpression }, + { original2, source.ShaperExpression } + }).Visit(resultSelector.Body); + + //newResultSelectorBody = ExpandWeakEntities(selectExpression, newResultSelectorBody); + + source.ShaperExpression = _projectionBindingExpressionVisitor.Translate(inMemoryQueryExpression, newResultSelectorBody); + + inMemoryQueryExpression.PushdownIntoSubquery(); + + return source; + } + + return null; + } + + private Expression TranslateGroupingKey(Expression expression) + { + switch (expression) + { + case NewExpression newExpression: + if (newExpression.Arguments.Count == 0) + { + return newExpression; + } + + var newArguments = new Expression[newExpression.Arguments.Count]; + for (var i = 0; i < newArguments.Length; i++) + { + newArguments[i] = TranslateGroupingKey(newExpression.Arguments[i]); + if (newArguments[i] == null) + { + return null; + } + } + + return newExpression.Update(newArguments); + + case MemberInitExpression memberInitExpression: + var updatedNewExpression = (NewExpression)TranslateGroupingKey(memberInitExpression.NewExpression); + if (updatedNewExpression == null) + { + return null; + } + + var newBindings = new MemberAssignment[memberInitExpression.Bindings.Count]; + for (var i = 0; i < newBindings.Length; i++) + { + var memberAssignment = (MemberAssignment)memberInitExpression.Bindings[i]; + var visitedExpression = TranslateGroupingKey(memberAssignment.Expression); + if (visitedExpression == null) + { + return null; + } + + newBindings[i] = memberAssignment.Update(visitedExpression); + } + + return memberInitExpression.Update(updatedNewExpression, newBindings); + + default: + var translation = _expressionTranslator.Translate(expression); + if (translation == null) + { + return null; + } + + return translation.Type == expression.Type + ? (Expression)translation + : Expression.Convert(translation, expression.Type); + } + } protected override ShapedQueryExpression TranslateGroupJoin(ShapedQueryExpression outer, ShapedQueryExpression inner, LambdaExpression outerKeySelector, LambdaExpression innerKeySelector, LambdaExpression resultSelector) => null; @@ -258,8 +357,8 @@ protected override ShapedQueryExpression TranslateLastOrDefault(ShapedQueryExpre predicate, returnType, returnDefault - ? InMemoryLinqOperatorProvider.LastOrDefault - : InMemoryLinqOperatorProvider.Last); + ? InMemoryLinqOperatorProvider.LastOrDefaultWithoutPredicate + : InMemoryLinqOperatorProvider.LastWithoutPredicate); } protected override ShapedQueryExpression TranslateLeftJoin(ShapedQueryExpression outer, ShapedQueryExpression inner, LambdaExpression outerKeySelector, LambdaExpression innerKeySelector, LambdaExpression resultSelector) @@ -296,7 +395,7 @@ protected override ShapedQueryExpression TranslateLongCount(ShapedQueryExpressio { inMemoryQueryExpression.ServerQueryExpression = Expression.Call( - InMemoryLinqOperatorProvider.LongCount.MakeGenericMethod(typeof(ValueBuffer)), + InMemoryLinqOperatorProvider.LongCountWithoutPredicate.MakeGenericMethod(inMemoryQueryExpression.CurrentParameter.Type), inMemoryQueryExpression.ServerQueryExpression); } else @@ -309,7 +408,7 @@ protected override ShapedQueryExpression TranslateLongCount(ShapedQueryExpressio inMemoryQueryExpression.ServerQueryExpression = Expression.Call( - InMemoryLinqOperatorProvider.LongCountPredicate.MakeGenericMethod(typeof(ValueBuffer)), + InMemoryLinqOperatorProvider.LongCountWithPredicate.MakeGenericMethod(inMemoryQueryExpression.CurrentParameter.Type), inMemoryQueryExpression.ServerQueryExpression, predicate); } @@ -412,7 +511,7 @@ protected override ShapedQueryExpression TranslateOrderBy(ShapedQueryExpression inMemoryQueryExpression.ServerQueryExpression = Expression.Call( (ascending ? InMemoryLinqOperatorProvider.OrderBy : InMemoryLinqOperatorProvider.OrderByDescending) - .MakeGenericMethod(typeof(ValueBuffer), keySelector.ReturnType), + .MakeGenericMethod(inMemoryQueryExpression.CurrentParameter.Type, keySelector.ReturnType), inMemoryQueryExpression.ServerQueryExpression, keySelector); @@ -432,8 +531,15 @@ protected override ShapedQueryExpression TranslateSelect(ShapedQueryExpression s var newSelectorBody = ReplacingExpressionVisitor.Replace( selector.Parameters.Single(), source.ShaperExpression, selector.Body); - source.ShaperExpression = _projectionBindingExpressionVisitor - .Translate((InMemoryQueryExpression)source.QueryExpression, newSelectorBody); + var groupByQuery = source.ShaperExpression is GroupByShaperExpression; + var queryExpression = (InMemoryQueryExpression)source.QueryExpression; + + source.ShaperExpression = _projectionBindingExpressionVisitor.Translate(queryExpression, newSelectorBody); + + if (groupByQuery) + { + queryExpression.PushdownIntoSubquery(); + } return source; } @@ -513,8 +619,8 @@ protected override ShapedQueryExpression TranslateSingleOrDefault(ShapedQueryExp predicate, returnType, returnDefault - ? InMemoryLinqOperatorProvider.SingleOrDefault - : InMemoryLinqOperatorProvider.Single); + ? InMemoryLinqOperatorProvider.SingleOrDefaultWithoutPredicate + : InMemoryLinqOperatorProvider.SingleWithoutPredicate); } protected override ShapedQueryExpression TranslateSkip(ShapedQueryExpression source, Expression count) @@ -528,7 +634,7 @@ protected override ShapedQueryExpression TranslateSkip(ShapedQueryExpression sou inMemoryQueryExpression.ServerQueryExpression = Expression.Call( - InMemoryLinqOperatorProvider.Skip.MakeGenericMethod(typeof(ValueBuffer)), + InMemoryLinqOperatorProvider.Skip.MakeGenericMethod(inMemoryQueryExpression.CurrentParameter.Type), inMemoryQueryExpression.ServerQueryExpression, count); @@ -552,7 +658,7 @@ protected override ShapedQueryExpression TranslateTake(ShapedQueryExpression sou inMemoryQueryExpression.ServerQueryExpression = Expression.Call( - InMemoryLinqOperatorProvider.Take.MakeGenericMethod(typeof(ValueBuffer)), + InMemoryLinqOperatorProvider.Take.MakeGenericMethod(inMemoryQueryExpression.CurrentParameter.Type), inMemoryQueryExpression.ServerQueryExpression, count); @@ -574,7 +680,7 @@ protected override ShapedQueryExpression TranslateThenBy(ShapedQueryExpression s inMemoryQueryExpression.ServerQueryExpression = Expression.Call( (ascending ? InMemoryLinqOperatorProvider.ThenBy : InMemoryLinqOperatorProvider.ThenByDescending) - .MakeGenericMethod(typeof(ValueBuffer), keySelector.ReturnType), + .MakeGenericMethod(inMemoryQueryExpression.CurrentParameter.Type, keySelector.ReturnType), inMemoryQueryExpression.ServerQueryExpression, keySelector); @@ -594,7 +700,7 @@ protected override ShapedQueryExpression TranslateWhere(ShapedQueryExpression so } inMemoryQueryExpression.ServerQueryExpression = Expression.Call( - InMemoryLinqOperatorProvider.Where.MakeGenericMethod(typeof(ValueBuffer)), + InMemoryLinqOperatorProvider.Where.MakeGenericMethod(inMemoryQueryExpression.CurrentParameter.Type), inMemoryQueryExpression.ServerQueryExpression, predicate); @@ -613,7 +719,7 @@ private LambdaExpression TranslateLambdaExpression( return lambdaBody != null ? Expression.Lambda(lambdaBody, - ((InMemoryQueryExpression)shapedQueryExpression.QueryExpression).ValueBufferParameter) + ((InMemoryQueryExpression)shapedQueryExpression.QueryExpression).CurrentParameter) : null; } @@ -632,7 +738,7 @@ private ShapedQueryExpression TranslateScalarAggregate( || selector.Body == selector.Parameters[0] ? Expression.Lambda( inMemoryQueryExpression.GetMappedProjection(new ProjectionMember()), - inMemoryQueryExpression.ValueBufferParameter) + inMemoryQueryExpression.CurrentParameter) : TranslateLambdaExpression(source, selector); if (selector == null) @@ -640,11 +746,22 @@ private ShapedQueryExpression TranslateScalarAggregate( return null; } + MethodInfo getMethod() + => methodName switch + { + nameof(Enumerable.Average) => InMemoryLinqOperatorProvider.GetAverageWithSelector(selector.ReturnType), + nameof(Enumerable.Max) => InMemoryLinqOperatorProvider.GetMaxWithSelector(selector.ReturnType), + nameof(Enumerable.Min) => InMemoryLinqOperatorProvider.GetMinWithSelector(selector.ReturnType), + nameof(Enumerable.Sum) => InMemoryLinqOperatorProvider.GetSumWithSelector(selector.ReturnType), + _ => throw new InvalidOperationException("Invalid Aggregate Operator encountered."), + }; + var method = getMethod(); + method = method.GetGenericArguments().Length == 2 + ? method.MakeGenericMethod(typeof(ValueBuffer), selector.ReturnType) + : method.MakeGenericMethod(typeof(ValueBuffer)); + inMemoryQueryExpression.ServerQueryExpression - = Expression.Call( - InMemoryLinqOperatorProvider - .GetAggregateMethod(methodName, selector.ReturnType, parameterCount: 1) - .MakeGenericMethod(typeof(ValueBuffer)), + = Expression.Call(method, inMemoryQueryExpression.ServerQueryExpression, selector); @@ -669,7 +786,7 @@ private ShapedQueryExpression TranslateSingleResultOperator( inMemoryQueryExpression.ServerQueryExpression = Expression.Call( - method.MakeGenericMethod(typeof(ValueBuffer)), + method.MakeGenericMethod(inMemoryQueryExpression.CurrentParameter.Type), inMemoryQueryExpression.ServerQueryExpression); inMemoryQueryExpression.ConvertToEnumerable(); diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryShapedQueryCompilingExpressionVisitor.InMemoryProjectionBindingRemovingExpressionVisitor.cs b/src/EFCore.InMemory/Query/Internal/InMemoryShapedQueryCompilingExpressionVisitor.InMemoryProjectionBindingRemovingExpressionVisitor.cs index b8d80ac7b1f..c2f8d1962f0 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryShapedQueryCompilingExpressionVisitor.InMemoryProjectionBindingRemovingExpressionVisitor.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryShapedQueryCompilingExpressionVisitor.InMemoryProjectionBindingRemovingExpressionVisitor.cs @@ -32,7 +32,7 @@ protected override Expression VisitBinary(BinaryExpression binaryExpression) _materializationContextBindings[parameterExpression] = ((IDictionary)GetProjectionIndex(queryExpression, projectionBindingExpression), - ((InMemoryQueryExpression)projectionBindingExpression.QueryExpression).ValueBufferParameter); + ((InMemoryQueryExpression)projectionBindingExpression.QueryExpression).CurrentParameter); var updatedExpression = Expression.New( newExpression.Constructor, @@ -79,7 +79,7 @@ protected override Expression VisitExtension(Expression extensionExpression) { var queryExpression = (InMemoryQueryExpression)projectionBindingExpression.QueryExpression; var projectionIndex = (int)GetProjectionIndex(queryExpression, projectionBindingExpression); - var valueBuffer = queryExpression.ValueBufferParameter; + var valueBuffer = queryExpression.CurrentParameter; return Expression.Call( EntityMaterializerSource.TryReadValueMethod.MakeGenericMethod(projectionBindingExpression.Type), diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryShapedQueryCompilingExpressionVisitor.cs b/src/EFCore.InMemory/Query/Internal/InMemoryShapedQueryCompilingExpressionVisitor.cs index 1cdaa15668c..a1efacff407 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryShapedQueryCompilingExpressionVisitor.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryShapedQueryCompilingExpressionVisitor.cs @@ -50,7 +50,7 @@ protected override Expression VisitShapedQueryExpression(ShapedQueryExpression s var inMemoryQueryExpression = (InMemoryQueryExpression)shapedQueryExpression.QueryExpression; var shaper = new ShaperExpressionProcessingExpressionVisitor( - inMemoryQueryExpression, inMemoryQueryExpression.ValueBufferParameter) + inMemoryQueryExpression, inMemoryQueryExpression.CurrentParameter) .Inject(shapedQueryExpression.ShaperExpression); shaper = InjectEntityMaterializers(shaper); diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryTableExpression.cs b/src/EFCore.InMemory/Query/Internal/InMemoryTableExpression.cs index 07f37fb029c..5318823b6e5 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryTableExpression.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryTableExpression.cs @@ -27,5 +27,4 @@ protected override Expression VisitChildren(ExpressionVisitor visitor) return this; } } - } diff --git a/src/EFCore/Query/GroupByShaperExpression.cs b/src/EFCore/Query/GroupByShaperExpression.cs index 14629ebf5dd..40a27f7a4f9 100644 --- a/src/EFCore/Query/GroupByShaperExpression.cs +++ b/src/EFCore/Query/GroupByShaperExpression.cs @@ -23,13 +23,13 @@ public GroupByShaperExpression(Expression keySelector, Expression elementSelecto public virtual void Print(ExpressionPrinter expressionPrinter) { - expressionPrinter.AppendLine("GroupBy("); + expressionPrinter.AppendLine($"{nameof(GroupByShaperExpression)}:"); expressionPrinter.Append("KeySelector: "); expressionPrinter.Visit(KeySelector); expressionPrinter.AppendLine(", "); expressionPrinter.Append("ElementSelector:"); expressionPrinter.Visit(ElementSelector); - expressionPrinter.AppendLine(")"); + expressionPrinter.AppendLine(); } protected override Expression VisitChildren(ExpressionVisitor visitor) diff --git a/src/EFCore/Query/QueryableMethods.cs b/src/EFCore/Query/QueryableMethods.cs index 75e9e2bdefd..fb4be67a623 100644 --- a/src/EFCore/Query/QueryableMethods.cs +++ b/src/EFCore/Query/QueryableMethods.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Linq; using System.Linq.Expressions; using System.Reflection; diff --git a/test/EFCore.InMemory.FunctionalTests/InMemoryComplianceTest.cs b/test/EFCore.InMemory.FunctionalTests/InMemoryComplianceTest.cs index c47cbf14784..a9de08cd218 100644 --- a/test/EFCore.InMemory.FunctionalTests/InMemoryComplianceTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/InMemoryComplianceTest.cs @@ -20,7 +20,6 @@ public class InMemoryComplianceTest : ComplianceTestBase // Remaining Issue #16963 3.0 query tests: typeof(ComplexNavigationsWeakQueryTestBase<>), typeof(OwnedQueryTestBase<>), - typeof(GroupByQueryTestBase<>), typeof(ComplexNavigationsQueryTestBase<>), typeof(GearsOfWarQueryTestBase<>), typeof(SpatialQueryTestBase<>) diff --git a/test/EFCore.InMemory.FunctionalTests/Query/AsyncGearsOfWarQueryInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/AsyncGearsOfWarQueryInMemoryTest.cs index 951c4c7d59e..564449e3c3f 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/AsyncGearsOfWarQueryInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/AsyncGearsOfWarQueryInMemoryTest.cs @@ -1,8 +1,6 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System.Threading.Tasks; -using Xunit; using Xunit.Abstractions; namespace Microsoft.EntityFrameworkCore.Query @@ -13,11 +11,5 @@ public AsyncGearsOfWarQueryInMemoryTest(GearsOfWarQueryInMemoryFixture fixture, : base(fixture) { } - - [ConditionalFact(Skip = "Issue#16963 Group By")] - public override Task GroupBy_Select_sum() - { - return base.GroupBy_Select_sum(); - } } } diff --git a/test/EFCore.InMemory.FunctionalTests/Query/GroupByQueryInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/GroupByQueryInMemoryTest.cs index 666d6df2635..6224eb64770 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/GroupByQueryInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/GroupByQueryInMemoryTest.cs @@ -8,7 +8,7 @@ namespace Microsoft.EntityFrameworkCore.Query { - internal class GroupByQueryInMemoryTest : GroupByQueryTestBase> + public class GroupByQueryInMemoryTest : GroupByQueryTestBase> { public GroupByQueryInMemoryTest( NorthwindQueryInMemoryFixture fixture, @@ -18,11 +18,5 @@ public GroupByQueryInMemoryTest( { //TestLoggerFactory.TestOutputHelper = testOutputHelper; } - - [ConditionalTheory(Skip = "See issue #9591")] - public override Task Select_Distinct_GroupBy(bool isAsync) - { - return base.Select_Distinct_GroupBy(isAsync); - } } } diff --git a/test/EFCore.InMemory.FunctionalTests/Query/QueryBugsInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/QueryBugsInMemoryTest.cs index 2a8ff48b6cc..b4e6554fc34 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/QueryBugsInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/QueryBugsInMemoryTest.cs @@ -150,7 +150,7 @@ public class Motor #region Bug3595 - [ConditionalFact(Skip = "Issue#16963 groupBy")] + [ConditionalFact] public void GroupBy_with_uninitialized_datetime_projection_3595() { using (CreateScratch(Seed3595, "3595")) diff --git a/test/EFCore.InMemory.FunctionalTests/Query/SimpleQueryInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/SimpleQueryInMemoryTest.cs index a0d76376ddc..3f97e8fda86 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/SimpleQueryInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/SimpleQueryInMemoryTest.cs @@ -87,12 +87,6 @@ public override void Client_code_using_instance_method_throws() base.Client_code_using_instance_method_throws(); } - [ConditionalTheory(Skip = "Issue#16963 (GroupBy)")] - public override Task GroupBy_Select_Union(bool isAsync) - { - return Task.CompletedTask; - } - [ConditionalTheory(Skip = "Issue#17386")] public override Task Contains_with_local_tuple_array_closure(bool isAsync) { diff --git a/test/EFCore.Sqlite.FunctionalTests/BuiltInDataTypesSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/BuiltInDataTypesSqliteTest.cs index 72b1dff0283..7410678ecb3 100644 --- a/test/EFCore.Sqlite.FunctionalTests/BuiltInDataTypesSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/BuiltInDataTypesSqliteTest.cs @@ -982,9 +982,8 @@ public virtual void Cant_query_Min_of_converted_types() Assert.Equal( CoreStrings.TranslationFailed( - "Min(\n source: GroupBy(\n KeySelector: 1, \n ElementSelector:EntityShaperExpression: \n EntityType: BuiltInNullableDataTypes\n ValueBufferExpression: \n ProjectionBindingExpression: EmptyProjectionMember\n IsNullable: False\n )\n , \n selector: (e) => e.TestNullableDecimal)"), - ex.Message, - ignoreLineEndingDifferences: true); + "Min( source: GroupByShaperExpression: KeySelector: 1, ElementSelector:EntityShaperExpression: EntityType: BuiltInNullableDataTypes ValueBufferExpression: ProjectionBindingExpression: EmptyProjectionMember IsNullable: False , selector: (e) => e.TestNullableDecimal)"), + RemoveNewLines(ex.Message)); ex = Assert.Throws( () => query @@ -993,9 +992,8 @@ public virtual void Cant_query_Min_of_converted_types() Assert.Equal( CoreStrings.TranslationFailed( - "Min>(\n source: GroupBy(\n KeySelector: 1, \n ElementSelector:EntityShaperExpression: \n EntityType: BuiltInNullableDataTypes\n ValueBufferExpression: \n ProjectionBindingExpression: EmptyProjectionMember\n IsNullable: False\n )\n , \n selector: (e) => e.TestNullableDateTimeOffset)"), - ex.Message, - ignoreLineEndingDifferences: true); + "Min>( source: GroupByShaperExpression: KeySelector: 1, ElementSelector:EntityShaperExpression: EntityType: BuiltInNullableDataTypes ValueBufferExpression: ProjectionBindingExpression: EmptyProjectionMember IsNullable: False , selector: (e) => e.TestNullableDateTimeOffset)"), + RemoveNewLines(ex.Message)); ex = Assert.Throws( () => query @@ -1004,9 +1002,8 @@ public virtual void Cant_query_Min_of_converted_types() Assert.Equal( CoreStrings.TranslationFailed( - "Min>(\n source: GroupBy(\n KeySelector: 1, \n ElementSelector:EntityShaperExpression: \n EntityType: BuiltInNullableDataTypes\n ValueBufferExpression: \n ProjectionBindingExpression: EmptyProjectionMember\n IsNullable: False\n )\n , \n selector: (e) => e.TestNullableTimeSpan)"), - ex.Message, - ignoreLineEndingDifferences: true); + "Min>( source: GroupByShaperExpression: KeySelector: 1, ElementSelector:EntityShaperExpression: EntityType: BuiltInNullableDataTypes ValueBufferExpression: ProjectionBindingExpression: EmptyProjectionMember IsNullable: False , selector: (e) => e.TestNullableTimeSpan)"), + RemoveNewLines(ex.Message)); ex = Assert.Throws( () => query @@ -1015,9 +1012,8 @@ public virtual void Cant_query_Min_of_converted_types() Assert.Equal( CoreStrings.TranslationFailed( - "Min>(\n source: GroupBy(\n KeySelector: 1, \n ElementSelector:EntityShaperExpression: \n EntityType: BuiltInNullableDataTypes\n ValueBufferExpression: \n ProjectionBindingExpression: EmptyProjectionMember\n IsNullable: False\n )\n , \n selector: (e) => e.TestNullableUnsignedInt64)"), - ex.Message, - ignoreLineEndingDifferences: true); + "Min>( source: GroupByShaperExpression: KeySelector: 1, ElementSelector:EntityShaperExpression: EntityType: BuiltInNullableDataTypes ValueBufferExpression: ProjectionBindingExpression: EmptyProjectionMember IsNullable: False , selector: (e) => e.TestNullableUnsignedInt64)"), + RemoveNewLines(ex.Message)); } } @@ -1061,9 +1057,8 @@ public virtual void Cant_query_Max_of_converted_types() Assert.Equal( CoreStrings.TranslationFailed( - "Max(\n source: GroupBy(\n KeySelector: 1, \n ElementSelector:EntityShaperExpression: \n EntityType: BuiltInNullableDataTypes\n ValueBufferExpression: \n ProjectionBindingExpression: EmptyProjectionMember\n IsNullable: False\n )\n , \n selector: (e) => e.TestNullableDecimal)"), - ex.Message, - ignoreLineEndingDifferences: true); + "Max( source: GroupByShaperExpression: KeySelector: 1, ElementSelector:EntityShaperExpression: EntityType: BuiltInNullableDataTypes ValueBufferExpression: ProjectionBindingExpression: EmptyProjectionMember IsNullable: False , selector: (e) => e.TestNullableDecimal)"), + RemoveNewLines(ex.Message)); ex = Assert.Throws( () => query @@ -1072,9 +1067,8 @@ public virtual void Cant_query_Max_of_converted_types() Assert.Equal( CoreStrings.TranslationFailed( - "Max>(\n source: GroupBy(\n KeySelector: 1, \n ElementSelector:EntityShaperExpression: \n EntityType: BuiltInNullableDataTypes\n ValueBufferExpression: \n ProjectionBindingExpression: EmptyProjectionMember\n IsNullable: False\n )\n , \n selector: (e) => e.TestNullableDateTimeOffset)"), - ex.Message, - ignoreLineEndingDifferences: true); + "Max>( source: GroupByShaperExpression: KeySelector: 1, ElementSelector:EntityShaperExpression: EntityType: BuiltInNullableDataTypes ValueBufferExpression: ProjectionBindingExpression: EmptyProjectionMember IsNullable: False , selector: (e) => e.TestNullableDateTimeOffset)"), + RemoveNewLines(ex.Message)); ex = Assert.Throws( () => query @@ -1083,9 +1077,8 @@ public virtual void Cant_query_Max_of_converted_types() Assert.Equal( CoreStrings.TranslationFailed( - "Max>(\n source: GroupBy(\n KeySelector: 1, \n ElementSelector:EntityShaperExpression: \n EntityType: BuiltInNullableDataTypes\n ValueBufferExpression: \n ProjectionBindingExpression: EmptyProjectionMember\n IsNullable: False\n )\n , \n selector: (e) => e.TestNullableTimeSpan)"), - ex.Message, - ignoreLineEndingDifferences: true); + "Max>( source: GroupByShaperExpression: KeySelector: 1, ElementSelector:EntityShaperExpression: EntityType: BuiltInNullableDataTypes ValueBufferExpression: ProjectionBindingExpression: EmptyProjectionMember IsNullable: False , selector: (e) => e.TestNullableTimeSpan)"), + RemoveNewLines(ex.Message)); ex = Assert.Throws( () => query @@ -1094,9 +1087,8 @@ public virtual void Cant_query_Max_of_converted_types() Assert.Equal( CoreStrings.TranslationFailed( - "Max>(\n source: GroupBy(\n KeySelector: 1, \n ElementSelector:EntityShaperExpression: \n EntityType: BuiltInNullableDataTypes\n ValueBufferExpression: \n ProjectionBindingExpression: EmptyProjectionMember\n IsNullable: False\n )\n , \n selector: (e) => e.TestNullableUnsignedInt64)"), - ex.Message, - ignoreLineEndingDifferences: true); + "Max>( source: GroupByShaperExpression: KeySelector: 1, ElementSelector:EntityShaperExpression: EntityType: BuiltInNullableDataTypes ValueBufferExpression: ProjectionBindingExpression: EmptyProjectionMember IsNullable: False , selector: (e) => e.TestNullableUnsignedInt64)"), + RemoveNewLines(ex.Message)); } } diff --git a/test/EFCore.Tests/ChangeTracking/Internal/FixupTest.cs b/test/EFCore.Tests/ChangeTracking/Internal/FixupTest.cs index 28b9e8fed9c..ce5b4da7668 100644 --- a/test/EFCore.Tests/ChangeTracking/Internal/FixupTest.cs +++ b/test/EFCore.Tests/ChangeTracking/Internal/FixupTest.cs @@ -2418,7 +2418,7 @@ public void Replace_dependent_one_to_one_no_navs_FK_set(EntityState oldEntitySta } } - [ConditionalFact(Skip = "issue #16963 - using InMemory Include query")] // Issue #6067 + [ConditionalFact] // Issue #6067 public void Collection_nav_props_remain_fixed_up_after_manual_fixup_and_DetectChanges() { using (var context = new FixupContext()) From 712ed07c6cfe5ebe2f89085253e5e4a7ed1bb5b8 Mon Sep 17 00:00:00 2001 From: Smit Patel Date: Wed, 28 Aug 2019 15:54:02 -0700 Subject: [PATCH 13/21] InMemory: Support for DefaultIfEmpty Part of #16963 --- .../Query/Internal/InMemoryQueryExpression.cs | 56 ++++++++++++++++++- ...yableMethodTranslatingExpressionVisitor.cs | 12 +++- src/EFCore/Query/EntityMaterializerSource.cs | 2 +- .../Query/AsyncSimpleQueryInMemoryTest.cs | 36 ------------ .../Query/SimpleQueryInMemoryTest.cs | 55 +++--------------- 5 files changed, 76 insertions(+), 85 deletions(-) diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs b/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs index 542d58dcc93..5de9275fde9 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs @@ -252,7 +252,61 @@ public virtual void PushdownIntoSubquery() } } - private IPropertyBase InferPropertyFromInner(Expression expression) + public virtual void ApplyDefaultIfEmpty() + { + if (_valueBufferSlots.Count != 0) + { + throw new InvalidOperationException("Cannot Apply DefaultIfEmpty after ClientProjection."); + } + + var result = new Dictionary(); + foreach (var keyValuePair in _projectionMapping) + { + if (keyValuePair.Value is EntityProjectionExpression entityProjection) + { + var map = new Dictionary(); + foreach (var property in GetAllPropertiesInHierarchy(entityProjection.EntityType)) + { + var index = AddToProjection(entityProjection.BindProperty(property)); + map[property] = CreateReadValueExpression(property.ClrType.MakeNullable(), index, property); + } + result[keyValuePair.Key] = new EntityProjectionExpression(entityProjection.EntityType, map); + } + else + { + var index = AddToProjection(keyValuePair.Value); + result[keyValuePair.Key] = CreateReadValueExpression( + keyValuePair.Value.Type.MakeNullable(), index, InferPropertyFromInner(keyValuePair.Value)); + } + } + + _projectionMapping = result; + + var selectorLambda = Lambda( + New( + _valueBufferConstructor, + NewArrayInit( + typeof(object), + _valueBufferSlots + .Select(e => e.Type.IsValueType ? Convert(e, typeof(object)) : e))), + CurrentParameter); + + _groupingParameter = null; + + ServerQueryExpression = Call( + InMemoryLinqOperatorProvider.Select.MakeGenericMethod(ServerQueryExpression.Type.TryGetSequenceType(), typeof(ValueBuffer)), + ServerQueryExpression, + selectorLambda); + + ServerQueryExpression = Call( + InMemoryLinqOperatorProvider.DefaultIfEmptyWithArgument.MakeGenericMethod(typeof(ValueBuffer)), + ServerQueryExpression, + New(_valueBufferConstructor, NewArrayInit(typeof(object), Enumerable.Repeat(Constant(null), _valueBufferSlots.Count)))); + + _valueBufferSlots.Clear(); + } + + private static IPropertyBase InferPropertyFromInner(Expression expression) { if (expression is MethodCallExpression methodCallExpression && methodCallExpression.Method.IsGenericMethod diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs index 7329a3e2b16..1944ed1f7ff 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs @@ -184,7 +184,17 @@ protected override ShapedQueryExpression TranslateCount(ShapedQueryExpression so } protected override ShapedQueryExpression TranslateDefaultIfEmpty(ShapedQueryExpression source, Expression defaultValue) - => null; + { + if (defaultValue == null) + { + ((InMemoryQueryExpression)source.QueryExpression).ApplyDefaultIfEmpty(); + source.ShaperExpression = MarkShaperNullable(source.ShaperExpression); + + return source; + } + + return null; + } protected override ShapedQueryExpression TranslateDistinct(ShapedQueryExpression source) { diff --git a/src/EFCore/Query/EntityMaterializerSource.cs b/src/EFCore/Query/EntityMaterializerSource.cs index 76b5b77c263..d4cbb842cac 100644 --- a/src/EFCore/Query/EntityMaterializerSource.cs +++ b/src/EFCore/Query/EntityMaterializerSource.cs @@ -52,7 +52,7 @@ public static readonly MethodInfo TryReadValueMethod [MethodImpl(MethodImplOptions.AggressiveInlining)] private static TValue TryReadValue( in ValueBuffer valueBuffer, int index, IPropertyBase property) - => (TValue)valueBuffer[index]; + => valueBuffer[index] is TValue value ? value : default; public virtual Expression CreateMaterializeExpression( IEntityType entityType, diff --git a/test/EFCore.InMemory.FunctionalTests/Query/AsyncSimpleQueryInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/AsyncSimpleQueryInMemoryTest.cs index 386bec964c3..295108eb638 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/AsyncSimpleQueryInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/AsyncSimpleQueryInMemoryTest.cs @@ -13,41 +13,5 @@ public AsyncSimpleQueryInMemoryTest(NorthwindQueryInMemoryFixture Date: Wed, 21 Aug 2019 15:39:50 -0700 Subject: [PATCH 14/21] Add null propagation/protection logic for InMemory provider. When we bind to a non-nullable property on entity that can be nullable (e.g. due to left join) we modify the result to be nullable, to avoid "nullable object must have a value" errors. This nullability is then propagated further. We have few blockers: - predicates (Where, Any, First, Count, etc): always need to be of type bool. When necessary we add "== true". - conditional expression Test: needs to be bool, same as above - method call arguments: we can't reliably rewrite methodcall when the arguments types change (generic methods specifically), we convert arguments back to their original types if they were changed to nullable versions. - method call caller: if the caller was changed from non-nullable to nullable we still need to call the method with the original type, but we add null check before - caller.Method(args) -> nullable_caller == null ? null : (resultType?)caller.Method(args) - selectors (Select, Max etc): we need to preserve the original result type, we use convert - anonymous type, array init: we need to preserve the original type, we use convert Also enable GearsOfWar and ComplexNavigation tests for in memory. --- .../Internal/EntityProjectionExpression.cs | 17 +- ...yExpressionTranslatingExpressionVisitor.cs | 171 +++++++++++++++++- ...emoryProjectionBindingExpressionVisitor.cs | 19 +- .../Query/Internal/InMemoryQueryExpression.cs | 27 ++- ...yableMethodTranslatingExpressionVisitor.cs | 75 ++++++-- .../Query/Internal/InMemoryTableExpression.cs | 8 +- ...SubqueryMemberPushdownExpressionVisitor.cs | 1 + .../ComplexNavigationsQueryInMemoryTest.cs | 170 ++++++++++++++++- .../Query/GearsOfWarQueryInMemoryTest.cs | 104 ++++++++++- .../QueryFilterFuncletizationInMemoryTest.cs | 7 + .../Query/SimpleQueryInMemoryTest.cs | 9 + .../Query/SpatialQueryInMemoryTest.cs | 123 ------------- 12 files changed, 581 insertions(+), 150 deletions(-) diff --git a/src/EFCore.InMemory/Query/Internal/EntityProjectionExpression.cs b/src/EFCore.InMemory/Query/Internal/EntityProjectionExpression.cs index 2f34586df3f..265765b4152 100644 --- a/src/EFCore.InMemory/Query/Internal/EntityProjectionExpression.cs +++ b/src/EFCore.InMemory/Query/Internal/EntityProjectionExpression.cs @@ -5,10 +5,11 @@ using System.Collections.Generic; using System.Linq.Expressions; using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Query; namespace Microsoft.EntityFrameworkCore.InMemory.Query.Internal { - public class EntityProjectionExpression : Expression + public class EntityProjectionExpression : Expression, IPrintableExpression { private readonly IDictionary _readExpressionMap; @@ -50,5 +51,19 @@ public virtual Expression BindProperty(IProperty property) return _readExpressionMap[property]; } + + public virtual void Print(ExpressionPrinter expressionPrinter) + { + expressionPrinter.AppendLine(nameof(EntityProjectionExpression) + ":"); + using (expressionPrinter.Indent()) + { + foreach (var readExpressionMapEntry in _readExpressionMap) + { + expressionPrinter.Append(readExpressionMapEntry.Key + " -> "); + expressionPrinter.Visit(readExpressionMapEntry.Value); + expressionPrinter.AppendLine(); + } + } + } } } diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs b/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs index be3564af6b2..2c06d662493 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Linq.Expressions; @@ -69,27 +70,59 @@ public virtual Expression Translate(Expression expression) protected override Expression VisitBinary(BinaryExpression binaryExpression) { - var left = Visit(binaryExpression.Left); - var right = Visit(binaryExpression.Right); - if (left == null || right == null) + var newLeft = Visit(binaryExpression.Left); + var newRight = Visit(binaryExpression.Right); + + if (newLeft == null || newRight == null) { return null; } - return binaryExpression.Update(left, binaryExpression.Conversion, right); + if (TypeNullabilityChanged(newLeft.Type, binaryExpression.Left.Type) + || TypeNullabilityChanged(newRight.Type, binaryExpression.Right.Type)) + { + newLeft = MakeNullable(newLeft); + newRight = MakeNullable(newRight); + } + + return Expression.MakeBinary( + binaryExpression.NodeType, + newLeft, + newRight, + binaryExpression.IsLiftedToNull, + binaryExpression.Method, + binaryExpression.Conversion); } + private static Expression MakeNullable(Expression expression) + => !expression.Type.IsNullableType() + ? Expression.Convert(expression, expression.Type.MakeNullable()) + : expression; + protected override Expression VisitConditional(ConditionalExpression conditionalExpression) { var test = Visit(conditionalExpression.Test); var ifTrue = Visit(conditionalExpression.IfTrue); var ifFalse = Visit(conditionalExpression.IfFalse); + if (test == null || ifTrue == null || ifFalse == null) { return null; } - return conditionalExpression.Update(test, ifTrue, ifFalse); + if (test.Type == typeof(bool?)) + { + test = Expression.Equal(test, Expression.Constant(true, typeof(bool?))); + } + + if (TypeNullabilityChanged(ifTrue.Type, conditionalExpression.IfTrue.Type) + || TypeNullabilityChanged(ifFalse.Type, conditionalExpression.IfFalse.Type)) + { + ifTrue = MakeNullable(ifTrue); + ifFalse = MakeNullable(ifFalse); + } + + return Expression.Condition(test, ifTrue, ifFalse); } protected override Expression VisitMember(MemberExpression memberExpression) @@ -109,9 +142,27 @@ protected override Expression VisitMember(MemberExpression memberExpression) return result; } - return memberExpression.Update(innerExpression); + var updatedMemberExpression = (Expression)memberExpression.Update(innerExpression); + if (innerExpression != null + && innerExpression.Type.IsNullableType() + && ShouldApplyNullProtectionForMemberAccess(innerExpression.Type, memberExpression.Member.Name)) + { + updatedMemberExpression = MakeNullable(updatedMemberExpression); + + return Expression.Condition( + Expression.Equal(innerExpression, Expression.Default(innerExpression.Type)), + Expression.Default(updatedMemberExpression.Type), + updatedMemberExpression); + } + + return updatedMemberExpression; } + private bool ShouldApplyNullProtectionForMemberAccess(Type callerType, string memberName) + => !(callerType.IsGenericType + && callerType.GetGenericTypeDefinition() == typeof(Nullable<>) + && (memberName == nameof(Nullable.Value) || memberName == nameof(Nullable.HasValue))); + private bool TryBindMember(Expression source, MemberIdentity memberIdentity, Type type, out Expression result) { result = null; @@ -151,7 +202,9 @@ private bool TryBindMember(Expression source, MemberIdentity memberIdentity, Typ } result = BindProperty(entityProjection, property); - if (result.Type != type) + + // if the result type change was just nullability change e.g from int to int? we want to preserve the new type for null propagation + if (result.Type != type && !TypeNullabilityChanged(result.Type, type)) { result = Expression.Convert(result, type); } @@ -162,6 +215,9 @@ private bool TryBindMember(Expression source, MemberIdentity memberIdentity, Typ return false; } + private bool TypeNullabilityChanged(Type maybeNullableType, Type nonNullableType) + => maybeNullableType.IsNullableType() && !nonNullableType.IsNullableType() && maybeNullableType.UnwrapNullableType() == nonNullableType; + private static Expression BindProperty(EntityProjectionExpression entityProjectionExpression, IProperty property) { return entityProjectionExpression.BindProperty(property); @@ -331,6 +387,7 @@ MethodInfo getMethod() } var arguments = new Expression[methodCallExpression.Arguments.Count]; + var parameterTypes = methodCallExpression.Method.GetParameters().Select(p => p.ParameterType).ToArray(); for (var i = 0; i < arguments.Length; i++) { var argument = Visit(methodCallExpression.Arguments[i]); @@ -338,9 +395,37 @@ MethodInfo getMethod() { return null; } + + // if the nullability of arguments change, we have no easy/reliable way to adjust the actual methodInfo to match the new type, + // so we are forced to cast back to the original type + if (argument.Type != methodCallExpression.Arguments[i].Type + && !parameterTypes[i].IsAssignableFrom(argument.Type)) + { + argument = Expression.Convert(argument, methodCallExpression.Arguments[i].Type); + } + arguments[i] = argument; } + // if object is nullable, add null safeguard before calling the function + // we special-case Nullable<>.GetValueOrDefault, which doesn't need the safeguard + if (methodCallExpression.Object != null + && @object.Type.IsNullableType() + && !(methodCallExpression.Method.Name == nameof(Nullable.GetValueOrDefault))) + { + var result = (Expression)methodCallExpression.Update( + Expression.Convert(@object, methodCallExpression.Object.Type), + arguments); + + result = MakeNullable(result); + result = Expression.Condition( + Expression.Equal(@object, Expression.Constant(null, @object.Type)), + Expression.Constant(null, result.Type), + result); + + return result; + } + return methodCallExpression.Update(@object, arguments); } @@ -384,6 +469,68 @@ protected override Expression VisitTypeBinary(TypeBinaryExpression typeBinaryExp return Expression.Constant(false); } + protected override Expression VisitNew(NewExpression newExpression) + { + var newArguments = new List(); + foreach (var argument in newExpression.Arguments) + { + var newArgument = Visit(argument); + if (newArgument.Type != argument.Type) + { + newArgument = Expression.Convert(newArgument, argument.Type); + } + + newArguments.Add(newArgument); + } + + return newExpression.Update(newArguments); + } + + protected override Expression VisitNewArray(NewArrayExpression newArrayExpression) + { + var newExpressions = new List(); + foreach (var expression in newArrayExpression.Expressions) + { + var newExpression = Visit(expression); + if (newExpression.Type != expression.Type) + { + newExpression = Expression.Convert(newExpression, expression.Type); + } + + newExpressions.Add(newExpression); + } + + return newArrayExpression.Update(newExpressions); + } + + protected override Expression VisitMemberInit(MemberInitExpression memberInitExpression) + { + var newExpression = (NewExpression)Visit(memberInitExpression.NewExpression); + var bindings = new List(); + foreach (var binding in memberInitExpression.Bindings) + { + switch (binding) + { + case MemberAssignment memberAssignment: + var expression = Visit(memberAssignment.Expression); + if (expression.Type != memberAssignment.Expression.Type) + { + expression = Expression.Convert(expression, memberAssignment.Expression.Type); + } + + bindings.Add(Expression.Bind(memberAssignment.Member, expression)); + break; + + default: + // TODO: MemberMemberBinding and MemberListBinding + bindings.Add(binding); + break; + } + } + + return memberInitExpression.Update(newExpression, bindings); + } + protected override Expression VisitExtension(Expression extensionExpression) { switch (extensionExpression) @@ -442,7 +589,15 @@ private static T GetParameterValue(QueryContext queryContext, string paramete protected override Expression VisitUnary(UnaryExpression unaryExpression) { - var result = base.VisitUnary(unaryExpression); + var newOperand = Visit(unaryExpression.Operand); + + if (unaryExpression.NodeType == ExpressionType.Convert + && newOperand.Type == unaryExpression.Type) + { + return newOperand; + } + + var result = (Expression)Expression.MakeUnary(unaryExpression.NodeType, newOperand, unaryExpression.Type); if (result is UnaryExpression outerUnary && outerUnary.NodeType == ExpressionType.Convert && outerUnary.Operand is UnaryExpression innerUnary diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryProjectionBindingExpressionVisitor.cs b/src/EFCore.InMemory/Query/Internal/InMemoryProjectionBindingExpressionVisitor.cs index ded09cef614..649095403cf 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryProjectionBindingExpressionVisitor.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryProjectionBindingExpressionVisitor.cs @@ -127,9 +127,17 @@ public override Expression Visit(Expression expression) } var translation = _expressionTranslatingExpressionVisitor.Translate(expression); - return translation == null - ? base.Visit(expression) - : new ProjectionBindingExpression(_queryExpression, _queryExpression.AddToProjection(translation), expression.Type); + if (translation == null) + { + return base.Visit(expression); + } + + if (translation.Type != expression.Type) + { + translation = Expression.Convert(translation, expression.Type); + } + + return new ProjectionBindingExpression(_queryExpression, _queryExpression.AddToProjection(translation), expression.Type); } else { @@ -139,6 +147,11 @@ public override Expression Visit(Expression expression) return null; } + if (translation.Type != expression.Type) + { + translation = Expression.Convert(translation, expression.Type); + } + _projectionMapping[_projectionMembers.Peek()] = translation; return new ProjectionBindingExpression(_queryExpression, _projectionMembers.Peek(), expression.Type); diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs b/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs index 5de9275fde9..a198ddf478e 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs @@ -13,7 +13,7 @@ namespace Microsoft.EntityFrameworkCore.InMemory.Query.Internal { - public partial class InMemoryQueryExpression : Expression + public partial class InMemoryQueryExpression : Expression, IPrintableExpression { private static readonly ConstructorInfo _valueBufferConstructor = typeof(ValueBuffer).GetConstructors().Single(ci => ci.GetParameters().Length == 1); @@ -751,6 +751,31 @@ public virtual void AddSelectMany(InMemoryQueryExpression innerQueryExpression, _projectionMapping = projectionMapping; } + public virtual void Print(ExpressionPrinter expressionPrinter) + { + expressionPrinter.AppendLine(nameof(InMemoryQueryExpression) + ": "); + using (expressionPrinter.Indent()) + { + expressionPrinter.AppendLine(nameof(ServerQueryExpression) + ": "); + using (expressionPrinter.Indent()) + { + expressionPrinter.Visit(ServerQueryExpression); + } + + expressionPrinter.AppendLine("ProjectionMapping:"); + using (expressionPrinter.Indent()) + { + foreach (var projectionMapping in _projectionMapping) + { + expressionPrinter.Append("Member: " + projectionMapping.Key + " Projection: "); + expressionPrinter.Visit(projectionMapping.Value); + } + } + + expressionPrinter.AppendLine(); + } + } + private class NullableReadValueExpressionVisitor : ExpressionVisitor { protected override Expression VisitMethodCall(MethodCallExpression methodCallExpression) diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs index 1944ed1f7ff..d1d8fad4e53 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs @@ -64,7 +64,7 @@ protected override ShapedQueryExpression CreateShapedQueryExpression(Type elemen protected override ShapedQueryExpression TranslateAll(ShapedQueryExpression source, LambdaExpression predicate) { var inMemoryQueryExpression = (InMemoryQueryExpression)source.QueryExpression; - predicate = TranslateLambdaExpression(source, predicate); + predicate = TranslateLambdaExpression(source, predicate, preserveType: true); if (predicate == null) { return null; @@ -93,7 +93,7 @@ protected override ShapedQueryExpression TranslateAny(ShapedQueryExpression sour } else { - predicate = TranslateLambdaExpression(source, predicate); + predicate = TranslateLambdaExpression(source, predicate, preserveType: true); if (predicate == null) { return null; @@ -131,17 +131,23 @@ protected override ShapedQueryExpression TranslateConcat(ShapedQueryExpression s protected override ShapedQueryExpression TranslateContains(ShapedQueryExpression source, Expression item) { var inMemoryQueryExpression = (InMemoryQueryExpression)source.QueryExpression; + var itemType = item.Type; item = TranslateExpression(item); if (item == null) { return null; } + if (item.Type != itemType) + { + item = Expression.Convert(item, itemType); + } + inMemoryQueryExpression.ServerQueryExpression = Expression.Call( - InMemoryLinqOperatorProvider.Contains.MakeGenericMethod(item.Type), + InMemoryLinqOperatorProvider.Contains.MakeGenericMethod(itemType), Expression.Call( - InMemoryLinqOperatorProvider.Select.MakeGenericMethod(inMemoryQueryExpression.CurrentParameter.Type, item.Type), + InMemoryLinqOperatorProvider.Select.MakeGenericMethod(inMemoryQueryExpression.CurrentParameter.Type, itemType), inMemoryQueryExpression.ServerQueryExpression, Expression.Lambda( inMemoryQueryExpression.GetMappedProjection(new ProjectionMember()), inMemoryQueryExpression.CurrentParameter)), @@ -165,7 +171,7 @@ protected override ShapedQueryExpression TranslateCount(ShapedQueryExpression so } else { - predicate = TranslateLambdaExpression(source, predicate); + predicate = TranslateLambdaExpression(source, predicate, preserveType: true); if (predicate == null) { return null; @@ -343,6 +349,10 @@ protected override ShapedQueryExpression TranslateJoin(ShapedQueryExpression out return null; } + var unifyNullabilityResult = UnifyNullability(outerKeySelector, innerKeySelector); + outerKeySelector = unifyNullabilityResult.lambda1; + innerKeySelector = unifyNullabilityResult.lambda2; + var transparentIdentifierType = TransparentIdentifierFactory.Create( resultSelector.Parameters[0].Type, resultSelector.Parameters[1].Type); @@ -360,6 +370,27 @@ protected override ShapedQueryExpression TranslateJoin(ShapedQueryExpression out transparentIdentifierType); } + private (LambdaExpression lambda1, LambdaExpression lambda2) UnifyNullability(LambdaExpression lambda1, LambdaExpression lambda2) + { + if (lambda1.Body.Type != lambda2.Body.Type) + { + if (TypeNullabilityChanged(lambda1.Body.Type, lambda2.Body.Type)) + { + lambda2 = Expression.Lambda(Expression.Convert(lambda2.Body, lambda1.Body.Type), lambda2.Parameters); + } + else if (TypeNullabilityChanged(lambda2.Body.Type, lambda1.Body.Type)) + { + lambda1 = Expression.Lambda(Expression.Convert(lambda1.Body, lambda2.Body.Type), lambda1.Parameters); + } + } + + return (lambda1, lambda2); + } + + // TODO: DRY + private bool TypeNullabilityChanged(Type maybeNullableType, Type nonNullableType) + => maybeNullableType.IsNullableType() && !nonNullableType.IsNullableType() && maybeNullableType.UnwrapNullableType() == nonNullableType; + protected override ShapedQueryExpression TranslateLastOrDefault(ShapedQueryExpression source, LambdaExpression predicate, Type returnType, bool returnDefault) { return TranslateSingleResultOperator( @@ -380,6 +411,10 @@ protected override ShapedQueryExpression TranslateLeftJoin(ShapedQueryExpression return null; } + var unifyNullabilityResult = UnifyNullability(outerKeySelector, innerKeySelector); + outerKeySelector = unifyNullabilityResult.lambda1; + innerKeySelector = unifyNullabilityResult.lambda2; + var transparentIdentifierType = TransparentIdentifierFactory.Create( resultSelector.Parameters[0].Type, resultSelector.Parameters[1].Type); @@ -410,7 +445,7 @@ protected override ShapedQueryExpression TranslateLongCount(ShapedQueryExpressio } else { - predicate = TranslateLambdaExpression(source, predicate); + predicate = TranslateLambdaExpression(source, predicate, preserveType: true); if (predicate == null) { return null; @@ -478,8 +513,8 @@ protected override ShapedQueryExpression TranslateOfType(ShapedQueryExpression s Expression.Constant(derivedDerivedType.GetDiscriminatorValue(), discriminatorProperty.ClrType))); } - var predicate = TranslateLambdaExpression(source, Expression.Lambda(equals, parameter)); - if (predicate == null) + var discriminatorPredicate = TranslateLambdaExpression(source, Expression.Lambda(equals, parameter)); + if (discriminatorPredicate == null) { return null; } @@ -487,7 +522,7 @@ protected override ShapedQueryExpression TranslateOfType(ShapedQueryExpression s inMemoryQueryExpression.ServerQueryExpression = Expression.Call( InMemoryLinqOperatorProvider.Where.MakeGenericMethod(typeof(ValueBuffer)), inMemoryQueryExpression.ServerQueryExpression, - predicate); + discriminatorPredicate); var projectionBindingExpression = (ProjectionBindingExpression)entityShaperExpression.ValueBufferExpression; var projectionMember = projectionBindingExpression.ProjectionMember; @@ -703,7 +738,7 @@ protected override ShapedQueryExpression TranslateUnion(ShapedQueryExpression so protected override ShapedQueryExpression TranslateWhere(ShapedQueryExpression source, LambdaExpression predicate) { var inMemoryQueryExpression = (InMemoryQueryExpression)source.QueryExpression; - predicate = TranslateLambdaExpression(source, predicate); + predicate = TranslateLambdaExpression(source, predicate, preserveType: true); if (predicate == null) { return null; @@ -723,10 +758,21 @@ private Expression TranslateExpression(Expression expression) } private LambdaExpression TranslateLambdaExpression( - ShapedQueryExpression shapedQueryExpression, LambdaExpression lambdaExpression) + ShapedQueryExpression shapedQueryExpression, + LambdaExpression lambdaExpression, + bool preserveType = false) { var lambdaBody = TranslateExpression(RemapLambdaBody(shapedQueryExpression, lambdaExpression)); + if (lambdaBody != null && preserveType) + { + lambdaBody = lambdaBody.Type == typeof(bool?) + ? Expression.Equal( + lambdaBody, + Expression.Constant(true, typeof(bool?))) + : lambdaBody; + } + return lambdaBody != null ? Expression.Lambda(lambdaBody, ((InMemoryQueryExpression)shapedQueryExpression.QueryExpression).CurrentParameter) @@ -744,6 +790,8 @@ private ShapedQueryExpression TranslateScalarAggregate( { var inMemoryQueryExpression = (InMemoryQueryExpression)source.QueryExpression; + var selectorBodyType = selector?.Body.Type; + selector = selector == null || selector.Body == selector.Parameters[0] ? Expression.Lambda( @@ -756,6 +804,11 @@ private ShapedQueryExpression TranslateScalarAggregate( return null; } + if (selectorBodyType != null && selector.Body.Type != selectorBodyType) + { + selector = Expression.Lambda(Expression.Convert(selector.Body, selectorBodyType), selector.Parameters); + } + MethodInfo getMethod() => methodName switch { diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryTableExpression.cs b/src/EFCore.InMemory/Query/Internal/InMemoryTableExpression.cs index 5318823b6e5..e038c490147 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryTableExpression.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryTableExpression.cs @@ -5,11 +5,12 @@ using System.Collections.Generic; using System.Linq.Expressions; using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Query; using Microsoft.EntityFrameworkCore.Storage; namespace Microsoft.EntityFrameworkCore.InMemory.Query.Internal { - public class InMemoryTableExpression : Expression + public class InMemoryTableExpression : Expression, IPrintableExpression { public InMemoryTableExpression(IEntityType entityType) { @@ -26,5 +27,10 @@ protected override Expression VisitChildren(ExpressionVisitor visitor) { return this; } + + public virtual void Print(ExpressionPrinter expressionPrinter) + { + expressionPrinter.Append(nameof(InMemoryTableExpression) + ": Entity: " + EntityType.DisplayName()); + } } } diff --git a/src/EFCore/Query/Internal/SubqueryMemberPushdownExpressionVisitor.cs b/src/EFCore/Query/Internal/SubqueryMemberPushdownExpressionVisitor.cs index 06a73286b76..92925ad8628 100644 --- a/src/EFCore/Query/Internal/SubqueryMemberPushdownExpressionVisitor.cs +++ b/src/EFCore/Query/Internal/SubqueryMemberPushdownExpressionVisitor.cs @@ -52,6 +52,7 @@ protected override Expression VisitMember(MemberExpression memberExpression) (target, nullable) => { var memberAccessExpression = Expression.MakeMemberAccess(target, memberExpression.Member); + return nullable && !memberAccessExpression.Type.IsNullableType() ? Expression.Convert(memberAccessExpression, memberAccessExpression.Type.MakeNullable()) : (Expression)memberAccessExpression; diff --git a/test/EFCore.InMemory.FunctionalTests/Query/ComplexNavigationsQueryInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/ComplexNavigationsQueryInMemoryTest.cs index 86b164eb948..53cce45a98f 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/ComplexNavigationsQueryInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/ComplexNavigationsQueryInMemoryTest.cs @@ -7,12 +7,180 @@ namespace Microsoft.EntityFrameworkCore.Query { - internal class ComplexNavigationsQueryInMemoryTest : ComplexNavigationsQueryTestBase + public class ComplexNavigationsQueryInMemoryTest : ComplexNavigationsQueryTestBase { public ComplexNavigationsQueryInMemoryTest(ComplexNavigationsQueryInMemoryFixture fixture, ITestOutputHelper testOutputHelper) : base(fixture) { //TestLoggerFactory.TestOutputHelper = testOutputHelper; } + + [ConditionalTheory(Skip = "issue #16963")] //DefaultIfEmpty + public override Task Complex_SelectMany_with_nested_navigations_and_explicit_DefaultIfEmpty_with_other_query_operators_composed_on_top(bool isAsync) + { + return base.Complex_SelectMany_with_nested_navigations_and_explicit_DefaultIfEmpty_with_other_query_operators_composed_on_top(isAsync); + } + + [ConditionalTheory(Skip = "issue #16963")] //DefaultIfEmpty + public override Task Multiple_SelectMany_with_navigation_and_explicit_DefaultIfEmpty(bool isAsync) + { + return base.Multiple_SelectMany_with_navigation_and_explicit_DefaultIfEmpty(isAsync); + } + + [ConditionalTheory(Skip = "issue #16963")] //DefaultIfEmpty + public override Task Multiple_SelectMany_with_nested_navigations_and_explicit_DefaultIfEmpty_joined_together(bool isAsync) + { + return base.Multiple_SelectMany_with_nested_navigations_and_explicit_DefaultIfEmpty_joined_together(isAsync); + } + + [ConditionalTheory(Skip = "issue #16963")] //DefaultIfEmpty + public override Task SelectMany_with_navigation_and_explicit_DefaultIfEmpty(bool isAsync) + { + return base.SelectMany_with_navigation_and_explicit_DefaultIfEmpty(isAsync); + } + + [ConditionalTheory(Skip = "issue #16963")] //DefaultIfEmpty + public override Task SelectMany_with_navigation_filter_and_explicit_DefaultIfEmpty(bool isAsync) + { + return base.SelectMany_with_navigation_filter_and_explicit_DefaultIfEmpty(isAsync); + } + + [ConditionalTheory(Skip = "issue #16963")] //DefaultIfEmpty + public override Task SelectMany_with_navigation_filter_paging_and_explicit_DefaultIfEmpty(bool isAsync) + { + return base.SelectMany_with_navigation_filter_paging_and_explicit_DefaultIfEmpty(isAsync); + } + + [ConditionalTheory(Skip = "issue #16963")] //DefaultIfEmpty + public override Task SelectMany_with_nested_navigations_and_explicit_DefaultIfEmpty_followed_by_Select_required_navigation_using_different_navs(bool isAsync) + { + return base.SelectMany_with_nested_navigations_and_explicit_DefaultIfEmpty_followed_by_Select_required_navigation_using_different_navs(isAsync); + } + + [ConditionalTheory(Skip = "issue #16963")] //DefaultIfEmpty + public override Task SelectMany_with_nested_navigations_and_explicit_DefaultIfEmpty_followed_by_Select_required_navigation_using_same_navs(bool isAsync) + { + return base.SelectMany_with_nested_navigations_and_explicit_DefaultIfEmpty_followed_by_Select_required_navigation_using_same_navs(isAsync); + } + + [ConditionalTheory(Skip = "issue #16963")] //DefaultIfEmpty + public override Task SelectMany_with_nested_navigations_explicit_DefaultIfEmpty_and_additional_joins_outside_of_SelectMany(bool isAsync) + { + return base.SelectMany_with_nested_navigations_explicit_DefaultIfEmpty_and_additional_joins_outside_of_SelectMany(isAsync); + } + + [ConditionalTheory(Skip = "issue #16963")] //DefaultIfEmpty + public override Task SelectMany_with_nested_navigations_explicit_DefaultIfEmpty_and_additional_joins_outside_of_SelectMany2(bool isAsync) + { + return base.SelectMany_with_nested_navigations_explicit_DefaultIfEmpty_and_additional_joins_outside_of_SelectMany2(isAsync); + } + + [ConditionalTheory(Skip = "issue #16963")] //DefaultIfEmpty + public override Task SelectMany_with_nested_navigations_explicit_DefaultIfEmpty_and_additional_joins_outside_of_SelectMany3(bool isAsync) + { + return base.SelectMany_with_nested_navigations_explicit_DefaultIfEmpty_and_additional_joins_outside_of_SelectMany3(isAsync); + } + + [ConditionalTheory(Skip = "issue #16963")] //DefaultIfEmpty + public override Task SelectMany_with_nested_navigations_explicit_DefaultIfEmpty_and_additional_joins_outside_of_SelectMany4(bool isAsync) + { + return base.SelectMany_with_nested_navigations_explicit_DefaultIfEmpty_and_additional_joins_outside_of_SelectMany4(isAsync); + } + + [ConditionalTheory(Skip = "issue #16963")] //DefaultIfEmpty + public override Task SelectMany_with_nested_navigation_and_explicit_DefaultIfEmpty(bool isAsync) + { + return base.SelectMany_with_nested_navigation_and_explicit_DefaultIfEmpty(isAsync); + } + + [ConditionalTheory(Skip = "issue #16963")] //DefaultIfEmpty + public override Task SelectMany_with_nested_navigation_filter_and_explicit_DefaultIfEmpty(bool isAsync) + { + return base.SelectMany_with_nested_navigation_filter_and_explicit_DefaultIfEmpty(isAsync); + } + + [ConditionalTheory(Skip = "issue #16963")] //DefaultIfEmpty + public override Task SelectMany_with_nested_required_navigation_filter_and_explicit_DefaultIfEmpty(bool isAsync) + { + return base.SelectMany_with_nested_required_navigation_filter_and_explicit_DefaultIfEmpty(isAsync); + } + + [ConditionalTheory(Skip = "issue #16963")] //GroupBy + public override Task Simple_level1_level2_GroupBy_Count(bool isAsync) + { + return base.Simple_level1_level2_GroupBy_Count(isAsync); + } + + [ConditionalTheory(Skip = "issue #16963")] //GroupBy + public override Task Simple_level1_level2_GroupBy_Having_Count(bool isAsync) + { + return base.Simple_level1_level2_GroupBy_Having_Count(isAsync); + } + + [ConditionalTheory(Skip = "issue #17386")] + public override Task Complex_query_with_optional_navigations_and_client_side_evaluation(bool isAsync) + { + return base.Complex_query_with_optional_navigations_and_client_side_evaluation(isAsync); + } + + [ConditionalTheory(Skip = "issue #17453")] + public override Task Project_collection_navigation_nested(bool isAsync) + { + return base.Project_collection_navigation_nested(isAsync); + } + + [ConditionalTheory(Skip = "issue #17453")] + public override Task Project_collection_navigation_nested_anonymous(bool isAsync) + { + return base.Project_collection_navigation_nested_anonymous(isAsync); + } + + [ConditionalTheory(Skip = "issue #17453")] + public override Task Project_collection_navigation_using_ef_property(bool isAsync) + { + return base.Project_collection_navigation_using_ef_property(isAsync); + } + + [ConditionalTheory(Skip = "issue #17453")] + public override Task Project_navigation_and_collection(bool isAsync) + { + return base.Project_navigation_and_collection(isAsync); + } + + [ConditionalTheory(Skip = "issue #17453")] + public override Task SelectMany_nested_navigation_property_optional_and_projection(bool isAsync) + { + return base.SelectMany_nested_navigation_property_optional_and_projection(isAsync); + } + + [ConditionalTheory(Skip = "issue #17453")] + public override Task SelectMany_nested_navigation_property_required(bool isAsync) + { + return base.SelectMany_nested_navigation_property_required(isAsync); + } + + [ConditionalTheory(Skip = "issue #17460")] + public override Task Where_complex_predicate_with_with_nav_prop_and_OrElse4(bool isAsync) + { + return base.Where_complex_predicate_with_with_nav_prop_and_OrElse4(isAsync); + } + + [ConditionalTheory(Skip = "issue #17460")] + public override Task Join_flattening_bug_4539(bool isAsync) + { + return base.Join_flattening_bug_4539(isAsync); + } + + [ConditionalTheory(Skip = "issue #17463")] + public override Task Include18_3_3(bool isAsync) + { + return base.Include18_3_3(isAsync); + } + + [ConditionalFact(Skip = "issue #17463")] + public override void Include19() + { + base.Include19(); + } } } diff --git a/test/EFCore.InMemory.FunctionalTests/Query/GearsOfWarQueryInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/GearsOfWarQueryInMemoryTest.cs index 75f037b9809..050d95bd736 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/GearsOfWarQueryInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/GearsOfWarQueryInMemoryTest.cs @@ -7,12 +7,114 @@ namespace Microsoft.EntityFrameworkCore.Query { - internal class GearsOfWarQueryInMemoryTest : GearsOfWarQueryTestBase + public class GearsOfWarQueryInMemoryTest : GearsOfWarQueryTestBase { public GearsOfWarQueryInMemoryTest(GearsOfWarQueryInMemoryFixture fixture, ITestOutputHelper testOutputHelper) : base(fixture) { //TestLoggerFactory.TestOutputHelper = testOutputHelper; } + + [ConditionalTheory(Skip = "issue #16963")] // groupby + public override Task GroupBy_Property_Include_Aggregate_with_anonymous_selector(bool isAsync) + { + return base.GroupBy_Property_Include_Aggregate_with_anonymous_selector(isAsync); + } + + [ConditionalTheory(Skip = "issue #16963")] // groupby + public override Task GroupBy_Property_Include_Select_Count(bool isAsync) + { + return base.GroupBy_Property_Include_Select_Count(isAsync); + } + + [ConditionalTheory(Skip = "issue #16963")] // groupby + public override Task GroupBy_Property_Include_Select_LongCount(bool isAsync) + { + return base.GroupBy_Property_Include_Select_LongCount(isAsync); + } + + [ConditionalTheory(Skip = "issue #16963")] // groupby + public override Task GroupBy_Property_Include_Select_Max(bool isAsync) + { + return base.GroupBy_Property_Include_Select_Max(isAsync); + } + + [ConditionalTheory(Skip = "issue #16963")] // groupby + public override Task GroupBy_Property_Include_Select_Min(bool isAsync) + { + return base.GroupBy_Property_Include_Select_Min(isAsync); + } + + [ConditionalTheory(Skip = "issue #17386")] + public override Task Correlated_collection_order_by_constant_null_of_non_mapped_type(bool isAsync) + { + return base.Correlated_collection_order_by_constant_null_of_non_mapped_type(isAsync); + } + + [ConditionalTheory(Skip = "issue #17386")] + public override Task Client_side_equality_with_parameter_works_with_optional_navigations(bool isAsync) + { + return base.Client_side_equality_with_parameter_works_with_optional_navigations(isAsync); + } + + [ConditionalTheory(Skip = "issue #17386")] + public override Task Where_coalesce_with_anonymous_types(bool isAsync) + { + return base.Where_coalesce_with_anonymous_types(isAsync); + } + + [ConditionalTheory(Skip = "issue #17386")] + public override Task Where_conditional_with_anonymous_type(bool isAsync) + { + return base.Where_conditional_with_anonymous_type(isAsync); + } + + [ConditionalTheory(Skip = "issue #17386")] + public override Task GetValueOrDefault_on_DateTimeOffset(bool isAsync) + { + return base.GetValueOrDefault_on_DateTimeOffset(isAsync); + } + + [ConditionalTheory(Skip = "issue #17453")] + public override Task Correlated_collection_with_complex_OrderBy(bool isAsync) + { + return base.Correlated_collection_with_complex_OrderBy(isAsync); + } + + [ConditionalTheory(Skip = "issue #17453")] + public override Task Correlated_collection_with_very_complex_order_by(bool isAsync) + { + return base.Correlated_collection_with_very_complex_order_by(isAsync); + } + + [ConditionalTheory(Skip = "issue #17463")] + public override Task Include_collection_OrderBy_aggregate(bool isAsync) + { + return base.Include_collection_OrderBy_aggregate(isAsync); + } + + [ConditionalTheory(Skip = "issue #17463")] + public override Task Include_collection_with_complex_OrderBy3(bool isAsync) + { + return base.Include_collection_with_complex_OrderBy3(isAsync); + } + + [ConditionalTheory(Skip = "issue #17463")] + public override void Include_on_GroupJoin_SelectMany_DefaultIfEmpty_with_coalesce_result1() + { + base.Include_on_GroupJoin_SelectMany_DefaultIfEmpty_with_coalesce_result1(); + } + + [ConditionalTheory(Skip = "issue #17463")] + public override void Include_on_GroupJoin_SelectMany_DefaultIfEmpty_with_coalesce_result2() + { + base.Include_on_GroupJoin_SelectMany_DefaultIfEmpty_with_coalesce_result2(); + } + + [ConditionalTheory(Skip = "issue #16963")] //length + public override Task Null_semantics_is_correctly_applied_for_function_comparisons_that_take_arguments_from_optional_navigation_complex(bool isAsync) + { + return base.Null_semantics_is_correctly_applied_for_function_comparisons_that_take_arguments_from_optional_navigation_complex(isAsync); + } } } diff --git a/test/EFCore.InMemory.FunctionalTests/Query/QueryFilterFuncletizationInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/QueryFilterFuncletizationInMemoryTest.cs index d4a85fa9f7f..f2ed8b793de 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/QueryFilterFuncletizationInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/QueryFilterFuncletizationInMemoryTest.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.EntityFrameworkCore.TestUtilities; +using Xunit; using Xunit.Abstractions; namespace Microsoft.EntityFrameworkCore.Query @@ -19,5 +20,11 @@ public class QueryFilterFuncletizationInMemoryFixture : QueryFilterFuncletizatio { protected override ITestStoreFactory TestStoreFactory => InMemoryTestStoreFactory.Instance; } + + [ConditionalFact(Skip = "issue #17386")] + public override void DbContext_list_is_parameterized() + { + base.DbContext_list_is_parameterized(); + } } } diff --git a/test/EFCore.InMemory.FunctionalTests/Query/SimpleQueryInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/SimpleQueryInMemoryTest.cs index b643a6ffa45..ee8f6df16b0 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/SimpleQueryInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/SimpleQueryInMemoryTest.cs @@ -2,7 +2,10 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Dynamic; +using System.Linq; using System.Runtime.InteropServices.WindowsRuntime; +using System.Text.RegularExpressions; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore.TestUtilities; using Xunit; @@ -201,5 +204,11 @@ public override Task Default_if_empty_top_level_projection(bool isAsync) } #endregion + + [ConditionalTheory(Skip = "issue #17386")] + public override Task Where_equals_on_null_nullable_int_types(bool isAsync) + { + return base.Where_equals_on_null_nullable_int_types(isAsync); + } } } diff --git a/test/EFCore.InMemory.FunctionalTests/Query/SpatialQueryInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/SpatialQueryInMemoryTest.cs index a8a7f1c5219..cbdbfeaba0d 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/SpatialQueryInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/SpatialQueryInMemoryTest.cs @@ -1,9 +1,6 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System.Threading.Tasks; -using Xunit; - namespace Microsoft.EntityFrameworkCore.Query { public class SpatialQueryInMemoryTest : SpatialQueryTestBase @@ -12,125 +9,5 @@ public SpatialQueryInMemoryTest(SpatialQueryInMemoryFixture fixture) : base(fixture) { } - - [ConditionalTheory(Skip = "Issue #16963. Nullable error")] - public override Task Area(bool isAsync) - => base.Area(isAsync); - - [ConditionalTheory(Skip = "Issue #16963. Nullable error")] - public override Task Boundary(bool isAsync) - => base.Boundary(isAsync); - - [ConditionalTheory(Skip = "Issue #16963. Nullable error")] - public override Task Centroid(bool isAsync) - => base.Centroid(isAsync); - - [ConditionalTheory(Skip = "Issue #16963. Nullable error")] - public override Task Dimension(bool isAsync) - => base.Dimension(isAsync); - - [ConditionalTheory(Skip = "Issue #16963. Nullable error")] - public override Task EndPoint(bool isAsync) - => base.EndPoint(isAsync); - - [ConditionalTheory(Skip = "Issue #16963. Nullable error")] - public override Task Envelope(bool isAsync) - => base.Envelope(isAsync); - - [ConditionalTheory(Skip = "Issue #16963. Nullable error")] - public override Task ExteriorRing(bool isAsync) - => base.ExteriorRing(isAsync); - - [ConditionalTheory(Skip = "Issue #16963. Nullable error")] - public override Task GeometryType(bool isAsync) - => base.GeometryType(isAsync); - - [ConditionalTheory(Skip = "Issue #16963. Nullable error")] - public override Task ICurve_IsClosed(bool isAsync) - => base.ICurve_IsClosed(isAsync); - - [ConditionalTheory(Skip = "Issue #16963. Nullable error")] - public override Task IGeometryCollection_Count(bool isAsync) - => base.IGeometryCollection_Count(isAsync); - - [ConditionalTheory(Skip = "Issue #16963. Nullable error")] - public override Task IMultiCurve_IsClosed(bool isAsync) - => base.IMultiCurve_IsClosed(isAsync); - - [ConditionalTheory(Skip = "Issue #16963. Nullable error")] - public override Task InteriorPoint(bool isAsync) - => base.InteriorPoint(isAsync); - - [ConditionalTheory(Skip = "Issue #16963. Nullable error")] - public override Task IsEmpty(bool isAsync) - => base.IsEmpty(isAsync); - - [ConditionalTheory(Skip = "Issue #16963. Nullable error")] - public override Task IsRing(bool isAsync) - => base.IsRing(isAsync); - - [ConditionalTheory(Skip = "Issue #16963. Nullable error")] - public override Task IsSimple(bool isAsync) - => base.IsSimple(isAsync); - - [ConditionalTheory(Skip = "Issue #16963. Nullable error")] - public override Task IsValid(bool isAsync) - => base.IsValid(isAsync); - - [ConditionalTheory(Skip = "Issue #16963. Nullable error")] - public override Task Length(bool isAsync) - => base.Length(isAsync); - - [ConditionalTheory(Skip = "Issue #16963. Nullable error")] - public override Task LineString_Count(bool isAsync) - => base.LineString_Count(isAsync); - - [ConditionalTheory(Skip = "Issue #16963. Nullable error")] - public override Task M(bool isAsync) - => base.M(isAsync); - - [ConditionalTheory(Skip = "Issue #16963. Nullable error")] - public override Task NumGeometries(bool isAsync) - => base.NumGeometries(isAsync); - - [ConditionalTheory(Skip = "Issue #16963. Nullable error")] - public override Task NumInteriorRings(bool isAsync) - => base.NumInteriorRings(isAsync); - - [ConditionalTheory(Skip = "Issue #16963. Nullable error")] - public override Task NumPoints(bool isAsync) - => base.NumPoints(isAsync); - - [ConditionalTheory(Skip = "Issue #16963. Nullable error")] - public override Task OgcGeometryType(bool isAsync) - => base.OgcGeometryType(isAsync); - - [ConditionalTheory(Skip = "Issue #16963. Nullable error")] - public override Task PointOnSurface(bool isAsync) - => base.PointOnSurface(isAsync); - - [ConditionalTheory(Skip = "Issue #16963. Nullable error")] - public override Task SRID(bool isAsync) - => base.SRID(isAsync); - - [ConditionalTheory(Skip = "Issue #16963. Nullable error")] - public override Task SRID_geometry(bool isAsync) - => base.SRID_geometry(isAsync); - - [ConditionalTheory(Skip = "Issue #16963. Nullable error")] - public override Task StartPoint(bool isAsync) - => base.StartPoint(isAsync); - - [ConditionalTheory(Skip = "Issue #16963. Nullable error")] - public override Task X(bool isAsync) - => base.X(isAsync); - - [ConditionalTheory(Skip = "Issue #16963. Nullable error")] - public override Task Y(bool isAsync) - => base.Y(isAsync); - - [ConditionalTheory(Skip = "Issue #16963. Nullable error")] - public override Task Z(bool isAsync) - => base.Z(isAsync); } } From 93abe3bf937a216137057a010316fef55745b596 Mon Sep 17 00:00:00 2001 From: Smit Patel Date: Wed, 28 Aug 2019 19:03:06 -0700 Subject: [PATCH 15/21] InMemory: Some refactorings Part of #16963 --- ...yExpressionTranslatingExpressionVisitor.cs | 105 ++++++++---------- .../Internal/InMemoryLinqOperatorProvider.cs | 15 ++- .../Query/Internal/InMemoryQueryExpression.cs | 6 +- ...yableMethodTranslatingExpressionVisitor.cs | 82 ++++++-------- 4 files changed, 98 insertions(+), 110 deletions(-) diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs b/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs index 2c06d662493..a9e5c8973e8 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs @@ -78,11 +78,11 @@ protected override Expression VisitBinary(BinaryExpression binaryExpression) return null; } - if (TypeNullabilityChanged(newLeft.Type, binaryExpression.Left.Type) - || TypeNullabilityChanged(newRight.Type, binaryExpression.Right.Type)) + if (IsConvertedToNullable(newLeft, binaryExpression.Left) + || IsConvertedToNullable(newRight, binaryExpression.Right)) { - newLeft = MakeNullable(newLeft); - newRight = MakeNullable(newRight); + newLeft = ConvertToNullable(newLeft); + newRight = ConvertToNullable(newRight); } return Expression.MakeBinary( @@ -94,11 +94,6 @@ protected override Expression VisitBinary(BinaryExpression binaryExpression) binaryExpression.Conversion); } - private static Expression MakeNullable(Expression expression) - => !expression.Type.IsNullableType() - ? Expression.Convert(expression, expression.Type.MakeNullable()) - : expression; - protected override Expression VisitConditional(ConditionalExpression conditionalExpression) { var test = Visit(conditionalExpression.Test); @@ -115,11 +110,11 @@ protected override Expression VisitConditional(ConditionalExpression conditional test = Expression.Equal(test, Expression.Constant(true, typeof(bool?))); } - if (TypeNullabilityChanged(ifTrue.Type, conditionalExpression.IfTrue.Type) - || TypeNullabilityChanged(ifFalse.Type, conditionalExpression.IfFalse.Type)) + if (IsConvertedToNullable(ifTrue, conditionalExpression.IfTrue) + || IsConvertedToNullable(ifFalse, conditionalExpression.IfFalse)) { - ifTrue = MakeNullable(ifTrue); - ifFalse = MakeNullable(ifFalse); + ifTrue = ConvertToNullable(ifTrue); + ifFalse = ConvertToNullable(ifFalse); } return Expression.Condition(test, ifTrue, ifFalse); @@ -142,12 +137,17 @@ protected override Expression VisitMember(MemberExpression memberExpression) return result; } + static bool shouldApplyNullProtectionForMemberAccess(Type callerType, string memberName) + => !(callerType.IsGenericType + && callerType.GetGenericTypeDefinition() == typeof(Nullable<>) + && (memberName == nameof(Nullable.Value) || memberName == nameof(Nullable.HasValue))); + var updatedMemberExpression = (Expression)memberExpression.Update(innerExpression); if (innerExpression != null && innerExpression.Type.IsNullableType() - && ShouldApplyNullProtectionForMemberAccess(innerExpression.Type, memberExpression.Member.Name)) + && shouldApplyNullProtectionForMemberAccess(innerExpression.Type, memberExpression.Member.Name)) { - updatedMemberExpression = MakeNullable(updatedMemberExpression); + updatedMemberExpression = ConvertToNullable(updatedMemberExpression); return Expression.Condition( Expression.Equal(innerExpression, Expression.Default(innerExpression.Type)), @@ -158,11 +158,6 @@ protected override Expression VisitMember(MemberExpression memberExpression) return updatedMemberExpression; } - private bool ShouldApplyNullProtectionForMemberAccess(Type callerType, string memberName) - => !(callerType.IsGenericType - && callerType.GetGenericTypeDefinition() == typeof(Nullable<>) - && (memberName == nameof(Nullable.Value) || memberName == nameof(Nullable.HasValue))); - private bool TryBindMember(Expression source, MemberIdentity memberIdentity, Type type, out Expression result) { result = null; @@ -204,7 +199,10 @@ private bool TryBindMember(Expression source, MemberIdentity memberIdentity, Typ result = BindProperty(entityProjection, property); // if the result type change was just nullability change e.g from int to int? we want to preserve the new type for null propagation - if (result.Type != type && !TypeNullabilityChanged(result.Type, type)) + if (result.Type != type + && !(result.Type.IsNullableType() + && !type.IsNullableType() + && result.Type.UnwrapNullableType() == type)) { result = Expression.Convert(result, type); } @@ -215,13 +213,23 @@ private bool TryBindMember(Expression source, MemberIdentity memberIdentity, Typ return false; } - private bool TypeNullabilityChanged(Type maybeNullableType, Type nonNullableType) - => maybeNullableType.IsNullableType() && !nonNullableType.IsNullableType() && maybeNullableType.UnwrapNullableType() == nonNullableType; + private static bool IsConvertedToNullable(Expression result, Expression original) + => result.Type.IsNullableType() + && !original.Type.IsNullableType() + && result.Type.UnwrapNullableType() == original.Type; + + private static Expression ConvertToNullable(Expression expression) + => !expression.Type.IsNullableType() + ? Expression.Convert(expression, expression.Type.MakeNullable()) + : expression; + + private static Expression ConvertToNonNullable(Expression expression) + => expression.Type.IsNullableType() + ? Expression.Convert(expression, expression.Type.UnwrapNullableType()) + : expression; private static Expression BindProperty(EntityProjectionExpression entityProjectionExpression, IProperty property) - { - return entityProjectionExpression.BindProperty(property); - } + => entityProjectionExpression.BindProperty(property); private static Expression GetSelector(MethodCallExpression methodCallExpression, GroupByShaperExpression groupByShaperExpression) { @@ -387,7 +395,7 @@ MethodInfo getMethod() } var arguments = new Expression[methodCallExpression.Arguments.Count]; - var parameterTypes = methodCallExpression.Method.GetParameters().Select(p => p.ParameterType).ToArray(); + var parameterTypes = methodCallExpression.Method.GetParameters().Select(p => p.ParameterType).ToArray(); for (var i = 0; i < arguments.Length; i++) { var argument = Visit(methodCallExpression.Arguments[i]); @@ -398,10 +406,10 @@ MethodInfo getMethod() // if the nullability of arguments change, we have no easy/reliable way to adjust the actual methodInfo to match the new type, // so we are forced to cast back to the original type - if (argument.Type != methodCallExpression.Arguments[i].Type + if (IsConvertedToNullable(argument, methodCallExpression.Arguments[i]) && !parameterTypes[i].IsAssignableFrom(argument.Type)) { - argument = Expression.Convert(argument, methodCallExpression.Arguments[i].Type); + argument = ConvertToNonNullable(argument); } arguments[i] = argument; @@ -413,11 +421,11 @@ MethodInfo getMethod() && @object.Type.IsNullableType() && !(methodCallExpression.Method.Name == nameof(Nullable.GetValueOrDefault))) { - var result = (Expression)methodCallExpression.Update( + var result = (Expression)methodCallExpression.Update( Expression.Convert(@object, methodCallExpression.Object.Type), arguments); - result = MakeNullable(result); + result = ConvertToNullable(result); result = Expression.Condition( Expression.Equal(@object, Expression.Constant(null, @object.Type)), Expression.Constant(null, result.Type), @@ -475,9 +483,9 @@ protected override Expression VisitNew(NewExpression newExpression) foreach (var argument in newExpression.Arguments) { var newArgument = Visit(argument); - if (newArgument.Type != argument.Type) + if (IsConvertedToNullable(newArgument, argument)) { - newArgument = Expression.Convert(newArgument, argument.Type); + newArgument = ConvertToNonNullable(newArgument); } newArguments.Add(newArgument); @@ -492,9 +500,9 @@ protected override Expression VisitNewArray(NewArrayExpression newArrayExpressio foreach (var expression in newArrayExpression.Expressions) { var newExpression = Visit(expression); - if (newExpression.Type != expression.Type) + if (IsConvertedToNullable(newExpression, expression)) { - newExpression = Expression.Convert(newExpression, expression.Type); + newExpression = ConvertToNonNullable(newExpression); } newExpressions.Add(newExpression); @@ -503,32 +511,15 @@ protected override Expression VisitNewArray(NewArrayExpression newArrayExpressio return newArrayExpression.Update(newExpressions); } - protected override Expression VisitMemberInit(MemberInitExpression memberInitExpression) + protected override MemberAssignment VisitMemberAssignment(MemberAssignment memberAssignment) { - var newExpression = (NewExpression)Visit(memberInitExpression.NewExpression); - var bindings = new List(); - foreach (var binding in memberInitExpression.Bindings) + var expression = Visit(memberAssignment.Expression); + if (IsConvertedToNullable(expression, memberAssignment.Expression)) { - switch (binding) - { - case MemberAssignment memberAssignment: - var expression = Visit(memberAssignment.Expression); - if (expression.Type != memberAssignment.Expression.Type) - { - expression = Expression.Convert(expression, memberAssignment.Expression.Type); - } - - bindings.Add(Expression.Bind(memberAssignment.Member, expression)); - break; - - default: - // TODO: MemberMemberBinding and MemberListBinding - bindings.Add(binding); - break; - } + expression = ConvertToNonNullable(expression); } - return memberInitExpression.Update(newExpression, bindings); + return memberAssignment.Update(expression); } protected override Expression VisitExtension(Expression extensionExpression) diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryLinqOperatorProvider.cs b/src/EFCore.InMemory/Query/Internal/InMemoryLinqOperatorProvider.cs index 3d7f53aee47..34b561f941a 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryLinqOperatorProvider.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryLinqOperatorProvider.cs @@ -108,9 +108,20 @@ public static MethodInfo GetMinWithSelector(Type type) private static Dictionary SumWithoutSelectorMethods { get; } private static Dictionary SumWithSelectorMethods { get; } - private static bool IsFunc(Type type, int funcGenericArgs = 2) + private static Type GetFuncType(int funcGenericArguments) + { + return funcGenericArguments switch + { + 1 => typeof(Func<>), + 2 => typeof(Func<,>), + 3 => typeof(Func<,,>), + 4 => typeof(Func<,,,>), + _ => throw new InvalidOperationException("Invalid number of arguments for Func"), + }; + } + private static bool IsFunc(Type type, int funcGenericArguments = 2) => type.IsGenericType - && type.GetGenericArguments().Length == funcGenericArgs; + && type.GetGenericTypeDefinition() == GetFuncType(funcGenericArguments); static InMemoryLinqOperatorProvider() { diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs b/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs index a198ddf478e..d6b8c8fb669 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs @@ -51,8 +51,8 @@ public InMemoryQueryExpression(IEntityType entityType) Constant(property.GetIndex()), MakeMemberAccess(_valueBufferParameter, _valueBufferCountMemberInfo)), - CreateReadValueExpression(typeof(object), property.GetIndex(), property), - Default(typeof(object))); + CreateReadValueExpression(property.ClrType, property.GetIndex(), property), + Default(property.ClrType)); } var entityProjection = new EntityProjectionExpression(entityType, readExpressionMap); @@ -256,7 +256,7 @@ public virtual void ApplyDefaultIfEmpty() { if (_valueBufferSlots.Count != 0) { - throw new InvalidOperationException("Cannot Apply DefaultIfEmpty after ClientProjection."); + throw new InvalidOperationException("Cannot apply DefaultIfEmpty after a client-evaluated projection."); } var result = new Dictionary(); diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs index d1d8fad4e53..39e405bddba 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs @@ -6,8 +6,6 @@ using System.Linq; using System.Linq.Expressions; using System.Reflection; -using Microsoft.EntityFrameworkCore.Diagnostics; -using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Query; @@ -131,23 +129,17 @@ protected override ShapedQueryExpression TranslateConcat(ShapedQueryExpression s protected override ShapedQueryExpression TranslateContains(ShapedQueryExpression source, Expression item) { var inMemoryQueryExpression = (InMemoryQueryExpression)source.QueryExpression; - var itemType = item.Type; - item = TranslateExpression(item); + item = TranslateExpression(item, preserveType: true); if (item == null) { return null; } - if (item.Type != itemType) - { - item = Expression.Convert(item, itemType); - } - inMemoryQueryExpression.ServerQueryExpression = Expression.Call( - InMemoryLinqOperatorProvider.Contains.MakeGenericMethod(itemType), + InMemoryLinqOperatorProvider.Contains.MakeGenericMethod(item.Type), Expression.Call( - InMemoryLinqOperatorProvider.Select.MakeGenericMethod(inMemoryQueryExpression.CurrentParameter.Type, itemType), + InMemoryLinqOperatorProvider.Select.MakeGenericMethod(inMemoryQueryExpression.CurrentParameter.Type, item.Type), inMemoryQueryExpression.ServerQueryExpression, Expression.Lambda( inMemoryQueryExpression.GetMappedProjection(new ProjectionMember()), inMemoryQueryExpression.CurrentParameter)), @@ -349,9 +341,7 @@ protected override ShapedQueryExpression TranslateJoin(ShapedQueryExpression out return null; } - var unifyNullabilityResult = UnifyNullability(outerKeySelector, innerKeySelector); - outerKeySelector = unifyNullabilityResult.lambda1; - innerKeySelector = unifyNullabilityResult.lambda2; + (outerKeySelector, innerKeySelector) = AlignKeySelectorTypes(outerKeySelector, innerKeySelector); var transparentIdentifierType = TransparentIdentifierFactory.Create( resultSelector.Parameters[0].Type, @@ -370,27 +360,31 @@ protected override ShapedQueryExpression TranslateJoin(ShapedQueryExpression out transparentIdentifierType); } - private (LambdaExpression lambda1, LambdaExpression lambda2) UnifyNullability(LambdaExpression lambda1, LambdaExpression lambda2) + private (LambdaExpression OuterKeySelector, LambdaExpression InnerKeySelector) + AlignKeySelectorTypes(LambdaExpression outerKeySelector, LambdaExpression innerKeySelector) { - if (lambda1.Body.Type != lambda2.Body.Type) + static bool isConvertedToNullable(Expression outer, Expression inner) + => outer.Type.IsNullableType() + && !inner.Type.IsNullableType() + && outer.Type.UnwrapNullableType() == inner.Type; + + if (outerKeySelector.Body.Type != innerKeySelector.Body.Type) { - if (TypeNullabilityChanged(lambda1.Body.Type, lambda2.Body.Type)) + if (isConvertedToNullable(outerKeySelector.Body, innerKeySelector.Body)) { - lambda2 = Expression.Lambda(Expression.Convert(lambda2.Body, lambda1.Body.Type), lambda2.Parameters); + innerKeySelector = Expression.Lambda( + Expression.Convert(innerKeySelector.Body, outerKeySelector.Body.Type), innerKeySelector.Parameters); } - else if (TypeNullabilityChanged(lambda2.Body.Type, lambda1.Body.Type)) + else if (isConvertedToNullable(innerKeySelector.Body, outerKeySelector.Body)) { - lambda1 = Expression.Lambda(Expression.Convert(lambda1.Body, lambda2.Body.Type), lambda1.Parameters); + outerKeySelector = Expression.Lambda( + Expression.Convert(outerKeySelector.Body, innerKeySelector.Body.Type), outerKeySelector.Parameters); } } - return (lambda1, lambda2); + return (outerKeySelector, innerKeySelector); } - // TODO: DRY - private bool TypeNullabilityChanged(Type maybeNullableType, Type nonNullableType) - => maybeNullableType.IsNullableType() && !nonNullableType.IsNullableType() && maybeNullableType.UnwrapNullableType() == nonNullableType; - protected override ShapedQueryExpression TranslateLastOrDefault(ShapedQueryExpression source, LambdaExpression predicate, Type returnType, bool returnDefault) { return TranslateSingleResultOperator( @@ -411,9 +405,7 @@ protected override ShapedQueryExpression TranslateLeftJoin(ShapedQueryExpression return null; } - var unifyNullabilityResult = UnifyNullability(outerKeySelector, innerKeySelector); - outerKeySelector = unifyNullabilityResult.lambda1; - innerKeySelector = unifyNullabilityResult.lambda2; + (outerKeySelector, innerKeySelector) = AlignKeySelectorTypes(outerKeySelector, innerKeySelector); var transparentIdentifierType = TransparentIdentifierFactory.Create( resultSelector.Parameters[0].Type, @@ -752,9 +744,19 @@ protected override ShapedQueryExpression TranslateWhere(ShapedQueryExpression so return source; } - private Expression TranslateExpression(Expression expression) + private Expression TranslateExpression(Expression expression, bool preserveType = false) { - return _expressionTranslator.Translate(expression); + var result = _expressionTranslator.Translate(expression); + + if (expression != null && result != null + && preserveType && expression.Type != result.Type) + { + result = expression.Type == typeof(bool) + ? Expression.Equal(result, Expression.Constant(true, result.Type)) + : (Expression)Expression.Convert(result, expression.Type); + } + + return result; } private LambdaExpression TranslateLambdaExpression( @@ -762,16 +764,7 @@ private LambdaExpression TranslateLambdaExpression( LambdaExpression lambdaExpression, bool preserveType = false) { - var lambdaBody = TranslateExpression(RemapLambdaBody(shapedQueryExpression, lambdaExpression)); - - if (lambdaBody != null && preserveType) - { - lambdaBody = lambdaBody.Type == typeof(bool?) - ? Expression.Equal( - lambdaBody, - Expression.Constant(true, typeof(bool?))) - : lambdaBody; - } + var lambdaBody = TranslateExpression(RemapLambdaBody(shapedQueryExpression, lambdaExpression), preserveType); return lambdaBody != null ? Expression.Lambda(lambdaBody, @@ -790,25 +783,18 @@ private ShapedQueryExpression TranslateScalarAggregate( { var inMemoryQueryExpression = (InMemoryQueryExpression)source.QueryExpression; - var selectorBodyType = selector?.Body.Type; - selector = selector == null || selector.Body == selector.Parameters[0] ? Expression.Lambda( inMemoryQueryExpression.GetMappedProjection(new ProjectionMember()), inMemoryQueryExpression.CurrentParameter) - : TranslateLambdaExpression(source, selector); + : TranslateLambdaExpression(source, selector, preserveType: true); if (selector == null) { return null; } - if (selectorBodyType != null && selector.Body.Type != selectorBodyType) - { - selector = Expression.Lambda(Expression.Convert(selector.Body, selectorBodyType), selector.Parameters); - } - MethodInfo getMethod() => methodName switch { From 3aa284e2e0fcdb6f526831bcb05f0ea948edc2c9 Mon Sep 17 00:00:00 2001 From: Smit Patel Date: Thu, 29 Aug 2019 10:40:48 -0700 Subject: [PATCH 16/21] Update query logging tests Filed #17498 to add logging Resolves #17245 --- .../Query/QueryLoggingSqlServerTest.cs | 113 ++++++------------ 1 file changed, 39 insertions(+), 74 deletions(-) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/QueryLoggingSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/QueryLoggingSqlServerTest.cs index ba5545bffbd..81b755b7ea1 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/QueryLoggingSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/QueryLoggingSqlServerTest.cs @@ -15,7 +15,6 @@ // ReSharper disable InconsistentNaming namespace Microsoft.EntityFrameworkCore.Query { - // Issue #16963 public class QueryLoggingSqlServerTest : IClassFixture { private static readonly string _eol = Environment.NewLine; @@ -65,93 +64,58 @@ var customers } } - //[ConditionalFact] - //public virtual void Query_with_ignored_include_should_log_warning() - //{ - // using (var context = CreateContext()) - // { - // var customers - // = context.Customers - // .Include(c => c.Orders) - // .Select(c => c.CustomerID) - // .ToList(); - - // Assert.NotNull(customers); - // Assert.Contains( - // CoreResources.LogIgnoredInclude(new TestLogger()).GenerateMessage("[c].Orders"), Fixture.TestSqlLoggerFactory.Log.Select(l => l.Message)); - // } - //} - - //[ConditionalFact] - //public virtual void Include_navigation() - //{ - // using (var context = CreateContext()) - // { - // var customers - // = context.Set() - // .Include(c => c.Orders) - // .ToList(); - - // Assert.NotNull(customers); - - // Assert.Equal( - // "Compiling query model: " + _eol + - // "'(from Customer c in DbSet" + _eol + - // @"select [c]).Include(""Orders"")'" - // , - // Fixture.TestSqlLoggerFactory.Log[0].Message); - // Assert.Equal( - // "Including navigation: '[c].Orders'" - // , - // Fixture.TestSqlLoggerFactory.Log[1].Message); - // Assert.StartsWith( - // "Optimized query model: " + _eol + - // "'from Customer c in DbSet" + _eol + - // @"order by EF.Property(?[c]?, ""CustomerID"") asc" + _eol + - // "select Customer _Include(" - // , - // Fixture.TestSqlLoggerFactory.Log[2].Message); - // } - //} - -#pragma warning disable CS0612 // Type or member is obsolete - [ConditionalFact(Skip = "Issue #17245")] - public virtual void Concat_Include_collection_ignored() + [ConditionalFact(Skip = "Issue#17498")] + public virtual void Query_with_ignored_include_should_log_warning() { using (var context = CreateContext()) { - var orders = context.Orders - .Where(o => o.OrderID < 10250) - .Concat(context.Orders.Where(o => o.CustomerID == "ALFKI")) - .Include(o => o.OrderDetails) - .ToList(); + var customers + = context.Customers + .Include(c => c.Orders) + .Select(c => c.CustomerID) + .ToList(); - Assert.NotNull(orders); + Assert.NotNull(customers); Assert.Contains( - CoreResources.LogIgnoredInclude(new TestLogger()).GenerateMessage("[o].OrderDetails"), - Fixture.TestSqlLoggerFactory.Log.Select(l => l.Message)); +#pragma warning disable CS0612 // Type or member is obsolete + CoreResources.LogIgnoredInclude(new TestLogger()).GenerateMessage("[c].Orders"), Fixture.TestSqlLoggerFactory.Log.Select(l => l.Message)); +#pragma warning restore CS0612 // Type or member is obsolete } } - [ConditionalFact(Skip = "Issue #17245")] - public virtual void Union_Include_collection_ignored() + [ConditionalFact(Skip = "Issue#17498")] + public virtual void Include_navigation() { using (var context = CreateContext()) { - var orders = context.Orders - .Where(o => o.OrderID < 10250) - .Union(context.Orders.Where(o => o.CustomerID == "ALFKI")) - .Include(o => o.OrderDetails) - .ToList(); + var customers + = context.Set() + .Include(c => c.Orders) + .ToList(); - Assert.NotNull(orders); - Assert.Contains( - CoreResources.LogIgnoredInclude(new TestLogger()).GenerateMessage("[o].OrderDetails"), - Fixture.TestSqlLoggerFactory.Log.Select(l => l.Message)); + Assert.NotNull(customers); + + Assert.Equal( + "Compiling query model: " + _eol + + "'(from Customer c in DbSet" + _eol + + @"select [c]).Include(""Orders"")'" + , + Fixture.TestSqlLoggerFactory.Log[0].Message); + Assert.Equal( + "Including navigation: '[c].Orders'" + , + Fixture.TestSqlLoggerFactory.Log[1].Message); + Assert.StartsWith( + "Optimized query model: " + _eol + + "'from Customer c in DbSet" + _eol + + @"order by EF.Property(?[c]?, ""CustomerID"") asc" + _eol + + "select Customer _Include(" + , + Fixture.TestSqlLoggerFactory.Log[2].Message); } } - [ConditionalFact(Skip = "Issue #17068")] + [ConditionalFact(Skip = "Issue #16752")] public virtual void GroupBy_Include_collection_ignored() { using (var context = CreateContext()) @@ -164,12 +128,13 @@ public virtual void GroupBy_Include_collection_ignored() Assert.NotNull(orders); Assert.Contains( +#pragma warning disable CS0612 // Type or member is obsolete CoreResources.LogIgnoredInclude(new TestLogger()).GenerateMessage( +#pragma warning restore CS0612 // Type or member is obsolete "{from Order o in [g] orderby [o].OrderID asc select [o] => FirstOrDefault()}.OrderDetails"), Fixture.TestSqlLoggerFactory.Log.Select(l => l.Message)); } } -#pragma warning restore CS0612 // Type or member is obsolete [ConditionalFact] public void SelectExpression_does_not_use_an_old_logger() From 142b25ba00ab9ad22afa41725e442853019e5b0d Mon Sep 17 00:00:00 2001 From: Smit Patel Date: Thu, 29 Aug 2019 10:59:31 -0700 Subject: [PATCH 17/21] InMemory: Tests scrub Update issues references and add area for #16963 Enable tests which are already working --- .../InMemoryComplianceTest.cs | 3 - .../ComplexNavigationsQueryInMemoryTest.cs | 118 ++---------------- ...ComplexNavigationsWeakQueryInMemoryTest.cs | 2 +- .../Query/GearsOfWarQueryInMemoryTest.cs | 32 +---- .../Query/InheritanceInMemoryTest.cs | 3 +- .../Query/OwnedQueryInMemoryTest.cs | 2 +- .../SpatialInMemoryTest.cs | 2 +- .../UpdatesInMemoryTestBase.cs | 2 +- .../ChangeTracking/Internal/QueryFixupTest.cs | 10 +- 9 files changed, 23 insertions(+), 151 deletions(-) diff --git a/test/EFCore.InMemory.FunctionalTests/InMemoryComplianceTest.cs b/test/EFCore.InMemory.FunctionalTests/InMemoryComplianceTest.cs index a9de08cd218..13708fbc41e 100644 --- a/test/EFCore.InMemory.FunctionalTests/InMemoryComplianceTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/InMemoryComplianceTest.cs @@ -20,9 +20,6 @@ public class InMemoryComplianceTest : ComplianceTestBase // Remaining Issue #16963 3.0 query tests: typeof(ComplexNavigationsWeakQueryTestBase<>), typeof(OwnedQueryTestBase<>), - typeof(ComplexNavigationsQueryTestBase<>), - typeof(GearsOfWarQueryTestBase<>), - typeof(SpatialQueryTestBase<>) }; protected override Assembly TargetAssembly { get; } = typeof(InMemoryComplianceTest).Assembly; diff --git a/test/EFCore.InMemory.FunctionalTests/Query/ComplexNavigationsQueryInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/ComplexNavigationsQueryInMemoryTest.cs index 53cce45a98f..ba3e2f3384d 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/ComplexNavigationsQueryInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/ComplexNavigationsQueryInMemoryTest.cs @@ -15,172 +15,76 @@ public ComplexNavigationsQueryInMemoryTest(ComplexNavigationsQueryInMemoryFixtur //TestLoggerFactory.TestOutputHelper = testOutputHelper; } - [ConditionalTheory(Skip = "issue #16963")] //DefaultIfEmpty - public override Task Complex_SelectMany_with_nested_navigations_and_explicit_DefaultIfEmpty_with_other_query_operators_composed_on_top(bool isAsync) - { - return base.Complex_SelectMany_with_nested_navigations_and_explicit_DefaultIfEmpty_with_other_query_operators_composed_on_top(isAsync); - } - - [ConditionalTheory(Skip = "issue #16963")] //DefaultIfEmpty - public override Task Multiple_SelectMany_with_navigation_and_explicit_DefaultIfEmpty(bool isAsync) - { - return base.Multiple_SelectMany_with_navigation_and_explicit_DefaultIfEmpty(isAsync); - } - - [ConditionalTheory(Skip = "issue #16963")] //DefaultIfEmpty - public override Task Multiple_SelectMany_with_nested_navigations_and_explicit_DefaultIfEmpty_joined_together(bool isAsync) - { - return base.Multiple_SelectMany_with_nested_navigations_and_explicit_DefaultIfEmpty_joined_together(isAsync); - } - - [ConditionalTheory(Skip = "issue #16963")] //DefaultIfEmpty - public override Task SelectMany_with_navigation_and_explicit_DefaultIfEmpty(bool isAsync) - { - return base.SelectMany_with_navigation_and_explicit_DefaultIfEmpty(isAsync); - } - - [ConditionalTheory(Skip = "issue #16963")] //DefaultIfEmpty - public override Task SelectMany_with_navigation_filter_and_explicit_DefaultIfEmpty(bool isAsync) - { - return base.SelectMany_with_navigation_filter_and_explicit_DefaultIfEmpty(isAsync); - } - - [ConditionalTheory(Skip = "issue #16963")] //DefaultIfEmpty - public override Task SelectMany_with_navigation_filter_paging_and_explicit_DefaultIfEmpty(bool isAsync) - { - return base.SelectMany_with_navigation_filter_paging_and_explicit_DefaultIfEmpty(isAsync); - } - - [ConditionalTheory(Skip = "issue #16963")] //DefaultIfEmpty + [ConditionalTheory(Skip = "issue #16963 TooManyResults")] public override Task SelectMany_with_nested_navigations_and_explicit_DefaultIfEmpty_followed_by_Select_required_navigation_using_different_navs(bool isAsync) { return base.SelectMany_with_nested_navigations_and_explicit_DefaultIfEmpty_followed_by_Select_required_navigation_using_different_navs(isAsync); } - [ConditionalTheory(Skip = "issue #16963")] //DefaultIfEmpty - public override Task SelectMany_with_nested_navigations_and_explicit_DefaultIfEmpty_followed_by_Select_required_navigation_using_same_navs(bool isAsync) - { - return base.SelectMany_with_nested_navigations_and_explicit_DefaultIfEmpty_followed_by_Select_required_navigation_using_same_navs(isAsync); - } - - [ConditionalTheory(Skip = "issue #16963")] //DefaultIfEmpty - public override Task SelectMany_with_nested_navigations_explicit_DefaultIfEmpty_and_additional_joins_outside_of_SelectMany(bool isAsync) - { - return base.SelectMany_with_nested_navigations_explicit_DefaultIfEmpty_and_additional_joins_outside_of_SelectMany(isAsync); - } - - [ConditionalTheory(Skip = "issue #16963")] //DefaultIfEmpty - public override Task SelectMany_with_nested_navigations_explicit_DefaultIfEmpty_and_additional_joins_outside_of_SelectMany2(bool isAsync) - { - return base.SelectMany_with_nested_navigations_explicit_DefaultIfEmpty_and_additional_joins_outside_of_SelectMany2(isAsync); - } - - [ConditionalTheory(Skip = "issue #16963")] //DefaultIfEmpty - public override Task SelectMany_with_nested_navigations_explicit_DefaultIfEmpty_and_additional_joins_outside_of_SelectMany3(bool isAsync) - { - return base.SelectMany_with_nested_navigations_explicit_DefaultIfEmpty_and_additional_joins_outside_of_SelectMany3(isAsync); - } - - [ConditionalTheory(Skip = "issue #16963")] //DefaultIfEmpty - public override Task SelectMany_with_nested_navigations_explicit_DefaultIfEmpty_and_additional_joins_outside_of_SelectMany4(bool isAsync) - { - return base.SelectMany_with_nested_navigations_explicit_DefaultIfEmpty_and_additional_joins_outside_of_SelectMany4(isAsync); - } - - [ConditionalTheory(Skip = "issue #16963")] //DefaultIfEmpty + [ConditionalTheory(Skip = "issue #16963 TooManyResults")] public override Task SelectMany_with_nested_navigation_and_explicit_DefaultIfEmpty(bool isAsync) { return base.SelectMany_with_nested_navigation_and_explicit_DefaultIfEmpty(isAsync); } - [ConditionalTheory(Skip = "issue #16963")] //DefaultIfEmpty + [ConditionalTheory(Skip = "issue #16963 TooManyResults")] public override Task SelectMany_with_nested_navigation_filter_and_explicit_DefaultIfEmpty(bool isAsync) { return base.SelectMany_with_nested_navigation_filter_and_explicit_DefaultIfEmpty(isAsync); } - [ConditionalTheory(Skip = "issue #16963")] //DefaultIfEmpty - public override Task SelectMany_with_nested_required_navigation_filter_and_explicit_DefaultIfEmpty(bool isAsync) - { - return base.SelectMany_with_nested_required_navigation_filter_and_explicit_DefaultIfEmpty(isAsync); - } - - [ConditionalTheory(Skip = "issue #16963")] //GroupBy - public override Task Simple_level1_level2_GroupBy_Count(bool isAsync) - { - return base.Simple_level1_level2_GroupBy_Count(isAsync); - } - - [ConditionalTheory(Skip = "issue #16963")] //GroupBy - public override Task Simple_level1_level2_GroupBy_Having_Count(bool isAsync) - { - return base.Simple_level1_level2_GroupBy_Having_Count(isAsync); - } - [ConditionalTheory(Skip = "issue #17386")] public override Task Complex_query_with_optional_navigations_and_client_side_evaluation(bool isAsync) { return base.Complex_query_with_optional_navigations_and_client_side_evaluation(isAsync); } - [ConditionalTheory(Skip = "issue #17453")] + [ConditionalTheory(Skip = "issue #16963 TooManyResults")] public override Task Project_collection_navigation_nested(bool isAsync) { return base.Project_collection_navigation_nested(isAsync); } - [ConditionalTheory(Skip = "issue #17453")] + [ConditionalTheory(Skip = "issue #16963 TooManyResults")] public override Task Project_collection_navigation_nested_anonymous(bool isAsync) { return base.Project_collection_navigation_nested_anonymous(isAsync); } - [ConditionalTheory(Skip = "issue #17453")] + [ConditionalTheory(Skip = "issue #16963 TooManyResults")] public override Task Project_collection_navigation_using_ef_property(bool isAsync) { return base.Project_collection_navigation_using_ef_property(isAsync); } - [ConditionalTheory(Skip = "issue #17453")] + [ConditionalTheory(Skip = "issue #16963 TooManyResults")] public override Task Project_navigation_and_collection(bool isAsync) { return base.Project_navigation_and_collection(isAsync); } - [ConditionalTheory(Skip = "issue #17453")] + [ConditionalTheory(Skip = "issue #16963 TooManyResults")] public override Task SelectMany_nested_navigation_property_optional_and_projection(bool isAsync) { return base.SelectMany_nested_navigation_property_optional_and_projection(isAsync); } - [ConditionalTheory(Skip = "issue #17453")] + [ConditionalTheory(Skip = "issue #16963 TooManyResults")] public override Task SelectMany_nested_navigation_property_required(bool isAsync) { return base.SelectMany_nested_navigation_property_required(isAsync); } - [ConditionalTheory(Skip = "issue #17460")] - public override Task Where_complex_predicate_with_with_nav_prop_and_OrElse4(bool isAsync) - { - return base.Where_complex_predicate_with_with_nav_prop_and_OrElse4(isAsync); - } - - [ConditionalTheory(Skip = "issue #17460")] + [ConditionalTheory(Skip = "issue #16963 Test")] public override Task Join_flattening_bug_4539(bool isAsync) { return base.Join_flattening_bug_4539(isAsync); } - [ConditionalTheory(Skip = "issue #17463")] + [ConditionalTheory(Skip = "issue #16963 Test")] public override Task Include18_3_3(bool isAsync) { return base.Include18_3_3(isAsync); } - - [ConditionalFact(Skip = "issue #17463")] - public override void Include19() - { - base.Include19(); - } } } diff --git a/test/EFCore.InMemory.FunctionalTests/Query/ComplexNavigationsWeakQueryInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/ComplexNavigationsWeakQueryInMemoryTest.cs index 273acb080ca..3d2d1c3ef3f 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/ComplexNavigationsWeakQueryInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/ComplexNavigationsWeakQueryInMemoryTest.cs @@ -5,7 +5,7 @@ namespace Microsoft.EntityFrameworkCore.Query { - // issue #16963 + // issue #16963 Weak internal class ComplexNavigationsWeakQueryInMemoryTest : ComplexNavigationsWeakQueryTestBase { diff --git a/test/EFCore.InMemory.FunctionalTests/Query/GearsOfWarQueryInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/GearsOfWarQueryInMemoryTest.cs index 050d95bd736..39ada99f121 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/GearsOfWarQueryInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/GearsOfWarQueryInMemoryTest.cs @@ -15,36 +15,6 @@ public GearsOfWarQueryInMemoryTest(GearsOfWarQueryInMemoryFixture fixture, ITest //TestLoggerFactory.TestOutputHelper = testOutputHelper; } - [ConditionalTheory(Skip = "issue #16963")] // groupby - public override Task GroupBy_Property_Include_Aggregate_with_anonymous_selector(bool isAsync) - { - return base.GroupBy_Property_Include_Aggregate_with_anonymous_selector(isAsync); - } - - [ConditionalTheory(Skip = "issue #16963")] // groupby - public override Task GroupBy_Property_Include_Select_Count(bool isAsync) - { - return base.GroupBy_Property_Include_Select_Count(isAsync); - } - - [ConditionalTheory(Skip = "issue #16963")] // groupby - public override Task GroupBy_Property_Include_Select_LongCount(bool isAsync) - { - return base.GroupBy_Property_Include_Select_LongCount(isAsync); - } - - [ConditionalTheory(Skip = "issue #16963")] // groupby - public override Task GroupBy_Property_Include_Select_Max(bool isAsync) - { - return base.GroupBy_Property_Include_Select_Max(isAsync); - } - - [ConditionalTheory(Skip = "issue #16963")] // groupby - public override Task GroupBy_Property_Include_Select_Min(bool isAsync) - { - return base.GroupBy_Property_Include_Select_Min(isAsync); - } - [ConditionalTheory(Skip = "issue #17386")] public override Task Correlated_collection_order_by_constant_null_of_non_mapped_type(bool isAsync) { @@ -111,7 +81,7 @@ public override void Include_on_GroupJoin_SelectMany_DefaultIfEmpty_with_coalesc base.Include_on_GroupJoin_SelectMany_DefaultIfEmpty_with_coalesce_result2(); } - [ConditionalTheory(Skip = "issue #16963")] //length + [ConditionalTheory(Skip = "issue #16963 Nullable error")] //length public override Task Null_semantics_is_correctly_applied_for_function_comparisons_that_take_arguments_from_optional_navigation_complex(bool isAsync) { return base.Null_semantics_is_correctly_applied_for_function_comparisons_that_take_arguments_from_optional_navigation_complex(isAsync); diff --git a/test/EFCore.InMemory.FunctionalTests/Query/InheritanceInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/InheritanceInMemoryTest.cs index c1042ddbe7b..e5fdc1a201b 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/InheritanceInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/InheritanceInMemoryTest.cs @@ -14,9 +14,10 @@ public InheritanceInMemoryTest(InheritanceInMemoryFixture fixture, ITestOutputHe //TestLoggerFactory.TestOutputHelper = testOutputHelper; } - [ConditionalFact(Skip = "See issue#16963 Cannot compose when using client method in defining query")] // Defining query + [ConditionalFact(Skip = "Issue#17504")] public override void Can_query_all_animal_views() { + base.Can_query_all_animal_views(); } protected override bool EnforcesFkConstraints => false; diff --git a/test/EFCore.InMemory.FunctionalTests/Query/OwnedQueryInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/OwnedQueryInMemoryTest.cs index e838b036b63..87a95f2e016 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/OwnedQueryInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/OwnedQueryInMemoryTest.cs @@ -6,7 +6,7 @@ namespace Microsoft.EntityFrameworkCore.Query { - // issue #16963 + // issue #16963 Owned internal class OwnedQueryInMemoryTest : OwnedQueryTestBase { public OwnedQueryInMemoryTest(OwnedQueryInMemoryFixture fixture, ITestOutputHelper testOutputHelper) diff --git a/test/EFCore.InMemory.FunctionalTests/SpatialInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/SpatialInMemoryTest.cs index 8dfa005123f..c46054b2856 100644 --- a/test/EFCore.InMemory.FunctionalTests/SpatialInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/SpatialInMemoryTest.cs @@ -14,7 +14,7 @@ public SpatialInMemoryTest(SpatialInMemoryFixture fixture) { } - [ConditionalFact(Skip = "Issue#16963")] + [ConditionalFact(Skip = "Issue#14042")] public override void Mutation_of_tracked_values_does_not_mutate_values_in_store() { base.Mutation_of_tracked_values_does_not_mutate_values_in_store(); diff --git a/test/EFCore.InMemory.FunctionalTests/UpdatesInMemoryTestBase.cs b/test/EFCore.InMemory.FunctionalTests/UpdatesInMemoryTestBase.cs index 2780f8d3a1e..95466273682 100644 --- a/test/EFCore.InMemory.FunctionalTests/UpdatesInMemoryTestBase.cs +++ b/test/EFCore.InMemory.FunctionalTests/UpdatesInMemoryTestBase.cs @@ -17,7 +17,7 @@ protected UpdatesInMemoryTestBase(TFixture fixture) { } - [ConditionalFact(Skip = "Issue #16963")] + [ConditionalFact(Skip = "Issue #14042")] public override void Mutation_of_tracked_values_does_not_mutate_values_in_store() { } diff --git a/test/EFCore.Tests/ChangeTracking/Internal/QueryFixupTest.cs b/test/EFCore.Tests/ChangeTracking/Internal/QueryFixupTest.cs index 0e4940fc2df..e537c8f6309 100644 --- a/test/EFCore.Tests/ChangeTracking/Internal/QueryFixupTest.cs +++ b/test/EFCore.Tests/ChangeTracking/Internal/QueryFixupTest.cs @@ -893,7 +893,7 @@ public void Query_principal_include_dependent_self_ref_unidirectional_with_exist } } - [ConditionalFact(Skip = "Issue #16963")] + [ConditionalFact(Skip = "Issue #16963 Owned")] public void Query_ownership_navigations() { Seed(); @@ -944,7 +944,7 @@ public void Query_ownership_navigations() } } - [ConditionalFact(Skip = "Issue #16963")] + [ConditionalFact(Skip = "Issue #16963 Owned")] public void Query_owned_foreign_key() { Seed(); @@ -961,7 +961,7 @@ public void Query_owned_foreign_key() } } - [ConditionalFact(Skip = "Issue #16963")] + [ConditionalFact(Skip = "Issue #16963 Owned")] public void Query_subowned_foreign_key() { Seed(); @@ -978,7 +978,7 @@ public void Query_subowned_foreign_key() } } - [ConditionalFact(Skip = "Issue #16963")] + [ConditionalFact(Skip = "Issue #16963 Owned")] public void Query_owned() { Seed(); @@ -999,7 +999,7 @@ public void Query_owned() } } - [ConditionalFact(Skip = "Issue #16963")] + [ConditionalFact(Skip = "Issue #16963 Owned")] public void Query_subowned() { Seed(); From a0c8c44f1e28ec75907b983df34e3edeb7b524fa Mon Sep 17 00:00:00 2001 From: Smit Patel Date: Thu, 29 Aug 2019 18:35:30 -0700 Subject: [PATCH 18/21] InMemory: Support for Weak/Owned entityTypes Part of #16963 --- .../Internal/EntityProjectionExpression.cs | 30 +++ ...emoryProjectionBindingExpressionVisitor.cs | 31 +-- .../Query/Internal/InMemoryQueryExpression.cs | 146 +++++++++++++ ...yableMethodTranslatingExpressionVisitor.cs | 204 +++++++++++++++++- ...yableMethodTranslatingExpressionVisitor.cs | 5 + .../InMemoryComplianceTest.cs | 3 - .../LazyLoadProxyInMemoryTest.cs | 24 --- .../MonsterFixupSnapshotInMemoryTest.cs | 48 ----- ...ComplexNavigationsWeakQueryInMemoryTest.cs | 113 +++++++++- .../Query/OwnedQueryInMemoryTest.cs | 27 +-- .../ChangeTracking/Internal/QueryFixupTest.cs | 12 +- 11 files changed, 517 insertions(+), 126 deletions(-) diff --git a/src/EFCore.InMemory/Query/Internal/EntityProjectionExpression.cs b/src/EFCore.InMemory/Query/Internal/EntityProjectionExpression.cs index 265765b4152..192f3d2d3d4 100644 --- a/src/EFCore.InMemory/Query/Internal/EntityProjectionExpression.cs +++ b/src/EFCore.InMemory/Query/Internal/EntityProjectionExpression.cs @@ -12,6 +12,8 @@ namespace Microsoft.EntityFrameworkCore.InMemory.Query.Internal public class EntityProjectionExpression : Expression, IPrintableExpression { private readonly IDictionary _readExpressionMap; + private readonly IDictionary _navigationExpressionsCache + = new Dictionary(); public EntityProjectionExpression( IEntityType entityType, IDictionary readExpressionMap) @@ -52,6 +54,34 @@ public virtual Expression BindProperty(IProperty property) return _readExpressionMap[property]; } + public virtual void AddNavigationBinding(INavigation navigation, EntityShaperExpression entityShaper) + { + if (!EntityType.IsAssignableFrom(navigation.DeclaringEntityType) + && !navigation.DeclaringEntityType.IsAssignableFrom(EntityType)) + { + throw new InvalidOperationException( + $"Called EntityProjectionExpression.AddNavigationBinding() with incorrect INavigation. " + + $"EntityType:{EntityType.DisplayName()}, Property:{navigation.Name}"); + } + + _navigationExpressionsCache[navigation] = entityShaper; + } + + public virtual EntityShaperExpression BindNavigation(INavigation navigation) + { + if (!EntityType.IsAssignableFrom(navigation.DeclaringEntityType) + && !navigation.DeclaringEntityType.IsAssignableFrom(EntityType)) + { + throw new InvalidOperationException( + $"Called EntityProjectionExpression.BindNavigation() with incorrect INavigation. " + + $"EntityType:{EntityType.DisplayName()}, Property:{navigation.Name}"); + } + + return _navigationExpressionsCache.TryGetValue(navigation, out var expression) + ? expression + : null; + } + public virtual void Print(ExpressionPrinter expressionPrinter) { expressionPrinter.AppendLine(nameof(EntityProjectionExpression) + ":"); diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryProjectionBindingExpressionVisitor.cs b/src/EFCore.InMemory/Query/Internal/InMemoryProjectionBindingExpressionVisitor.cs index 649095403cf..2edc10ac293 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryProjectionBindingExpressionVisitor.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryProjectionBindingExpressionVisitor.cs @@ -15,7 +15,7 @@ namespace Microsoft.EntityFrameworkCore.InMemory.Query.Internal { public class InMemoryProjectionBindingExpressionVisitor : ExpressionVisitor { - private readonly QueryableMethodTranslatingExpressionVisitor _queryableMethodTranslatingExpressionVisitor; + private readonly InMemoryQueryableMethodTranslatingExpressionVisitor _queryableMethodTranslatingExpressionVisitor; private readonly InMemoryExpressionTranslatingExpressionVisitor _expressionTranslatingExpressionVisitor; private InMemoryQueryExpression _queryExpression; @@ -25,7 +25,7 @@ private readonly IDictionary _projectionMapping private readonly Stack _projectionMembers = new Stack(); public InMemoryProjectionBindingExpressionVisitor( - QueryableMethodTranslatingExpressionVisitor queryableMethodTranslatingExpressionVisitor, + InMemoryQueryableMethodTranslatingExpressionVisitor queryableMethodTranslatingExpressionVisitor, InMemoryExpressionTranslatingExpressionVisitor expressionTranslatingExpressionVisitor) { _queryableMethodTranslatingExpressionVisitor = queryableMethodTranslatingExpressionVisitor; @@ -39,13 +39,15 @@ public virtual Expression Translate(InMemoryQueryExpression queryExpression, Exp _projectionMembers.Push(new ProjectionMember()); - var result = Visit(expression); + var expandedExpression = _queryableMethodTranslatingExpressionVisitor.ExpandWeakEntities(_queryExpression, expression); + var result = Visit(expandedExpression); if (result == null) { _clientEval = true; - result = Visit(expression); + expandedExpression = _queryableMethodTranslatingExpressionVisitor.ExpandWeakEntities(_queryExpression, expression); + result = Visit(expandedExpression); _projectionMapping.Clear(); } @@ -179,21 +181,26 @@ protected override Expression VisitExtension(Expression extensionExpression) { if (extensionExpression is EntityShaperExpression entityShaperExpression) { - var projectionBindingExpression = (ProjectionBindingExpression)entityShaperExpression.ValueBufferExpression; - VerifyQueryExpression(projectionBindingExpression); - - if (_clientEval) + EntityProjectionExpression entityProjectionExpression; + if (entityShaperExpression.ValueBufferExpression is ProjectionBindingExpression projectionBindingExpression) { - var entityProjection = (EntityProjectionExpression)_queryExpression.GetMappedProjection( + VerifyQueryExpression(projectionBindingExpression); + entityProjectionExpression = (EntityProjectionExpression)_queryExpression.GetMappedProjection( projectionBindingExpression.ProjectionMember); + } + else + { + entityProjectionExpression = (EntityProjectionExpression)entityShaperExpression.ValueBufferExpression; + } + if (_clientEval) + { return entityShaperExpression.Update( - new ProjectionBindingExpression(_queryExpression, _queryExpression.AddToProjection(entityProjection))); + new ProjectionBindingExpression(_queryExpression, _queryExpression.AddToProjection(entityProjectionExpression))); } else { - _projectionMapping[_projectionMembers.Peek()] - = _queryExpression.GetMappedProjection(projectionBindingExpression.ProjectionMember); + _projectionMapping[_projectionMembers.Peek()] = entityProjectionExpression; return entityShaperExpression.Update( new ProjectionBindingExpression(_queryExpression, _projectionMembers.Peek(), typeof(ValueBuffer))); diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs b/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs index d6b8c8fb669..4cba1dd1af6 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs @@ -751,6 +751,152 @@ public virtual void AddSelectMany(InMemoryQueryExpression innerQueryExpression, _projectionMapping = projectionMapping; } + public virtual EntityShaperExpression AddNavigationToWeakEntityType( + EntityProjectionExpression entityProjectionExpression, + INavigation navigation, + InMemoryQueryExpression innerQueryExpression, + LambdaExpression outerKeySelector, + LambdaExpression innerKeySelector) + { + // GroupJoin phase + var groupTransparentIdentifierType = TransparentIdentifierFactory.Create( + typeof(ValueBuffer), typeof(IEnumerable)); + var outerParameter = Parameter(typeof(ValueBuffer), "outer"); + var innerParameter = Parameter(typeof(IEnumerable), "inner"); + var outerMemberInfo = groupTransparentIdentifierType.GetTypeInfo().GetDeclaredField("Outer"); + var innerMemberInfo = groupTransparentIdentifierType.GetTypeInfo().GetDeclaredField("Inner"); + var resultSelector = Lambda( + New( + groupTransparentIdentifierType.GetTypeInfo().DeclaredConstructors.Single(), + new[] { outerParameter, innerParameter }, + new[] { outerMemberInfo, innerMemberInfo }), + outerParameter, + innerParameter); + + var groupJoinExpression = Call( + InMemoryLinqOperatorProvider.GroupJoin.MakeGenericMethod( + typeof(ValueBuffer), typeof(ValueBuffer), outerKeySelector.ReturnType, groupTransparentIdentifierType), + ServerQueryExpression, + innerQueryExpression.ServerQueryExpression, + outerKeySelector, + innerKeySelector, + resultSelector); + + // SelectMany phase + var collectionParameter = Parameter(groupTransparentIdentifierType, "collection"); + var collection = MakeMemberAccess(collectionParameter, innerMemberInfo); + outerParameter = Parameter(groupTransparentIdentifierType, "outer"); + innerParameter = Parameter(typeof(ValueBuffer), "inner"); + + var resultValueBufferExpressions = new List(); + var projectionMapping = new Dictionary(); + var replacingVisitor = new ReplacingExpressionVisitor( + new Dictionary + { + { CurrentParameter, MakeMemberAccess(outerParameter, outerMemberInfo) }, + { innerQueryExpression.CurrentParameter, innerParameter } + }); + var index = 0; + + EntityProjectionExpression copyEntityProjectionToOuter(EntityProjectionExpression entityProjection) + { + var readExpressionMap = new Dictionary(); + foreach (var property in GetAllPropertiesInHierarchy(entityProjection.EntityType)) + { + var replacedExpression = replacingVisitor.Visit(entityProjection.BindProperty(property)); + resultValueBufferExpressions.Add(replacedExpression); + readExpressionMap[property] = CreateReadValueExpression(replacedExpression.Type, index++, property); + } + + var newEntityProjection = new EntityProjectionExpression(entityProjection.EntityType, readExpressionMap); + if (ReferenceEquals(entityProjectionExpression, entityProjection)) + { + entityProjectionExpression = newEntityProjection; + } + + // Also lift nested entity projections + foreach (var navigation in entityProjection.EntityType.GetTypesInHierarchy() + .SelectMany(EntityTypeExtensions.GetDeclaredNavigations)) + { + var boundEntityShaperExpression = entityProjection.BindNavigation(navigation); + if (boundEntityShaperExpression != null) + { + var innerEntityProjection = (EntityProjectionExpression)boundEntityShaperExpression.ValueBufferExpression; + var newInnerEntityProjection = copyEntityProjectionToOuter(innerEntityProjection); + boundEntityShaperExpression = boundEntityShaperExpression.Update(newInnerEntityProjection); + newEntityProjection.AddNavigationBinding(navigation, boundEntityShaperExpression); + } + } + + return newEntityProjection; + } + + foreach (var projection in _projectionMapping) + { + if (projection.Value is EntityProjectionExpression entityProjection) + { + projectionMapping[projection.Key] = copyEntityProjectionToOuter(entityProjection); + } + else + { + var replacedExpression = replacingVisitor.Visit(projection.Value); + resultValueBufferExpressions.Add(replacedExpression); + projectionMapping[projection.Key] + = CreateReadValueExpression(replacedExpression.Type, index++, InferPropertyFromInner(projection.Value)); + } + } + + _projectionMapping = projectionMapping; + + var outerIndex = index; + var nullableReadValueExpressionVisitor = new NullableReadValueExpressionVisitor(); + var innerEntityProjection = (EntityProjectionExpression)innerQueryExpression.GetMappedProjection(new ProjectionMember()); + + var innerReadExpressionMap = new Dictionary(); + foreach (var property in GetAllPropertiesInHierarchy(innerEntityProjection.EntityType)) + { + var replacedExpression = replacingVisitor.Visit(innerEntityProjection.BindProperty(property)); + replacedExpression = nullableReadValueExpressionVisitor.Visit(replacedExpression); + resultValueBufferExpressions.Add(replacedExpression); + innerReadExpressionMap[property] = CreateReadValueExpression(replacedExpression.Type, index++, property); + } + innerEntityProjection = new EntityProjectionExpression(innerEntityProjection.EntityType, innerReadExpressionMap); + + var collectionSelector = Lambda( + Call( + InMemoryLinqOperatorProvider.DefaultIfEmptyWithArgument.MakeGenericMethod(typeof(ValueBuffer)), + collection, + New( + _valueBufferConstructor, + NewArrayInit( + typeof(object), + Enumerable.Range(0, index - outerIndex).Select(i => Constant(null))))), + collectionParameter); + + resultSelector = Lambda( + New( + _valueBufferConstructor, + NewArrayInit( + typeof(object), + resultValueBufferExpressions + .Select(e => e.Type.IsValueType ? Convert(e, typeof(object)) : e) + .ToArray())), + outerParameter, + innerParameter); + + ServerQueryExpression = Call( + InMemoryLinqOperatorProvider.SelectManyWithCollectionSelector.MakeGenericMethod( + groupTransparentIdentifierType, typeof(ValueBuffer), typeof(ValueBuffer)), + groupJoinExpression, + collectionSelector, + resultSelector); + + var entityShaper = new EntityShaperExpression(innerEntityProjection.EntityType, innerEntityProjection, nullable: true); + entityProjectionExpression.AddNavigationBinding(navigation, entityShaper); + + return entityShaper; + } + public virtual void Print(ExpressionPrinter expressionPrinter) { expressionPrinter.AppendLine(nameof(InMemoryQueryExpression) + ": "); diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs index 39e405bddba..325bbaea64d 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs @@ -6,6 +6,8 @@ using System.Linq; using System.Linq.Expressions; using System.Reflection; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Query; @@ -18,6 +20,7 @@ public class InMemoryQueryableMethodTranslatingExpressionVisitor : QueryableMeth private static readonly MethodInfo _efPropertyMethod = typeof(EF).GetTypeInfo().GetDeclaredMethod(nameof(EF.Property)); private readonly InMemoryExpressionTranslatingExpressionVisitor _expressionTranslator; + private readonly WeakEntityExpandingExpressionVisitor _weakEntityExpandingExpressionVisitor; private readonly InMemoryProjectionBindingExpressionVisitor _projectionBindingExpressionVisitor; private readonly IModel _model; @@ -27,6 +30,7 @@ public InMemoryQueryableMethodTranslatingExpressionVisitor( : base(dependencies, subquery: false) { _expressionTranslator = new InMemoryExpressionTranslatingExpressionVisitor(this); + _weakEntityExpandingExpressionVisitor = new WeakEntityExpandingExpressionVisitor(_expressionTranslator); _projectionBindingExpressionVisitor = new InMemoryProjectionBindingExpressionVisitor(this, _expressionTranslator); _model = model; } @@ -36,6 +40,7 @@ protected InMemoryQueryableMethodTranslatingExpressionVisitor( : base(parentVisitor.Dependencies, subquery: true) { _expressionTranslator = parentVisitor._expressionTranslator; + _weakEntityExpandingExpressionVisitor = parentVisitor._weakEntityExpandingExpressionVisitor; _projectionBindingExpressionVisitor = new InMemoryProjectionBindingExpressionVisitor(this, _expressionTranslator); _model = parentVisitor._model; } @@ -44,8 +49,10 @@ protected override QueryableMethodTranslatingExpressionVisitor CreateSubqueryVis => new InMemoryQueryableMethodTranslatingExpressionVisitor(this); protected override ShapedQueryExpression CreateShapedQueryExpression(Type elementType) + => CreateShapedQueryExpression(_model.FindEntityType(elementType)); + + private static ShapedQueryExpression CreateShapedQueryExpression(IEntityType entityType) { - var entityType = _model.FindEntityType(elementType); var queryExpression = new InMemoryQueryExpression(entityType); return new ShapedQueryExpression( @@ -257,7 +264,7 @@ protected override ShapedQueryExpression TranslateGroupBy(ShapedQueryExpression { original2, source.ShaperExpression } }).Visit(resultSelector.Body); - //newResultSelectorBody = ExpandWeakEntities(selectExpression, newResultSelectorBody); + newResultSelectorBody = ExpandWeakEntities(inMemoryQueryExpression, newResultSelectorBody); source.ShaperExpression = _projectionBindingExpressionVisitor.Translate(inMemoryQueryExpression, newResultSelectorBody); @@ -360,7 +367,7 @@ protected override ShapedQueryExpression TranslateJoin(ShapedQueryExpression out transparentIdentifierType); } - private (LambdaExpression OuterKeySelector, LambdaExpression InnerKeySelector) + private static (LambdaExpression OuterKeySelector, LambdaExpression InnerKeySelector) AlignKeySelectorTypes(LambdaExpression outerKeySelector, LambdaExpression innerKeySelector) { static bool isConvertedToNullable(Expression outer, Expression inner) @@ -772,10 +779,197 @@ private LambdaExpression TranslateLambdaExpression( : null; } - private static Expression RemapLambdaBody(ShapedQueryExpression shapedQueryExpression, LambdaExpression lambdaExpression) + private Expression RemapLambdaBody(ShapedQueryExpression shapedQueryExpression, LambdaExpression lambdaExpression) { - return ReplacingExpressionVisitor.Replace( + var lambdaBody = ReplacingExpressionVisitor.Replace( lambdaExpression.Parameters.Single(), shapedQueryExpression.ShaperExpression, lambdaExpression.Body); + + return ExpandWeakEntities((InMemoryQueryExpression)shapedQueryExpression.QueryExpression, lambdaBody); + } + + internal Expression ExpandWeakEntities(InMemoryQueryExpression queryExpression, Expression lambdaBody) + => _weakEntityExpandingExpressionVisitor.Expand(queryExpression, lambdaBody); + + private class WeakEntityExpandingExpressionVisitor : ExpressionVisitor + { + private InMemoryQueryExpression _queryExpression; + private readonly InMemoryExpressionTranslatingExpressionVisitor _expressionTranslator; + + public WeakEntityExpandingExpressionVisitor(InMemoryExpressionTranslatingExpressionVisitor expressionTranslator) + { + _expressionTranslator = expressionTranslator; + } + + public virtual Expression Expand(InMemoryQueryExpression queryExpression, Expression lambdaBody) + { + _queryExpression = queryExpression; + + return Visit(lambdaBody); + } + + protected override Expression VisitMember(MemberExpression memberExpression) + { + var innerExpression = Visit(memberExpression.Expression); + + if (innerExpression is EntityShaperExpression + || (innerExpression is UnaryExpression innerUnaryExpression + && innerUnaryExpression.NodeType == ExpressionType.Convert + && innerUnaryExpression.Operand is EntityShaperExpression)) + { + var collectionNavigation = Expand(innerExpression, MemberIdentity.Create(memberExpression.Member)); + if (collectionNavigation != null) + { + return collectionNavigation; + } + } + + return memberExpression.Update(innerExpression); + } + + protected override Expression VisitMethodCall(MethodCallExpression methodCallExpression) + { + if (methodCallExpression.TryGetEFPropertyArguments(out var source, out var navigationName)) + { + source = Visit(source); + if (source is EntityShaperExpression + || (source is UnaryExpression innerUnaryExpression + && innerUnaryExpression.NodeType == ExpressionType.Convert + && innerUnaryExpression.Operand is EntityShaperExpression)) + { + var collectionNavigation = Expand(source, MemberIdentity.Create(navigationName)); + if (collectionNavigation != null) + { + return collectionNavigation; + } + } + + return methodCallExpression.Update(null, new[] { source, methodCallExpression.Arguments[1] }); + } + + return base.VisitMethodCall(methodCallExpression); + } + + protected override Expression VisitExtension(Expression extensionExpression) + => extensionExpression is EntityShaperExpression + ? extensionExpression + : base.VisitExtension(extensionExpression); + + private Expression Expand(Expression source, MemberIdentity member) + { + Type convertedType = null; + if (source is UnaryExpression unaryExpression + && unaryExpression.NodeType == ExpressionType.Convert) + { + source = unaryExpression.Operand; + if (unaryExpression.Type != typeof(object)) + { + convertedType = unaryExpression.Type; + } + } + + if (!(source is EntityShaperExpression entityShaperExpression)) + { + return null; + } + + var entityType = entityShaperExpression.EntityType; + if (convertedType != null) + { + entityType = entityType.GetRootType().GetDerivedTypesInclusive() + .FirstOrDefault(et => et.ClrType == convertedType); + + if (entityType == null) + { + return null; + } + } + + var navigation = member.MemberInfo != null + ? entityType.FindNavigation(member.MemberInfo) + : entityType.FindNavigation(member.Name); + + if (navigation == null) + { + return null; + } + + var targetEntityType = navigation.GetTargetType(); + if (targetEntityType == null + || (!targetEntityType.HasDefiningNavigation() + && !targetEntityType.IsOwned())) + { + return null; + } + + var foreignKey = navigation.ForeignKey; + if (navigation.IsCollection()) + { + var innerShapedQuery = CreateShapedQueryExpression(targetEntityType); + var innerQueryExpression = (InMemoryQueryExpression)innerShapedQuery.QueryExpression; + + var makeNullable = foreignKey.PrincipalKey.Properties + .Concat(foreignKey.Properties) + .Select(p => p.ClrType) + .Any(t => t.IsNullableType()); + + var outerKey = entityShaperExpression.CreateKeyAccessExpression( + navigation.IsDependentToPrincipal() + ? foreignKey.Properties + : foreignKey.PrincipalKey.Properties, + makeNullable); + var innerKey = innerShapedQuery.ShaperExpression.CreateKeyAccessExpression( + navigation.IsDependentToPrincipal() + ? foreignKey.PrincipalKey.Properties + : foreignKey.Properties, + makeNullable); + + var correlationPredicate = _expressionTranslator.Translate(Expression.Equal(outerKey, innerKey)); + + innerQueryExpression.ServerQueryExpression = Expression.Call( + InMemoryLinqOperatorProvider.Where.MakeGenericMethod(innerQueryExpression.CurrentParameter.Type), + innerQueryExpression.ServerQueryExpression, + Expression.Lambda(correlationPredicate, innerQueryExpression.CurrentParameter)); + + return innerShapedQuery; + } + + var entityProjectionExpression + = (EntityProjectionExpression)(entityShaperExpression.ValueBufferExpression is + ProjectionBindingExpression projectionBindingExpression + ? _queryExpression.GetMappedProjection(projectionBindingExpression.ProjectionMember) + : entityShaperExpression.ValueBufferExpression); + + var innerShaper = entityProjectionExpression.BindNavigation(navigation); + if (innerShaper == null) + { + var innerShapedQuery = CreateShapedQueryExpression(targetEntityType); + var innerQueryExpression = (InMemoryQueryExpression)innerShapedQuery.QueryExpression; + + var makeNullable = foreignKey.PrincipalKey.Properties + .Concat(foreignKey.Properties) + .Select(p => p.ClrType) + .Any(t => t.IsNullableType()); + + var outerKey = entityShaperExpression.CreateKeyAccessExpression( + navigation.IsDependentToPrincipal() + ? foreignKey.Properties + : foreignKey.PrincipalKey.Properties, + makeNullable); + var innerKey = innerShapedQuery.ShaperExpression.CreateKeyAccessExpression( + navigation.IsDependentToPrincipal() + ? foreignKey.PrincipalKey.Properties + : foreignKey.Properties, + makeNullable); + + var outerKeySelector = Expression.Lambda(_expressionTranslator.Translate(outerKey), _queryExpression.CurrentParameter); + var innerKeySelector = Expression.Lambda(_expressionTranslator.Translate(innerKey), innerQueryExpression.CurrentParameter); + (outerKeySelector, innerKeySelector) = AlignKeySelectorTypes(outerKeySelector, innerKeySelector); + innerShaper = _queryExpression.AddNavigationToWeakEntityType( + entityProjectionExpression, navigation, innerQueryExpression, outerKeySelector, innerKeySelector); + } + + return innerShaper; + } } private ShapedQueryExpression TranslateScalarAggregate( diff --git a/src/EFCore/Query/QueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore/Query/QueryableMethodTranslatingExpressionVisitor.cs index 8cb80eedc0b..73a51488a1e 100644 --- a/src/EFCore/Query/QueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore/Query/QueryableMethodTranslatingExpressionVisitor.cs @@ -33,6 +33,11 @@ protected override Expression VisitConstant(ConstantExpression constantExpressio ? CreateShapedQueryExpression(((IQueryable)constantExpression.Value).ElementType) : base.VisitConstant(constantExpression); + protected override Expression VisitExtension(Expression expression) + => expression is ShapedQueryExpression + ? expression + : base.VisitExtension(expression); + protected override Expression VisitMethodCall(MethodCallExpression methodCallExpression) { ShapedQueryExpression CheckTranslated(ShapedQueryExpression translated) diff --git a/test/EFCore.InMemory.FunctionalTests/InMemoryComplianceTest.cs b/test/EFCore.InMemory.FunctionalTests/InMemoryComplianceTest.cs index 13708fbc41e..678e7fa592b 100644 --- a/test/EFCore.InMemory.FunctionalTests/InMemoryComplianceTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/InMemoryComplianceTest.cs @@ -17,9 +17,6 @@ public class InMemoryComplianceTest : ComplianceTestBase typeof(OptimisticConcurrencyTestBase<>), typeof(StoreGeneratedTestBase<>), typeof(ConferencePlannerTestBase<>), - // Remaining Issue #16963 3.0 query tests: - typeof(ComplexNavigationsWeakQueryTestBase<>), - typeof(OwnedQueryTestBase<>), }; protected override Assembly TargetAssembly { get; } = typeof(InMemoryComplianceTest).Assembly; diff --git a/test/EFCore.InMemory.FunctionalTests/LazyLoadProxyInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/LazyLoadProxyInMemoryTest.cs index 0fb3f7c3cf7..6eaecc03271 100644 --- a/test/EFCore.InMemory.FunctionalTests/LazyLoadProxyInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/LazyLoadProxyInMemoryTest.cs @@ -14,30 +14,6 @@ public LazyLoadProxyInMemoryTest(LoadInMemoryFixture fixture) { } - [ConditionalFact(Skip = "Issue#16963 Owned")] - public override void Lazy_loading_finds_correct_entity_type_with_already_loaded_owned_types() - { - base.Lazy_loading_finds_correct_entity_type_with_already_loaded_owned_types(); - } - - [ConditionalFact(Skip = "Issue#16963 Owned")] - public override void Lazy_loading_finds_correct_entity_type_with_alternate_model() - { - base.Lazy_loading_finds_correct_entity_type_with_alternate_model(); - } - - [ConditionalFact(Skip = "Issue#16963 Owned")] - public override void Lazy_loading_finds_correct_entity_type_with_multiple_queries() - { - base.Lazy_loading_finds_correct_entity_type_with_multiple_queries(); - } - - [ConditionalFact(Skip = "Issue#16963 Owned")] - public override void Lazy_loading_finds_correct_entity_type_with_opaque_predicate_and_multiple_queries() - { - base.Lazy_loading_finds_correct_entity_type_with_opaque_predicate_and_multiple_queries(); - } - public class LoadInMemoryFixture : LoadFixtureBase { protected override ITestStoreFactory TestStoreFactory => InMemoryTestStoreFactory.Instance; diff --git a/test/EFCore.InMemory.FunctionalTests/MonsterFixupSnapshotInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/MonsterFixupSnapshotInMemoryTest.cs index 82fdc30be9e..cb9c08417cc 100644 --- a/test/EFCore.InMemory.FunctionalTests/MonsterFixupSnapshotInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/MonsterFixupSnapshotInMemoryTest.cs @@ -14,54 +14,6 @@ public MonsterFixupSnapshotInMemoryTest(MonsterFixupSnapshotInMemoryFixture fixt { } - [ConditionalFact(Skip = "Issue#16963 Owned")] - public override void Can_build_monster_model_and_seed_data_using_all_navigations() - { - base.Can_build_monster_model_and_seed_data_using_all_navigations(); - } - - [ConditionalFact(Skip = "Issue#16963 Owned")] - public override void Can_build_monster_model_and_seed_data_using_dependent_navigations() - { - base.Can_build_monster_model_and_seed_data_using_dependent_navigations(); - } - - [ConditionalFact(Skip = "Issue#16963 Owned")] - public override void Can_build_monster_model_and_seed_data_using_FKs() - { - base.Can_build_monster_model_and_seed_data_using_FKs(); - } - - [ConditionalFact(Skip = "Issue#16963 Owned")] - public override void Can_build_monster_model_and_seed_data_using_navigations_with_deferred_add() - { - base.Can_build_monster_model_and_seed_data_using_navigations_with_deferred_add(); - } - - [ConditionalFact(Skip = "Issue#16963 Owned")] - public override void Can_build_monster_model_and_seed_data_using_principal_navigations() - { - base.Can_build_monster_model_and_seed_data_using_principal_navigations(); - } - - [ConditionalFact(Skip = "Issue#16963 Owned")] - public override void One_to_one_fixup_happens_when_FKs_change_test() - { - base.One_to_one_fixup_happens_when_FKs_change_test(); - } - - [ConditionalFact(Skip = "Issue#16963 Owned")] - public override void One_to_one_fixup_happens_when_reference_change_test() - { - base.One_to_one_fixup_happens_when_reference_change_test(); - } - - [ConditionalFact(Skip = "Issue#16963 Owned")] - public override void Composite_fixup_happens_when_FKs_change_test() - { - base.Composite_fixup_happens_when_FKs_change_test(); - } - public class MonsterFixupSnapshotInMemoryFixture : MonsterFixupSnapshotFixtureBase { protected override ITestStoreFactory TestStoreFactory => InMemoryTestStoreFactory.Instance; diff --git a/test/EFCore.InMemory.FunctionalTests/Query/ComplexNavigationsWeakQueryInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/ComplexNavigationsWeakQueryInMemoryTest.cs index 3d2d1c3ef3f..3bc1141ac05 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/ComplexNavigationsWeakQueryInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/ComplexNavigationsWeakQueryInMemoryTest.cs @@ -1,12 +1,13 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Threading.Tasks; +using Xunit; using Xunit.Abstractions; namespace Microsoft.EntityFrameworkCore.Query { - // issue #16963 Weak - internal class ComplexNavigationsWeakQueryInMemoryTest : + public class ComplexNavigationsWeakQueryInMemoryTest : ComplexNavigationsWeakQueryTestBase { // ReSharper disable once UnusedParameter.Local @@ -16,5 +17,113 @@ public ComplexNavigationsWeakQueryInMemoryTest( { //TestLoggerFactory.TestOutputHelper = testOutputHelper; } + + [ConditionalTheory(Skip = "Issue#17386")] + public override Task Complex_query_with_optional_navigations_and_client_side_evaluation(bool isAsync) + { + return base.Complex_query_with_optional_navigations_and_client_side_evaluation(isAsync); + } + + [ConditionalTheory(Skip = "Issue#16963 Too Many Results")] + public override Task Complex_navigations_with_predicate_projected_into_anonymous_type(bool isAsync) + { + return base.Complex_navigations_with_predicate_projected_into_anonymous_type(isAsync); + } + + [ConditionalTheory(Skip = "issue #16963 TooManyResults")] + public override Task SelectMany_with_nested_navigations_and_explicit_DefaultIfEmpty_followed_by_Select_required_navigation_using_different_navs(bool isAsync) + { + return base.SelectMany_with_nested_navigations_and_explicit_DefaultIfEmpty_followed_by_Select_required_navigation_using_different_navs(isAsync); + } + + [ConditionalTheory(Skip = "issue #16963 TooManyResults")] + public override Task SelectMany_with_nested_navigation_filter_and_explicit_DefaultIfEmpty(bool isAsync) + { + return base.SelectMany_with_nested_navigation_filter_and_explicit_DefaultIfEmpty(isAsync); + } + + [ConditionalTheory(Skip = "issue #16963 TooManyResults")] + public override Task Project_collection_navigation_nested(bool isAsync) + { + return base.Project_collection_navigation_nested(isAsync); + } + + [ConditionalTheory(Skip = "issue #16963 TooManyResults")] + public override Task Project_collection_navigation_nested_anonymous(bool isAsync) + { + return base.Project_collection_navigation_nested_anonymous(isAsync); + } + + [ConditionalTheory(Skip = "issue #16963 TooManyResults")] + public override Task Project_collection_navigation_using_ef_property(bool isAsync) + { + return base.Project_collection_navigation_using_ef_property(isAsync); + } + + [ConditionalTheory(Skip = "issue #16963 TooManyResults")] + public override Task Project_navigation_and_collection(bool isAsync) + { + return base.Project_navigation_and_collection(isAsync); + } + + [ConditionalTheory(Skip = "issue #16963 TooManyResults")] + public override Task SelectMany_nested_navigation_property_optional_and_projection(bool isAsync) + { + return base.SelectMany_nested_navigation_property_optional_and_projection(isAsync); + } + + [ConditionalTheory(Skip = "issue #16963 Test")] + public override Task Join_navigations_in_inner_selector_translated_without_collision(bool isAsync) + { + return base.Join_navigations_in_inner_selector_translated_without_collision(isAsync); + } + + [ConditionalTheory(Skip = "issue #16963 Test")] + public override Task Join_with_navigations_in_the_result_selector1(bool isAsync) + { + return base.Join_with_navigations_in_the_result_selector1(isAsync); + } + + [ConditionalTheory(Skip = "issue #16963 Test")] + public override Task Include10(bool isAsync) + { + return base.Include10(isAsync); + } + + [ConditionalTheory(Skip = "issue #16963 Test")] + public override Task Include11(bool isAsync) + { + return base.Include11(isAsync); + } + + [ConditionalTheory(Skip = "issue #16963 Test")] + public override Task Include18_3_3(bool isAsync) + { + return base.Include18_3_3(isAsync); + } + + [ConditionalTheory(Skip = "issue #16963 Test")] + public override Task Multiple_include_with_multiple_optional_navigations(bool isAsync) + { + return base.Multiple_include_with_multiple_optional_navigations(isAsync); + } + + [ConditionalTheory(Skip = "issue #16963 Test")] + public override Task Order_by_key_of_anonymous_type_projected_navigation_doesnt_get_optimized_into_FK_access_subquery(bool isAsync) + { + return base.Order_by_key_of_anonymous_type_projected_navigation_doesnt_get_optimized_into_FK_access_subquery(isAsync); + } + + [ConditionalTheory(Skip = "issue #16963 Test")] + public override Task Where_nav_prop_reference_optional1_via_DefaultIfEmpty(bool isAsync) + { + return base.Where_nav_prop_reference_optional1_via_DefaultIfEmpty(isAsync); + } + + [ConditionalTheory(Skip = "issue #16963 Test")] + public override Task Optional_navigation_propagates_nullability_to_manually_created_left_join2(bool isAsync) + { + return base.Optional_navigation_propagates_nullability_to_manually_created_left_join2(isAsync); + } } } diff --git a/test/EFCore.InMemory.FunctionalTests/Query/OwnedQueryInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/OwnedQueryInMemoryTest.cs index 87a95f2e016..9ea51a6a553 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/OwnedQueryInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/OwnedQueryInMemoryTest.cs @@ -6,8 +6,7 @@ namespace Microsoft.EntityFrameworkCore.Query { - // issue #16963 Owned - internal class OwnedQueryInMemoryTest : OwnedQueryTestBase + public class OwnedQueryInMemoryTest : OwnedQueryTestBase { public OwnedQueryInMemoryTest(OwnedQueryInMemoryFixture fixture, ITestOutputHelper testOutputHelper) : base(fixture) @@ -15,30 +14,6 @@ public OwnedQueryInMemoryTest(OwnedQueryInMemoryFixture fixture, ITestOutputHelp //TestLoggerFactory.TestOutputHelper = testOutputHelper; } - public override void No_ignored_include_warning_when_implicit_load() - { - } - - public override void Query_for_base_type_loads_all_owned_navs() - { - } - - public override void Query_for_branch_type_loads_all_owned_navs() - { - } - - public override void Query_for_leaf_type_loads_all_owned_navs() - { - } - - public override void Query_when_group_by() - { - } - - public override void Query_when_subquery() - { - } - public class OwnedQueryInMemoryFixture : OwnedQueryFixtureBase { protected override ITestStoreFactory TestStoreFactory => InMemoryTestStoreFactory.Instance; diff --git a/test/EFCore.Tests/ChangeTracking/Internal/QueryFixupTest.cs b/test/EFCore.Tests/ChangeTracking/Internal/QueryFixupTest.cs index e537c8f6309..1e4c146d46f 100644 --- a/test/EFCore.Tests/ChangeTracking/Internal/QueryFixupTest.cs +++ b/test/EFCore.Tests/ChangeTracking/Internal/QueryFixupTest.cs @@ -893,7 +893,7 @@ public void Query_principal_include_dependent_self_ref_unidirectional_with_exist } } - [ConditionalFact(Skip = "Issue #16963 Owned")] + [ConditionalFact] public void Query_ownership_navigations() { Seed(); @@ -944,7 +944,7 @@ public void Query_ownership_navigations() } } - [ConditionalFact(Skip = "Issue #16963 Owned")] + [ConditionalFact] public void Query_owned_foreign_key() { Seed(); @@ -961,7 +961,7 @@ public void Query_owned_foreign_key() } } - [ConditionalFact(Skip = "Issue #16963 Owned")] + [ConditionalFact] public void Query_subowned_foreign_key() { Seed(); @@ -978,14 +978,14 @@ public void Query_subowned_foreign_key() } } - [ConditionalFact(Skip = "Issue #16963 Owned")] + [ConditionalFact] public void Query_owned() { Seed(); using (var context = new QueryFixupContext()) { - var owned = context.Set().Select(o => o.OrderDetails).Single(); + var owned = context.Set().Single().OrderDetails; var principal = context.Set().AsNoTracking().Single(); AssertFixup( @@ -999,7 +999,7 @@ public void Query_owned() } } - [ConditionalFact(Skip = "Issue #16963 Owned")] + [ConditionalFact(Skip = "Issue #16752")] public void Query_subowned() { Seed(); From 4d01f59fce22b18cc1d3ee62f5441adca22d6d34 Mon Sep 17 00:00:00 2001 From: Smit Patel Date: Fri, 30 Aug 2019 11:59:19 -0700 Subject: [PATCH 19/21] InMemory: Preserve type of value buffer expressions when doing Join/SelectMany Part of #16963 --- .../Query/Internal/InMemoryQueryExpression.cs | 15 ++++++---- .../ComplexNavigationsQueryInMemoryTest.cs | 6 ---- ...ComplexNavigationsWeakQueryInMemoryTest.cs | 30 ------------------- 3 files changed, 9 insertions(+), 42 deletions(-) diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs b/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs index 4cba1dd1af6..5cdd06a4291 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs @@ -472,8 +472,9 @@ public virtual void AddInnerJoin( var readExpressionMap = new Dictionary(); foreach (var property in GetAllPropertiesInHierarchy(entityProjection.EntityType)) { - resultValueBufferExpressions.Add(replacingVisitor.Visit(entityProjection.BindProperty(property))); - readExpressionMap[property] = CreateReadValueExpression(property.ClrType, index++, property); + var replacedExpression = replacingVisitor.Visit(entityProjection.BindProperty(property)); + resultValueBufferExpressions.Add(replacedExpression); + readExpressionMap[property] = CreateReadValueExpression(replacedExpression.Type, index++, property); } projectionMapping[projection.Key.Prepend(outerMemberInfo)] = new EntityProjectionExpression(entityProjection.EntityType, readExpressionMap); @@ -494,8 +495,9 @@ public virtual void AddInnerJoin( var readExpressionMap = new Dictionary(); foreach (var property in GetAllPropertiesInHierarchy(entityProjection.EntityType)) { - resultValueBufferExpressions.Add(replacingVisitor.Visit(entityProjection.BindProperty(property))); - readExpressionMap[property] = CreateReadValueExpression(property.ClrType, index++, property); + var replacedExpression = replacingVisitor.Visit(entityProjection.BindProperty(property)); + resultValueBufferExpressions.Add(replacedExpression); + readExpressionMap[property] = CreateReadValueExpression(replacedExpression.Type, index++, property); } projectionMapping[projection.Key.Prepend(innerMemberInfo)] = new EntityProjectionExpression(entityProjection.EntityType, readExpressionMap); @@ -683,8 +685,9 @@ public virtual void AddSelectMany(InMemoryQueryExpression innerQueryExpression, var readExpressionMap = new Dictionary(); foreach (var property in GetAllPropertiesInHierarchy(entityProjection.EntityType)) { - resultValueBufferExpressions.Add(replacingVisitor.Visit(entityProjection.BindProperty(property))); - readExpressionMap[property] = CreateReadValueExpression(property.ClrType, index++, property); + var replacedExpression = replacingVisitor.Visit(entityProjection.BindProperty(property)); + resultValueBufferExpressions.Add(replacedExpression); + readExpressionMap[property] = CreateReadValueExpression(replacedExpression.Type, index++, property); } projectionMapping[projection.Key.Prepend(outerMemberInfo)] = new EntityProjectionExpression(entityProjection.EntityType, readExpressionMap); diff --git a/test/EFCore.InMemory.FunctionalTests/Query/ComplexNavigationsQueryInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/ComplexNavigationsQueryInMemoryTest.cs index ba3e2f3384d..e34e6ee35ee 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/ComplexNavigationsQueryInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/ComplexNavigationsQueryInMemoryTest.cs @@ -75,12 +75,6 @@ public override Task SelectMany_nested_navigation_property_required(bool isAsync return base.SelectMany_nested_navigation_property_required(isAsync); } - [ConditionalTheory(Skip = "issue #16963 Test")] - public override Task Join_flattening_bug_4539(bool isAsync) - { - return base.Join_flattening_bug_4539(isAsync); - } - [ConditionalTheory(Skip = "issue #16963 Test")] public override Task Include18_3_3(bool isAsync) { diff --git a/test/EFCore.InMemory.FunctionalTests/Query/ComplexNavigationsWeakQueryInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/ComplexNavigationsWeakQueryInMemoryTest.cs index 3bc1141ac05..3637547868b 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/ComplexNavigationsWeakQueryInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/ComplexNavigationsWeakQueryInMemoryTest.cs @@ -24,12 +24,6 @@ public override Task Complex_query_with_optional_navigations_and_client_side_eva return base.Complex_query_with_optional_navigations_and_client_side_evaluation(isAsync); } - [ConditionalTheory(Skip = "Issue#16963 Too Many Results")] - public override Task Complex_navigations_with_predicate_projected_into_anonymous_type(bool isAsync) - { - return base.Complex_navigations_with_predicate_projected_into_anonymous_type(isAsync); - } - [ConditionalTheory(Skip = "issue #16963 TooManyResults")] public override Task SelectMany_with_nested_navigations_and_explicit_DefaultIfEmpty_followed_by_Select_required_navigation_using_different_navs(bool isAsync) { @@ -84,36 +78,12 @@ public override Task Join_with_navigations_in_the_result_selector1(bool isAsync) return base.Join_with_navigations_in_the_result_selector1(isAsync); } - [ConditionalTheory(Skip = "issue #16963 Test")] - public override Task Include10(bool isAsync) - { - return base.Include10(isAsync); - } - - [ConditionalTheory(Skip = "issue #16963 Test")] - public override Task Include11(bool isAsync) - { - return base.Include11(isAsync); - } - [ConditionalTheory(Skip = "issue #16963 Test")] public override Task Include18_3_3(bool isAsync) { return base.Include18_3_3(isAsync); } - [ConditionalTheory(Skip = "issue #16963 Test")] - public override Task Multiple_include_with_multiple_optional_navigations(bool isAsync) - { - return base.Multiple_include_with_multiple_optional_navigations(isAsync); - } - - [ConditionalTheory(Skip = "issue #16963 Test")] - public override Task Order_by_key_of_anonymous_type_projected_navigation_doesnt_get_optimized_into_FK_access_subquery(bool isAsync) - { - return base.Order_by_key_of_anonymous_type_projected_navigation_doesnt_get_optimized_into_FK_access_subquery(isAsync); - } - [ConditionalTheory(Skip = "issue #16963 Test")] public override Task Where_nav_prop_reference_optional1_via_DefaultIfEmpty(bool isAsync) { From f36fa7eecf0c2c9ce92944b464b737347a7d887f Mon Sep 17 00:00:00 2001 From: Maurycy Markowski Date: Thu, 29 Aug 2019 16:34:53 -0700 Subject: [PATCH 20/21] Further improvements on null protection/propagation Fixed cases involving member pushdown. We now remove all converts from nullable to non-nullable (even ones explicitly added by users) so that nullability can propagate to the very top. --- ...yExpressionTranslatingExpressionVisitor.cs | 6 ++++ ...emoryProjectionBindingExpressionVisitor.cs | 10 ++++-- .../Query/SimpleQueryCosmosTest.Select.cs | 6 ++++ .../Query/SimpleQueryCosmosTest.Where.cs | 6 ++++ .../ComplexNavigationsQueryInMemoryTest.cs | 18 +++++------ .../Query/SimpleQueryInMemoryTest.cs | 31 ++----------------- .../Query/ComplexNavigationsQueryTestBase.cs | 23 ++++++++++++++ .../Query/GearsOfWarQueryTestBase.cs | 20 ++++++++++++ .../Query/SimpleQueryTestBase.Select.cs | 10 ++++++ .../Query/SimpleQueryTestBase.Where.cs | 10 ++++++ .../Query/GearsOfWarQuerySqlServerTest.cs | 28 +++++++++++++++++ .../Query/SimpleQuerySqlServerTest.Where.cs | 28 +++++++++++++++++ 12 files changed, 155 insertions(+), 41 deletions(-) diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs b/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs index a9e5c8973e8..e09a7e7b48e 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs @@ -588,6 +588,12 @@ protected override Expression VisitUnary(UnaryExpression unaryExpression) return newOperand; } + if (unaryExpression.NodeType == ExpressionType.Convert + && IsConvertedToNullable(newOperand, unaryExpression)) + { + return newOperand; + } + var result = (Expression)Expression.MakeUnary(unaryExpression.NodeType, newOperand, unaryExpression.Type); if (result is UnaryExpression outerUnary && outerUnary.NodeType == ExpressionType.Convert diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryProjectionBindingExpressionVisitor.cs b/src/EFCore.InMemory/Query/Internal/InMemoryProjectionBindingExpressionVisitor.cs index 2edc10ac293..26a5163c4a4 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryProjectionBindingExpressionVisitor.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryProjectionBindingExpressionVisitor.cs @@ -136,7 +136,7 @@ public override Expression Visit(Expression expression) if (translation.Type != expression.Type) { - translation = Expression.Convert(translation, expression.Type); + translation = NullSafeConvert(translation, expression.Type); } return new ProjectionBindingExpression(_queryExpression, _queryExpression.AddToProjection(translation), expression.Type); @@ -151,19 +151,23 @@ public override Expression Visit(Expression expression) if (translation.Type != expression.Type) { - translation = Expression.Convert(translation, expression.Type); + translation = NullSafeConvert(translation, expression.Type); } _projectionMapping[_projectionMembers.Peek()] = translation; return new ProjectionBindingExpression(_queryExpression, _projectionMembers.Peek(), expression.Type); } - } return base.Visit(expression); } + private Expression NullSafeConvert(Expression expression, Type convertTo) + => expression.Type.IsNullableType() && !convertTo.IsNullableType() && expression.Type.UnwrapNullableType() == convertTo + ? (Expression)Expression.Coalesce(expression, Expression.Default(convertTo)) + : Expression.Convert(expression, convertTo); + private CollectionShaperExpression AddCollectionProjection( ShapedQueryExpression subquery, INavigation navigation, Type elementType) => new CollectionShaperExpression( diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/SimpleQueryCosmosTest.Select.cs b/test/EFCore.Cosmos.FunctionalTests/Query/SimpleQueryCosmosTest.Select.cs index 5a4057684c1..8469b094b62 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/SimpleQueryCosmosTest.Select.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/SimpleQueryCosmosTest.Select.cs @@ -857,5 +857,11 @@ public override Task Client_method_in_projection_requiring_materialization_2(boo { return base.Client_method_in_projection_requiring_materialization_2(isAsync); } + + [ConditionalTheory(Skip = "Issue#17246")] + public override Task Project_non_nullable_value_after_FirstOrDefault_on_empty_collection(bool isAsync) + { + return base.Project_non_nullable_value_after_FirstOrDefault_on_empty_collection(isAsync); + } } } diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/SimpleQueryCosmosTest.Where.cs b/test/EFCore.Cosmos.FunctionalTests/Query/SimpleQueryCosmosTest.Where.cs index 5c203509d71..d151d8bc734 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/SimpleQueryCosmosTest.Where.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/SimpleQueryCosmosTest.Where.cs @@ -1882,5 +1882,11 @@ public override Task Where_is_conditional(bool isAsync) { return base.Where_is_conditional(isAsync); } + + [ConditionalTheory(Skip = "Issue#17246")] + public override Task Filter_non_nullable_value_after_FirstOrDefault_on_empty_collection(bool isAsync) + { + return base.Filter_non_nullable_value_after_FirstOrDefault_on_empty_collection(isAsync); + } } } diff --git a/test/EFCore.InMemory.FunctionalTests/Query/ComplexNavigationsQueryInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/ComplexNavigationsQueryInMemoryTest.cs index e34e6ee35ee..b78f9ac2328 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/ComplexNavigationsQueryInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/ComplexNavigationsQueryInMemoryTest.cs @@ -15,19 +15,19 @@ public ComplexNavigationsQueryInMemoryTest(ComplexNavigationsQueryInMemoryFixtur //TestLoggerFactory.TestOutputHelper = testOutputHelper; } - [ConditionalTheory(Skip = "issue #16963 TooManyResults")] + [ConditionalTheory(Skip = "issue #17531")] public override Task SelectMany_with_nested_navigations_and_explicit_DefaultIfEmpty_followed_by_Select_required_navigation_using_different_navs(bool isAsync) { return base.SelectMany_with_nested_navigations_and_explicit_DefaultIfEmpty_followed_by_Select_required_navigation_using_different_navs(isAsync); } - [ConditionalTheory(Skip = "issue #16963 TooManyResults")] + [ConditionalTheory(Skip = "issue #17531")] public override Task SelectMany_with_nested_navigation_and_explicit_DefaultIfEmpty(bool isAsync) { return base.SelectMany_with_nested_navigation_and_explicit_DefaultIfEmpty(isAsync); } - [ConditionalTheory(Skip = "issue #16963 TooManyResults")] + [ConditionalTheory(Skip = "issue #17531")] public override Task SelectMany_with_nested_navigation_filter_and_explicit_DefaultIfEmpty(bool isAsync) { return base.SelectMany_with_nested_navigation_filter_and_explicit_DefaultIfEmpty(isAsync); @@ -39,37 +39,37 @@ public override Task Complex_query_with_optional_navigations_and_client_side_eva return base.Complex_query_with_optional_navigations_and_client_side_evaluation(isAsync); } - [ConditionalTheory(Skip = "issue #16963 TooManyResults")] + [ConditionalTheory(Skip = "issue #17531")] public override Task Project_collection_navigation_nested(bool isAsync) { return base.Project_collection_navigation_nested(isAsync); } - [ConditionalTheory(Skip = "issue #16963 TooManyResults")] + [ConditionalTheory(Skip = "issue #17531")] public override Task Project_collection_navigation_nested_anonymous(bool isAsync) { return base.Project_collection_navigation_nested_anonymous(isAsync); } - [ConditionalTheory(Skip = "issue #16963 TooManyResults")] + [ConditionalTheory(Skip = "issue #17531")] public override Task Project_collection_navigation_using_ef_property(bool isAsync) { return base.Project_collection_navigation_using_ef_property(isAsync); } - [ConditionalTheory(Skip = "issue #16963 TooManyResults")] + [ConditionalTheory(Skip = "issue #17531")] public override Task Project_navigation_and_collection(bool isAsync) { return base.Project_navigation_and_collection(isAsync); } - [ConditionalTheory(Skip = "issue #16963 TooManyResults")] + [ConditionalTheory(Skip = "issue #17531")] public override Task SelectMany_nested_navigation_property_optional_and_projection(bool isAsync) { return base.SelectMany_nested_navigation_property_optional_and_projection(isAsync); } - [ConditionalTheory(Skip = "issue #16963 TooManyResults")] + [ConditionalTheory(Skip = "issue #17531")] public override Task SelectMany_nested_navigation_property_required(bool isAsync) { return base.SelectMany_nested_navigation_property_required(isAsync); diff --git a/test/EFCore.InMemory.FunctionalTests/Query/SimpleQueryInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/SimpleQueryInMemoryTest.cs index ee8f6df16b0..ac062ef5fca 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/SimpleQueryInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/SimpleQueryInMemoryTest.cs @@ -2,10 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Dynamic; -using System.Linq; -using System.Runtime.InteropServices.WindowsRuntime; -using System.Text.RegularExpressions; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore.TestUtilities; using Xunit; @@ -164,16 +160,6 @@ public override Task Projection_when_arithmetic_mixed_subqueries(bool isAsync) #region SelectMany - public override Task SelectMany_Joined_DefaultIfEmpty(bool isAsync) - { - return Task.CompletedTask; - } - - public override Task SelectMany_Joined_DefaultIfEmpty2(bool isAsync) - { - return Task.CompletedTask; - } - public override Task SelectMany_correlated_with_outer_3(bool isAsync) { return Task.CompletedTask; @@ -186,25 +172,12 @@ public override Task SelectMany_correlated_with_outer_4(bool isAsync) #endregion - #region NullableError - - public override Task Project_single_element_from_collection_with_OrderBy_Distinct_and_FirstOrDefault_followed_by_projecting_length(bool isAsync) - { - return Task.CompletedTask; - } - + [ConditionalTheory(Skip = "Issue #17531")] public override Task DefaultIfEmpty_in_subquery_nested(bool isAsync) { - return Task.CompletedTask; - } - - public override Task Default_if_empty_top_level_projection(bool isAsync) - { - return Task.CompletedTask; + return base.DefaultIfEmpty_in_subquery_nested(isAsync); } - #endregion - [ConditionalTheory(Skip = "issue #17386")] public override Task Where_equals_on_null_nullable_int_types(bool isAsync) { diff --git a/test/EFCore.Specification.Tests/Query/ComplexNavigationsQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/ComplexNavigationsQueryTestBase.cs index cd7fae99fa1..c77936a9047 100644 --- a/test/EFCore.Specification.Tests/Query/ComplexNavigationsQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/ComplexNavigationsQueryTestBase.cs @@ -4762,6 +4762,29 @@ select Maybe(l1.OneToOne_Optional_FK1, () => l1.OneToOne_Optional_FK1.OneToMany_ }); } + [ConditionalTheory(Skip = "issue #17531")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Project_collection_navigation_nested_with_take(bool isAsync) + { + return AssertQuery( + isAsync, + l1s => from l1 in l1s + select l1.OneToOne_Optional_FK1.OneToMany_Optional2.Take(50), + l1s => from l1 in l1s + select Maybe(l1.OneToOne_Optional_FK1, () => l1.OneToOne_Optional_FK1.OneToMany_Optional2.Take(50)), + elementSorter: e => e != null ? e.Count : 0, + elementAsserter: (e, a) => + { + var actualCollection = new List(); + foreach (var actualElement in a) + { + actualCollection.Add(actualElement); + } + + Assert.Equal(((IEnumerable)e)?.Count() ?? 0, actualCollection.Count); + }); + } + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Project_collection_navigation_using_ef_property(bool isAsync) diff --git a/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs index aa116131d95..2a784bb3717 100644 --- a/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs @@ -6694,6 +6694,26 @@ public virtual Task Select_subquery_boolean_empty_with_pushdown(bool isAsync) gs => gs.Select(g => (bool?)null)); } + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Select_subquery_boolean_empty_with_pushdown_without_convert_to_nullable1(bool isAsync) + { + return AssertQueryScalar( + isAsync, + gs => gs.Select(g => g.Weapons.Where(w => w.Name == "BFG").OrderBy(w => w.Id).FirstOrDefault().IsAutomatic), + gs => gs.Select(g => false)); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Select_subquery_boolean_empty_with_pushdown_without_convert_to_nullable2(bool isAsync) + { + return AssertQueryScalar( + isAsync, + gs => gs.Select(g => g.Weapons.Where(w => w.Name == "BFG").OrderBy(w => w.Id).FirstOrDefault().Id), + gs => gs.Select(g => 0)); + } + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Select_subquery_distinct_singleordefault_boolean1(bool isAsync) diff --git a/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.Select.cs b/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.Select.cs index 19bd4c35d04..fc8c98aa3e9 100644 --- a/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.Select.cs +++ b/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.Select.cs @@ -1299,5 +1299,15 @@ from o in os.Where(o => c.CustomerID == o.CustomerID).OrderBy(o => c.City).Take( }, entryCount: 268); } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Project_non_nullable_value_after_FirstOrDefault_on_empty_collection(bool isAsync) + { + return AssertQueryScalar( + isAsync, + (cs, os) => cs.Select(c => os.Where(o => o.CustomerID == "John Doe").Select(o => o.CustomerID).FirstOrDefault().Length), + (cs, os) => cs.Select(c => 0)); + } } } diff --git a/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.Where.cs b/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.Where.cs index 27f6aaae05f..9dc8bae721d 100644 --- a/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.Where.cs +++ b/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.Where.cs @@ -2098,5 +2098,15 @@ public virtual Task Enclosing_class_const_member_does_not_generate_parameter(boo private int SettableProperty { get; set; } private int ReadOnlyProperty => 5; private const int ConstantProperty = 1; + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Filter_non_nullable_value_after_FirstOrDefault_on_empty_collection(bool isAsync) + { + return AssertQuery( + isAsync, + (cs, os) => cs.Where(c => os.Where(o => o.CustomerID == "John Doe").Select(o => o.CustomerID).FirstOrDefault().Length == 0), + (cs, os) => cs.Where(c => false)); + } } } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs index e2c242e7a5a..ea86cc9b374 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs @@ -6038,6 +6038,34 @@ FROM [Gears] AS [g] WHERE [g].[Discriminator] IN (N'Gear', N'Officer')"); } + public override async Task Select_subquery_boolean_empty_with_pushdown_without_convert_to_nullable1(bool isAsync) + { + await base.Select_subquery_boolean_empty_with_pushdown_without_convert_to_nullable1(isAsync); + + AssertSql( + @"SELECT ( + SELECT TOP(1) [w].[IsAutomatic] + FROM [Weapons] AS [w] + WHERE (([g].[FullName] = [w].[OwnerFullName]) AND [w].[OwnerFullName] IS NOT NULL) AND (([w].[Name] = N'BFG') AND [w].[Name] IS NOT NULL) + ORDER BY [w].[Id]) +FROM [Gears] AS [g] +WHERE [g].[Discriminator] IN (N'Gear', N'Officer')"); + } + + public override async Task Select_subquery_boolean_empty_with_pushdown_without_convert_to_nullable2(bool isAsync) + { + await base.Select_subquery_boolean_empty_with_pushdown_without_convert_to_nullable2(isAsync); + + AssertSql( + @"SELECT ( + SELECT TOP(1) [w].[Id] + FROM [Weapons] AS [w] + WHERE (([g].[FullName] = [w].[OwnerFullName]) AND [w].[OwnerFullName] IS NOT NULL) AND (([w].[Name] = N'BFG') AND [w].[Name] IS NOT NULL) + ORDER BY [w].[Id]) +FROM [Gears] AS [g] +WHERE [g].[Discriminator] IN (N'Gear', N'Officer')"); + } + public override async Task Select_subquery_distinct_singleordefault_boolean1(bool isAsync) { await base.Select_subquery_distinct_singleordefault_boolean1(isAsync); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.Where.cs b/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.Where.cs index fdcbf69de64..7931ca9fd8a 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.Where.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.Where.cs @@ -1738,5 +1738,33 @@ public override async Task Enclosing_class_const_member_does_not_generate_parame FROM [Orders] AS [o] WHERE [o].[OrderID] = 1"); } + + public override async Task Project_non_nullable_value_after_FirstOrDefault_on_empty_collection(bool isAsync) + { + await base.Project_non_nullable_value_after_FirstOrDefault_on_empty_collection(isAsync); + + AssertSql( + @"SELECT ( + SELECT TOP(1) CAST(LEN([o].[CustomerID]) AS int) + FROM [Orders] AS [o] + WHERE ([o].[CustomerID] = N'John Doe') AND [o].[CustomerID] IS NOT NULL) +FROM [Customers] AS [c]"); + } + + public override async Task Filter_non_nullable_value_after_FirstOrDefault_on_empty_collection(bool isAsync) + { + await base.Filter_non_nullable_value_after_FirstOrDefault_on_empty_collection(isAsync); + + AssertSql( + @"SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] +FROM [Customers] AS [c] +WHERE (( + SELECT TOP(1) CAST(LEN([o].[CustomerID]) AS int) + FROM [Orders] AS [o] + WHERE ([o].[CustomerID] = N'John Doe') AND [o].[CustomerID] IS NOT NULL) = 0) AND ( + SELECT TOP(1) CAST(LEN([o].[CustomerID]) AS int) + FROM [Orders] AS [o] + WHERE ([o].[CustomerID] = N'John Doe') AND [o].[CustomerID] IS NOT NULL) IS NOT NULL"); + } } } From 35f42b618dd6ed4f77588ff12d61123b1a51a1ba Mon Sep 17 00:00:00 2001 From: Smit Patel Date: Fri, 30 Aug 2019 18:45:27 -0700 Subject: [PATCH 21/21] InMemory: Last cleanup Close #16963 --- .../Query/Internal/InMemoryQueryExpression.cs | 10 ++++--- .../ComplexNavigationsQueryInMemoryTest.cs | 6 ---- ...ComplexNavigationsWeakQueryInMemoryTest.cs | 28 ++++++++----------- .../Query/GearsOfWarQueryInMemoryTest.cs | 14 +++++----- .../Query/SimpleQueryInMemoryTest.cs | 12 ++------ 5 files changed, 26 insertions(+), 44 deletions(-) diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs b/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs index 5cdd06a4291..1f7bfec20bb 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs @@ -205,8 +205,9 @@ public virtual void PushdownIntoSubquery() var map = new Dictionary(); foreach (var property in GetAllPropertiesInHierarchy(entityProjection.EntityType)) { - var index = AddToProjection(entityProjection.BindProperty(property)); - map[property] = CreateReadValueExpression(property.ClrType, index, property); + var expressionToAdd = entityProjection.BindProperty(property); + var index = AddToProjection(expressionToAdd); + map[property] = CreateReadValueExpression(expressionToAdd.Type, index, property); } result[keyValuePair.Key] = new EntityProjectionExpression(entityProjection.EntityType, map); } @@ -267,8 +268,9 @@ public virtual void ApplyDefaultIfEmpty() var map = new Dictionary(); foreach (var property in GetAllPropertiesInHierarchy(entityProjection.EntityType)) { - var index = AddToProjection(entityProjection.BindProperty(property)); - map[property] = CreateReadValueExpression(property.ClrType.MakeNullable(), index, property); + var expressionToAdd = entityProjection.BindProperty(property); + var index = AddToProjection(expressionToAdd); + map[property] = CreateReadValueExpression(expressionToAdd.Type.MakeNullable(), index, property); } result[keyValuePair.Key] = new EntityProjectionExpression(entityProjection.EntityType, map); } diff --git a/test/EFCore.InMemory.FunctionalTests/Query/ComplexNavigationsQueryInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/ComplexNavigationsQueryInMemoryTest.cs index b78f9ac2328..b55714760bf 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/ComplexNavigationsQueryInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/ComplexNavigationsQueryInMemoryTest.cs @@ -74,11 +74,5 @@ public override Task SelectMany_nested_navigation_property_required(bool isAsync { return base.SelectMany_nested_navigation_property_required(isAsync); } - - [ConditionalTheory(Skip = "issue #16963 Test")] - public override Task Include18_3_3(bool isAsync) - { - return base.Include18_3_3(isAsync); - } } } diff --git a/test/EFCore.InMemory.FunctionalTests/Query/ComplexNavigationsWeakQueryInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/ComplexNavigationsWeakQueryInMemoryTest.cs index 3637547868b..51713052d35 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/ComplexNavigationsWeakQueryInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/ComplexNavigationsWeakQueryInMemoryTest.cs @@ -24,73 +24,67 @@ public override Task Complex_query_with_optional_navigations_and_client_side_eva return base.Complex_query_with_optional_navigations_and_client_side_evaluation(isAsync); } - [ConditionalTheory(Skip = "issue #16963 TooManyResults")] + [ConditionalTheory(Skip = "issue #17531")] public override Task SelectMany_with_nested_navigations_and_explicit_DefaultIfEmpty_followed_by_Select_required_navigation_using_different_navs(bool isAsync) { return base.SelectMany_with_nested_navigations_and_explicit_DefaultIfEmpty_followed_by_Select_required_navigation_using_different_navs(isAsync); } - [ConditionalTheory(Skip = "issue #16963 TooManyResults")] + [ConditionalTheory(Skip = "issue #17531")] public override Task SelectMany_with_nested_navigation_filter_and_explicit_DefaultIfEmpty(bool isAsync) { return base.SelectMany_with_nested_navigation_filter_and_explicit_DefaultIfEmpty(isAsync); } - [ConditionalTheory(Skip = "issue #16963 TooManyResults")] + [ConditionalTheory(Skip = "issue #17531")] public override Task Project_collection_navigation_nested(bool isAsync) { return base.Project_collection_navigation_nested(isAsync); } - [ConditionalTheory(Skip = "issue #16963 TooManyResults")] + [ConditionalTheory(Skip = "issue #17531")] public override Task Project_collection_navigation_nested_anonymous(bool isAsync) { return base.Project_collection_navigation_nested_anonymous(isAsync); } - [ConditionalTheory(Skip = "issue #16963 TooManyResults")] + [ConditionalTheory(Skip = "issue #17531")] public override Task Project_collection_navigation_using_ef_property(bool isAsync) { return base.Project_collection_navigation_using_ef_property(isAsync); } - [ConditionalTheory(Skip = "issue #16963 TooManyResults")] + [ConditionalTheory(Skip = "issue #17531")] public override Task Project_navigation_and_collection(bool isAsync) { return base.Project_navigation_and_collection(isAsync); } - [ConditionalTheory(Skip = "issue #16963 TooManyResults")] + [ConditionalTheory(Skip = "issue #17531")] public override Task SelectMany_nested_navigation_property_optional_and_projection(bool isAsync) { return base.SelectMany_nested_navigation_property_optional_and_projection(isAsync); } - [ConditionalTheory(Skip = "issue #16963 Test")] + [ConditionalTheory(Skip = "17539")] public override Task Join_navigations_in_inner_selector_translated_without_collision(bool isAsync) { return base.Join_navigations_in_inner_selector_translated_without_collision(isAsync); } - [ConditionalTheory(Skip = "issue #16963 Test")] + [ConditionalTheory(Skip = "17539")] public override Task Join_with_navigations_in_the_result_selector1(bool isAsync) { return base.Join_with_navigations_in_the_result_selector1(isAsync); } - [ConditionalTheory(Skip = "issue #16963 Test")] - public override Task Include18_3_3(bool isAsync) - { - return base.Include18_3_3(isAsync); - } - - [ConditionalTheory(Skip = "issue #16963 Test")] + [ConditionalTheory(Skip = "17539")] public override Task Where_nav_prop_reference_optional1_via_DefaultIfEmpty(bool isAsync) { return base.Where_nav_prop_reference_optional1_via_DefaultIfEmpty(isAsync); } - [ConditionalTheory(Skip = "issue #16963 Test")] + [ConditionalTheory(Skip = "17539")] public override Task Optional_navigation_propagates_nullability_to_manually_created_left_join2(bool isAsync) { return base.Optional_navigation_propagates_nullability_to_manually_created_left_join2(isAsync); diff --git a/test/EFCore.InMemory.FunctionalTests/Query/GearsOfWarQueryInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/GearsOfWarQueryInMemoryTest.cs index 39ada99f121..f96a06f59e7 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/GearsOfWarQueryInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/GearsOfWarQueryInMemoryTest.cs @@ -45,43 +45,43 @@ public override Task GetValueOrDefault_on_DateTimeOffset(bool isAsync) return base.GetValueOrDefault_on_DateTimeOffset(isAsync); } - [ConditionalTheory(Skip = "issue #17453")] + [ConditionalTheory(Skip = "issue #17531")] public override Task Correlated_collection_with_complex_OrderBy(bool isAsync) { return base.Correlated_collection_with_complex_OrderBy(isAsync); } - [ConditionalTheory(Skip = "issue #17453")] + [ConditionalTheory(Skip = "issue #17531")] public override Task Correlated_collection_with_very_complex_order_by(bool isAsync) { return base.Correlated_collection_with_very_complex_order_by(isAsync); } - [ConditionalTheory(Skip = "issue #17463")] + [ConditionalTheory(Skip = "issue #17531")] public override Task Include_collection_OrderBy_aggregate(bool isAsync) { return base.Include_collection_OrderBy_aggregate(isAsync); } - [ConditionalTheory(Skip = "issue #17463")] + [ConditionalTheory(Skip = "issue #17531")] public override Task Include_collection_with_complex_OrderBy3(bool isAsync) { return base.Include_collection_with_complex_OrderBy3(isAsync); } - [ConditionalTheory(Skip = "issue #17463")] + [ConditionalTheory(Skip = "issue #17537")] public override void Include_on_GroupJoin_SelectMany_DefaultIfEmpty_with_coalesce_result1() { base.Include_on_GroupJoin_SelectMany_DefaultIfEmpty_with_coalesce_result1(); } - [ConditionalTheory(Skip = "issue #17463")] + [ConditionalTheory(Skip = "issue #17537")] public override void Include_on_GroupJoin_SelectMany_DefaultIfEmpty_with_coalesce_result2() { base.Include_on_GroupJoin_SelectMany_DefaultIfEmpty_with_coalesce_result2(); } - [ConditionalTheory(Skip = "issue #16963 Nullable error")] //length + [ConditionalTheory(Skip = "issue #17540")] public override Task Null_semantics_is_correctly_applied_for_function_comparisons_that_take_arguments_from_optional_navigation_complex(bool isAsync) { return base.Null_semantics_is_correctly_applied_for_function_comparisons_that_take_arguments_from_optional_navigation_complex(isAsync); diff --git a/test/EFCore.InMemory.FunctionalTests/Query/SimpleQueryInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/SimpleQueryInMemoryTest.cs index ac062ef5fca..5232b49f54a 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/SimpleQueryInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/SimpleQueryInMemoryTest.cs @@ -158,20 +158,12 @@ public override Task Projection_when_arithmetic_mixed_subqueries(bool isAsync) return base.Projection_when_arithmetic_mixed_subqueries(isAsync); } - #region SelectMany - + [ConditionalTheory(Skip = "Issue#17536")] public override Task SelectMany_correlated_with_outer_3(bool isAsync) { - return Task.CompletedTask; + return base.SelectMany_correlated_with_outer_3(isAsync); } - public override Task SelectMany_correlated_with_outer_4(bool isAsync) - { - return Task.CompletedTask; - } - - #endregion - [ConditionalTheory(Skip = "Issue #17531")] public override Task DefaultIfEmpty_in_subquery_nested(bool isAsync) {