diff --git a/src/EFCore.Cosmos/Query/CosmosQueryTranslationPostprocessor.cs b/src/EFCore.Cosmos/Query/CosmosQueryTranslationPostprocessor.cs deleted file mode 100644 index 0d64f18fe35..00000000000 --- a/src/EFCore.Cosmos/Query/CosmosQueryTranslationPostprocessor.cs +++ /dev/null @@ -1,43 +0,0 @@ -// 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 JetBrains.Annotations; -using Microsoft.EntityFrameworkCore.Cosmos.Query.Internal; -using Microsoft.EntityFrameworkCore.Query; -using Microsoft.EntityFrameworkCore.Utilities; - -namespace Microsoft.EntityFrameworkCore.Cosmos.Query -{ - /// - public class CosmosQueryTranslationPostprocessor : QueryTranslationPostprocessor - { - private readonly ISqlExpressionFactory _sqlExpressionFactory; - - /// - /// Creates a new instance of the class. - /// - /// Parameter object containing dependencies for this class. - /// The SqlExpressionFactory object to use. - /// The query compilation context object to use. - public CosmosQueryTranslationPostprocessor( - [NotNull] QueryTranslationPostprocessorDependencies dependencies, - [NotNull] ISqlExpressionFactory sqlExpressionFactory, - [NotNull] QueryCompilationContext queryCompilationContext) - : base(dependencies, queryCompilationContext) - { - Check.NotNull(sqlExpressionFactory, nameof(sqlExpressionFactory)); - - _sqlExpressionFactory = sqlExpressionFactory; - } - - /// - public override Expression Process(Expression query) - { - query = base.Process(query); - query = new CosmosValueConverterCompensatingExpressionVisitor(_sqlExpressionFactory).Visit(query); - - return query; - } - } -} diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosQueryTranslationPostprocessor.cs b/src/EFCore.Cosmos/Query/Internal/CosmosQueryTranslationPostprocessor.cs new file mode 100644 index 00000000000..07341c85951 --- /dev/null +++ b/src/EFCore.Cosmos/Query/Internal/CosmosQueryTranslationPostprocessor.cs @@ -0,0 +1,52 @@ +// 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 JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Query; +using Microsoft.EntityFrameworkCore.Utilities; + +namespace Microsoft.EntityFrameworkCore.Cosmos.Query.Internal +{ + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// 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. + /// + public class CosmosQueryTranslationPostprocessor : QueryTranslationPostprocessor + { + private readonly ISqlExpressionFactory _sqlExpressionFactory; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// 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. + /// + public CosmosQueryTranslationPostprocessor( + [NotNull] QueryTranslationPostprocessorDependencies dependencies, + [NotNull] ISqlExpressionFactory sqlExpressionFactory, + [NotNull] QueryCompilationContext queryCompilationContext) + : base(dependencies, queryCompilationContext) + { + Check.NotNull(sqlExpressionFactory, nameof(sqlExpressionFactory)); + + _sqlExpressionFactory = sqlExpressionFactory; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// 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. + /// + public override Expression Process(Expression query) + { + query = base.Process(query); + query = new CosmosValueConverterCompensatingExpressionVisitor(_sqlExpressionFactory).Visit(query); + + return query; + } + } +} diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.CosmosProjectionBindingRemovingExpressionVisitor.cs b/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.CosmosProjectionBindingRemovingExpressionVisitor.cs index 6d2ca88555e..147b0ee190e 100644 --- a/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.CosmosProjectionBindingRemovingExpressionVisitor.cs +++ b/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.CosmosProjectionBindingRemovingExpressionVisitor.cs @@ -23,7 +23,7 @@ public CosmosProjectionBindingRemovingExpressionVisitor( { _selectExpression = selectExpression; } - + protected override ProjectionExpression GetProjection(ProjectionBindingExpression projectionBindingExpression) => _selectExpression.Projection[GetProjectionIndex(projectionBindingExpression)]; diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs b/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs index a583b0b30d4..266f5aea8e3 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs @@ -483,8 +483,16 @@ MethodInfo GetMethod() return new EntityReferenceExpression(subqueryTranslation); } + var shaperExpression = subqueryTranslation.ShaperExpression; + if (shaperExpression is UnaryExpression unaryExpression + && unaryExpression.NodeType == ExpressionType.Convert + && unaryExpression.Type.MakeNullable() == unaryExpression.Operand.Type) + { + shaperExpression = unaryExpression.Operand; + } + #pragma warning disable IDE0046 // Convert to conditional expression - if (!(subqueryTranslation.ShaperExpression is ProjectionBindingExpression projectionBindingExpression)) + if (!(shaperExpression is ProjectionBindingExpression projectionBindingExpression)) #pragma warning restore IDE0046 // Convert to conditional expression { return null; @@ -869,7 +877,6 @@ private Expression TryBindMember(Expression source, MemberIdentity member, Type if (property != null) { return BindProperty(entityReferenceExpression, property, type); - } AddTranslationErrorDetails( diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryProjectionBindingExpressionVisitor.cs b/src/EFCore.InMemory/Query/Internal/InMemoryProjectionBindingExpressionVisitor.cs index 41844ab73a6..dcbc3cec828 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryProjectionBindingExpressionVisitor.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryProjectionBindingExpressionVisitor.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; +using System.Reflection; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Infrastructure; @@ -79,6 +80,8 @@ public virtual Expression Translate([NotNull] InMemoryQueryExpression queryExpre _projectionMapping.Clear(); _projectionMembers.Clear(); + result = MatchTypes(result, expression.Type); + return result; } @@ -164,18 +167,10 @@ public override Expression Visit(Expression expression) } var translation = _expressionTranslatingExpressionVisitor.Translate(expression); - if (translation == null) - { - return base.Visit(expression); - } - - if (translation.Type != expression.Type) - { - translation = NullSafeConvert(translation, expression.Type); - } - - return new ProjectionBindingExpression( - _queryExpression, _queryExpression.AddToProjection(translation), expression.Type); + return translation == null + ? base.Visit(expression) + : new ProjectionBindingExpression( + _queryExpression, _queryExpression.AddToProjection(translation), expression.Type.MakeNullable()); } else { @@ -185,37 +180,48 @@ public override Expression Visit(Expression expression) return null; } - if (translation.Type != expression.Type) - { - translation = NullSafeConvert(translation, expression.Type); - } - _projectionMapping[_projectionMembers.Peek()] = translation; - return new ProjectionBindingExpression(_queryExpression, _projectionMembers.Peek(), expression.Type); + return new ProjectionBindingExpression(_queryExpression, _projectionMembers.Peek(), expression.Type.MakeNullable()); } } 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); + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// 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 Expression VisitBinary(BinaryExpression binaryExpression) + { + var left = MatchTypes(Visit(binaryExpression.Left), binaryExpression.Left.Type); + var right = MatchTypes(Visit(binaryExpression.Right), binaryExpression.Right.Type); - private CollectionShaperExpression AddCollectionProjection( - ShapedQueryExpression subquery, INavigationBase navigation, Type elementType) - => new CollectionShaperExpression( - new ProjectionBindingExpression( - _queryExpression, - _queryExpression.AddSubqueryProjection( - subquery, - out var innerShaper), - typeof(IEnumerable)), - innerShaper, - navigation, - elementType); + return binaryExpression.Update(left, VisitAndConvert(binaryExpression.Conversion, "VisitBinary"), right); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// 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 Expression VisitConditional(ConditionalExpression conditionalExpression) + { + var test = Visit(conditionalExpression.Test); + var ifTrue = Visit(conditionalExpression.IfTrue); + var ifFalse = Visit(conditionalExpression.IfFalse); + + if (test.Type == typeof(bool?)) + { + test = Expression.Equal(test, Expression.Constant(true, typeof(bool?))); + } + + return conditionalExpression.Update(test, ifTrue, ifFalse); + } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -269,43 +275,69 @@ protected override Expression VisitExtension(Expression extensionExpression) /// 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 Expression VisitNew(NewExpression newExpression) + protected override ElementInit VisitElementInit(ElementInit elementInit) + => elementInit.Update(elementInit.Arguments.Select(e => MatchTypes(Visit(e), e.Type))); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// 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 Expression VisitMember(MemberExpression memberExpression) { - Check.NotNull(newExpression, nameof(newExpression)); + var expression = Visit(memberExpression.Expression); + Expression updatedMemberExpression = memberExpression.Update( + expression != null ? MatchTypes(expression, memberExpression.Expression.Type) : expression); - if (newExpression.Arguments.Count == 0) + if (expression?.Type.IsNullableValueType() == true) { - return newExpression; + var nullableReturnType = memberExpression.Type.MakeNullable(); + if (!memberExpression.Type.IsNullableType()) + { + updatedMemberExpression = Expression.Convert(updatedMemberExpression, nullableReturnType); + } + + updatedMemberExpression = Expression.Condition( + Expression.Equal(expression, Expression.Default(expression.Type)), + Expression.Constant(null, nullableReturnType), + updatedMemberExpression); } - if (!_clientEval - && newExpression.Members == null) + return updatedMemberExpression; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// 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 MemberAssignment VisitMemberAssignment(MemberAssignment memberAssignment) + { + var expression = memberAssignment.Expression; + Expression visitedExpression; + if (_clientEval) { - return null; + visitedExpression = Visit(memberAssignment.Expression); } - - var newArguments = new Expression[newExpression.Arguments.Count]; - for (var i = 0; i < newArguments.Length; i++) + else { - if (_clientEval) + var projectionMember = _projectionMembers.Peek().Append(memberAssignment.Member); + _projectionMembers.Push(projectionMember); + + visitedExpression = Visit(memberAssignment.Expression); + if (visitedExpression == null) { - newArguments[i] = Visit(newExpression.Arguments[i]); + return null; } - else - { - var projectionMember = _projectionMembers.Peek().Append(newExpression.Members[i]); - _projectionMembers.Push(projectionMember); - newArguments[i] = Visit(newExpression.Arguments[i]); - if (newArguments[i] == null) - { - return null; - } - _projectionMembers.Pop(); - } + _projectionMembers.Pop(); } - return newExpression.Update(newArguments); + visitedExpression = MatchTypes(visitedExpression, expression.Type); + + return memberAssignment.Update(visitedExpression); } /// @@ -348,24 +380,111 @@ protected override Expression VisitMemberInit(MemberInitExpression memberInitExp /// 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 MemberAssignment VisitMemberAssignment(MemberAssignment memberAssignment) + protected override Expression VisitMethodCall(MethodCallExpression methodCallExpression) { - if (_clientEval) + var @object = Visit(methodCallExpression.Object); + var arguments = new Expression[methodCallExpression.Arguments.Count]; + for (var i = 0; i < methodCallExpression.Arguments.Count; i++) { - return memberAssignment.Update(Visit(memberAssignment.Expression)); + var argument = methodCallExpression.Arguments[i]; + arguments[i] = MatchTypes(Visit(argument), argument.Type); + } + + Expression updatedMethodCallExpression = methodCallExpression.Update( + @object != null ? MatchTypes(@object, methodCallExpression.Object.Type) : @object, + arguments); + + if (@object?.Type.IsNullableType() == true + && !methodCallExpression.Object.Type.IsNullableType()) + { + var nullableReturnType = methodCallExpression.Type.MakeNullable(); + if (!methodCallExpression.Type.IsNullableType()) + { + updatedMethodCallExpression = Expression.Convert(updatedMethodCallExpression, nullableReturnType); + } + + return Expression.Condition( + Expression.Equal(@object, Expression.Default(@object.Type)), + Expression.Constant(null, nullableReturnType), + updatedMethodCallExpression); } - var projectionMember = _projectionMembers.Peek().Append(memberAssignment.Member); - _projectionMembers.Push(projectionMember); + return updatedMethodCallExpression; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// 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 Expression VisitNew(NewExpression newExpression) + { + Check.NotNull(newExpression, nameof(newExpression)); + + if (newExpression.Arguments.Count == 0) + { + return newExpression; + } - var visitedExpression = Visit(memberAssignment.Expression); - if (visitedExpression == null) + if (!_clientEval + && newExpression.Members == null) { return null; } - _projectionMembers.Pop(); - return memberAssignment.Update(visitedExpression); + var newArguments = new Expression[newExpression.Arguments.Count]; + for (var i = 0; i < newArguments.Length; i++) + { + var argument = newExpression.Arguments[i]; + Expression visitedArgument; + if (_clientEval) + { + visitedArgument = Visit(argument); + } + else + { + var projectionMember = _projectionMembers.Peek().Append(newExpression.Members[i]); + _projectionMembers.Push(projectionMember); + visitedArgument = Visit(argument); + if (visitedArgument == null) + { + return null; + } + + _projectionMembers.Pop(); + } + + newArguments[i] = MatchTypes(visitedArgument, argument.Type); + } + + return newExpression.Update(newArguments); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// 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 Expression VisitNewArray(NewArrayExpression newArrayExpression) + => newArrayExpression.Update(newArrayExpression.Expressions.Select(e => MatchTypes(Visit(e), e.Type))); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// 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 Expression VisitUnary(UnaryExpression unaryExpression) + { + var operand = Visit(unaryExpression.Operand); + + return (unaryExpression.NodeType == ExpressionType.Convert + || unaryExpression.NodeType == ExpressionType.ConvertChecked) + && unaryExpression.Type == operand.Type + ? operand + : unaryExpression.Update(MatchTypes(operand, unaryExpression.Operand.Type)); } // TODO: Debugging @@ -376,5 +495,31 @@ private void VerifyQueryExpression(ProjectionBindingExpression projectionBinding throw new InvalidOperationException(CoreStrings.QueryFailed(projectionBindingExpression.Print(), GetType().Name)); } } + + private CollectionShaperExpression AddCollectionProjection( + ShapedQueryExpression subquery, INavigationBase navigation, Type elementType) + => new CollectionShaperExpression( + new ProjectionBindingExpression( + _queryExpression, + _queryExpression.AddSubqueryProjection( + subquery, + out var innerShaper), + typeof(IEnumerable)), + innerShaper, + navigation, + elementType); + + private static Expression MatchTypes(Expression expression, Type targetType) + { + if (targetType != expression.Type + && targetType.TryGetElementType(typeof(IQueryable<>)) == null) + { + Check.DebugAssert(targetType.MakeNullable() == expression.Type, "Not a nullable to non-nullable conversion"); + + expression = Expression.Convert(expression, targetType); + } + + return expression; + } } } diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs b/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs index ea56be5271f..9d14460626f 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs @@ -88,25 +88,30 @@ public InMemoryQueryExpression([NotNull] IEntityType entityType) _valueBufferParameter = Parameter(typeof(ValueBuffer), "valueBuffer"); ServerQueryExpression = new InMemoryTableExpression(entityType); var readExpressionMap = new Dictionary(); + var discriminatorProperty = entityType.GetDiscriminatorProperty(); foreach (var property in entityType.GetAllBaseTypesInclusive().SelectMany(et => et.GetDeclaredProperties())) { readExpressionMap[property] = CreateReadValueExpression(property.ClrType, property.GetIndex(), property); } - foreach (var property in entityType.GetDerivedTypes().SelectMany(et => et.GetDeclaredProperties())) + foreach (var derivedEntityType in entityType.GetDerivedTypes()) { - readExpressionMap[property] = Condition( - LessThan( - Constant(property.GetIndex()), - MakeMemberAccess( - _valueBufferParameter, - _valueBufferCountMemberInfo)), - CreateReadValueExpression(property.ClrType, property.GetIndex(), property), - Default(property.ClrType)); + var entityCheck = derivedEntityType.GetConcreteDerivedTypesInclusive() + .Select(e => Equal(readExpressionMap[discriminatorProperty], Constant(e.GetDiscriminatorValue()))) + .Aggregate((l, r) => OrElse(l, r)); + + foreach (var property in derivedEntityType.GetDeclaredProperties()) + { + readExpressionMap[property] = Condition( + entityCheck, + CreateReadValueExpression(property.ClrType, property.GetIndex(), property), + Default(property.ClrType)); + } } var entityProjection = new EntityProjectionExpression(entityType, readExpressionMap); _projectionMapping[new ProjectionMember()] = entityProjection; + } /// @@ -123,7 +128,7 @@ public virtual Expression GetSingleScalarProjection() ConvertToEnumerable(); - return new ProjectionBindingExpression(this, new ProjectionMember(), expression.Type); + return new ProjectionBindingExpression(this, new ProjectionMember(), expression.Type.MakeNullable()); } /// diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs index 3543db62faf..6bba55266e9 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs @@ -141,7 +141,7 @@ protected override ShapedQueryExpression TranslateAll(ShapedQueryExpression sour EnumerableMethods.AnyWithoutPredicate.MakeGenericMethod(inMemoryQueryExpression.CurrentParameter.Type), inMemoryQueryExpression.ServerQueryExpression))); - return source.UpdateShaperExpression(inMemoryQueryExpression.GetSingleScalarProjection()); + return source.UpdateShaperExpression(Expression.Convert(inMemoryQueryExpression.GetSingleScalarProjection(), typeof(bool))); } /// @@ -170,11 +170,11 @@ protected override ShapedQueryExpression TranslateAny(ShapedQueryExpression sour } inMemoryQueryExpression.UpdateServerQueryExpression( - Expression.Call( - EnumerableMethods.AnyWithoutPredicate.MakeGenericMethod(inMemoryQueryExpression.CurrentParameter.Type), - inMemoryQueryExpression.ServerQueryExpression)); + Expression.Call( + EnumerableMethods.AnyWithoutPredicate.MakeGenericMethod(inMemoryQueryExpression.CurrentParameter.Type), + inMemoryQueryExpression.ServerQueryExpression)); - return source.UpdateShaperExpression(inMemoryQueryExpression.GetSingleScalarProjection()); + return source.UpdateShaperExpression(Expression.Convert(inMemoryQueryExpression.GetSingleScalarProjection(), typeof(bool))); } /// @@ -188,7 +188,7 @@ protected override ShapedQueryExpression TranslateAverage(ShapedQueryExpression Check.NotNull(source, nameof(source)); Check.NotNull(resultType, nameof(resultType)); - return TranslateScalarAggregate(source, selector, nameof(Enumerable.Average)); + return TranslateScalarAggregate(source, selector, nameof(Enumerable.Average), resultType); } /// @@ -249,7 +249,7 @@ protected override ShapedQueryExpression TranslateContains(ShapedQueryExpression inMemoryQueryExpression.GetMappedProjection(new ProjectionMember()), inMemoryQueryExpression.CurrentParameter)), item)); - return source.UpdateShaperExpression(inMemoryQueryExpression.GetSingleScalarProjection()); + return source.UpdateShaperExpression(Expression.Convert(inMemoryQueryExpression.GetSingleScalarProjection(), typeof(bool))); } /// @@ -284,7 +284,7 @@ protected override ShapedQueryExpression TranslateCount(ShapedQueryExpression so EnumerableMethods.CountWithoutPredicate.MakeGenericMethod(inMemoryQueryExpression.CurrentParameter.Type), inMemoryQueryExpression.ServerQueryExpression)); - return source.UpdateShaperExpression(inMemoryQueryExpression.GetSingleScalarProjection()); + return source.UpdateShaperExpression(Expression.Convert(inMemoryQueryExpression.GetSingleScalarProjection(), typeof(int))); } /// @@ -751,7 +751,7 @@ protected override ShapedQueryExpression TranslateLongCount(ShapedQueryExpressio inMemoryQueryExpression.CurrentParameter.Type), inMemoryQueryExpression.ServerQueryExpression)); - return source.UpdateShaperExpression(inMemoryQueryExpression.GetSingleScalarProjection()); + return source.UpdateShaperExpression(Expression.Convert(inMemoryQueryExpression.GetSingleScalarProjection(), typeof(long))); } /// @@ -765,7 +765,7 @@ protected override ShapedQueryExpression TranslateMax( { Check.NotNull(source, nameof(source)); - return TranslateScalarAggregate(source, selector, nameof(Enumerable.Max)); + return TranslateScalarAggregate(source, selector, nameof(Enumerable.Max), resultType); } /// @@ -778,7 +778,7 @@ protected override ShapedQueryExpression TranslateMin(ShapedQueryExpression sour { Check.NotNull(source, nameof(source)); - return TranslateScalarAggregate(source, selector, nameof(Enumerable.Min)); + return TranslateScalarAggregate(source, selector, nameof(Enumerable.Min), resultType); } /// @@ -1095,7 +1095,7 @@ protected override ShapedQueryExpression TranslateSum(ShapedQueryExpression sour Check.NotNull(source, nameof(source)); Check.NotNull(resultType, nameof(resultType)); - return TranslateScalarAggregate(source, selector, nameof(Enumerable.Sum)); + return TranslateScalarAggregate(source, selector, nameof(Enumerable.Sum), resultType); } /// @@ -1435,7 +1435,7 @@ ProjectionBindingExpression projectionBindingExpression } private ShapedQueryExpression TranslateScalarAggregate( - ShapedQueryExpression source, LambdaExpression selector, string methodName) + ShapedQueryExpression source, LambdaExpression selector, string methodName, Type returnType) { var inMemoryQueryExpression = (InMemoryQueryExpression)source.QueryExpression; @@ -1459,7 +1459,7 @@ private ShapedQueryExpression TranslateScalarAggregate( inMemoryQueryExpression.UpdateServerQueryExpression( Expression.Call(method, inMemoryQueryExpression.ServerQueryExpression, selector)); - return source.UpdateShaperExpression(inMemoryQueryExpression.GetSingleScalarProjection()); + return source.UpdateShaperExpression(Expression.Convert(inMemoryQueryExpression.GetSingleScalarProjection(), returnType)); MethodInfo GetMethod() => methodName switch diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryShapedQueryCompilingExpressionVisitor.InMemoryProjectionBindingRemovingExpressionVisitor.cs b/src/EFCore.InMemory/Query/Internal/InMemoryShapedQueryCompilingExpressionVisitor.InMemoryProjectionBindingRemovingExpressionVisitor.cs index 677f64cbe07..fce4c902944 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryShapedQueryCompilingExpressionVisitor.InMemoryProjectionBindingRemovingExpressionVisitor.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryShapedQueryCompilingExpressionVisitor.InMemoryProjectionBindingRemovingExpressionVisitor.cs @@ -1,6 +1,7 @@ // 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.Generic; using System.Linq.Expressions; using System.Reflection; @@ -69,6 +70,8 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp _materializationContextBindings[ (ParameterExpression)((MethodCallExpression)methodCallExpression.Arguments[0]).Object]; + Check.DebugAssert(property != null || methodCallExpression.Type.IsNullableType(), "Must read nullable value without property"); + return Expression.Call( methodCallExpression.Method, valueBuffer, @@ -88,11 +91,12 @@ protected override Expression VisitExtension(Expression extensionExpression) var queryExpression = (InMemoryQueryExpression)projectionBindingExpression.QueryExpression; var projectionIndex = (int)GetProjectionIndex(queryExpression, projectionBindingExpression); var valueBuffer = queryExpression.CurrentParameter; + var property = InferPropertyFromInner(queryExpression.Projection[projectionIndex]); + + Check.DebugAssert(property != null || projectionBindingExpression.Type.IsNullableType() + || projectionBindingExpression.Type == typeof(ValueBuffer), "Must read nullable value without property"); - return valueBuffer.CreateValueBufferReadValueExpression( - projectionBindingExpression.Type, - projectionIndex, - InferPropertyFromInner(queryExpression.Projection[projectionIndex])); + return valueBuffer.CreateValueBufferReadValueExpression(projectionBindingExpression.Type, projectionIndex, property); } return base.VisitExtension(extensionExpression); diff --git a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs index 162407c7534..9d669ea6e33 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs +++ b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs @@ -943,6 +943,12 @@ public static string DefaultValueSqlUnspecified([CanBeNull] object column, [CanB GetString("DefaultValueSqlUnspecified", nameof(column), nameof(table)), column, table); + /// + /// Sequence contains no elements. + /// + public static string SequenceContainsNoElements + => GetString("SequenceContainsNoElements"); + private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name); diff --git a/src/EFCore.Relational/Properties/RelationalStrings.resx b/src/EFCore.Relational/Properties/RelationalStrings.resx index 7c822104ecd..4b7e25267ce 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.resx +++ b/src/EFCore.Relational/Properties/RelationalStrings.resx @@ -688,4 +688,7 @@ The column '{column}' on table {table} has unspecified default value SQL. Specify the SQL before using EF Core to create the database schema. + + Sequence contains no elements. + \ No newline at end of file diff --git a/src/EFCore.Relational/Query/Internal/GetValueOrDefaultTranslator.cs b/src/EFCore.Relational/Query/Internal/GetValueOrDefaultTranslator.cs index 13d858fcfdb..58f25daf294 100644 --- a/src/EFCore.Relational/Query/Internal/GetValueOrDefaultTranslator.cs +++ b/src/EFCore.Relational/Query/Internal/GetValueOrDefaultTranslator.cs @@ -49,24 +49,12 @@ public virtual SqlExpression Translate(SqlExpression instance, MethodInfo method return _sqlExpressionFactory.Coalesce( instance, arguments.Count == 0 - ? GetDefaultConstant(method.ReturnType) + ? new SqlConstantExpression(method.ReturnType.GetDefaultValueConstant(), null) : arguments[0], instance.TypeMapping); } return null; } - - private SqlConstantExpression GetDefaultConstant(Type type) - { - return (SqlConstantExpression)_generateDefaultValueConstantMethod - .MakeGenericMethod(type).Invoke(null, Array.Empty()); - } - - private static readonly MethodInfo _generateDefaultValueConstantMethod = - typeof(GetValueOrDefaultTranslator).GetTypeInfo().GetDeclaredMethod(nameof(GenerateDefaultValueConstant)); - - private static SqlConstantExpression GenerateDefaultValueConstant() - => new SqlConstantExpression(Expression.Constant(default(TDefault)), null); } } diff --git a/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs b/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs index 789eab9c1d8..7659ac1847e 100644 --- a/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs @@ -24,6 +24,10 @@ namespace Microsoft.EntityFrameworkCore.Query.Internal /// public class RelationalProjectionBindingExpressionVisitor : ExpressionVisitor { + private static readonly MethodInfo _getParameterValueMethodInfo + = typeof(RelationalProjectionBindingExpressionVisitor) + .GetTypeInfo().GetDeclaredMethod(nameof(GetParameterValue)); + private readonly RelationalQueryableMethodTranslatingExpressionVisitor _queryableMethodTranslatingExpressionVisitor; private readonly RelationalSqlTranslatingExpressionVisitor _sqlTranslator; @@ -83,6 +87,8 @@ public virtual Expression Translate([NotNull] SelectExpression selectExpression, _projectionMembers.Clear(); _projectionMapping.Clear(); + result = MatchTypes(result, expression.Type); + return result; } @@ -200,6 +206,10 @@ static bool IsAggregateResultWithCustomShaper(MethodInfo method) } if (!(subquery.ShaperExpression is ProjectionBindingExpression + || (subquery.ShaperExpression is UnaryExpression unaryExpression + && unaryExpression.NodeType == ExpressionType.Convert + && unaryExpression.Type.MakeNullable() == unaryExpression.Operand.Type + && unaryExpression.Operand is ProjectionBindingExpression) || IsAggregateResultWithCustomShaper(methodCallExpression.Method))) { return _selectExpression.AddSingleProjection(subquery); @@ -215,7 +225,7 @@ static bool IsAggregateResultWithCustomShaper(MethodInfo method) return translation == null ? base.Visit(expression) : new ProjectionBindingExpression( - _selectExpression, _selectExpression.AddToProjection(translation), expression.Type); + _selectExpression, _selectExpression.AddToProjection(translation), expression.Type.MakeNullable()); } else { @@ -227,21 +237,46 @@ static bool IsAggregateResultWithCustomShaper(MethodInfo method) _projectionMapping[_projectionMembers.Peek()] = translation; - return new ProjectionBindingExpression(_selectExpression, _projectionMembers.Peek(), expression.Type); + return new ProjectionBindingExpression(_selectExpression, _projectionMembers.Peek(), expression.Type.MakeNullable()); } } return base.Visit(expression); } - private static readonly MethodInfo _getParameterValueMethodInfo - = typeof(RelationalProjectionBindingExpressionVisitor) - .GetTypeInfo().GetDeclaredMethod(nameof(GetParameterValue)); + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// 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 Expression VisitBinary(BinaryExpression binaryExpression) + { + var left = MatchTypes(Visit(binaryExpression.Left), binaryExpression.Left.Type); + var right = MatchTypes(Visit(binaryExpression.Right), binaryExpression.Right.Type); -#pragma warning disable IDE0052 // Remove unread private members - private static T GetParameterValue(QueryContext queryContext, string parameterName) -#pragma warning restore IDE0052 // Remove unread private members - => (T)queryContext.ParameterValues[parameterName]; + return binaryExpression.Update(left, VisitAndConvert(binaryExpression.Conversion, "VisitBinary"), right); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// 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 Expression VisitConditional(ConditionalExpression conditionalExpression) + { + var test = Visit(conditionalExpression.Test); + var ifTrue = Visit(conditionalExpression.IfTrue); + var ifFalse = Visit(conditionalExpression.IfFalse); + + if (test.Type == typeof(bool?)) + { + test = Expression.Equal(test, Expression.Constant(true, typeof(bool?))); + } + + return conditionalExpression.Update(test, ifTrue, ifFalse); + } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -255,7 +290,6 @@ protected override Expression VisitExtension(Expression extensionExpression) switch (extensionExpression) { - case EntityShaperExpression entityShaperExpression: { // TODO: Make this easier to understand some day. @@ -318,43 +352,69 @@ protected override Expression VisitExtension(Expression extensionExpression) /// 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 Expression VisitNew(NewExpression newExpression) + protected override ElementInit VisitElementInit(ElementInit elementInit) + => elementInit.Update(elementInit.Arguments.Select(e => MatchTypes(Visit(e), e.Type))); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// 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 Expression VisitMember(MemberExpression memberExpression) { - Check.NotNull(newExpression, nameof(newExpression)); + var expression = Visit(memberExpression.Expression); + Expression updatedMemberExpression = memberExpression.Update( + expression != null ? MatchTypes(expression, memberExpression.Expression.Type) : expression); - if (newExpression.Arguments.Count == 0) + if (expression?.Type.IsNullableValueType() == true) { - return newExpression; + var nullableReturnType = memberExpression.Type.MakeNullable(); + if (!memberExpression.Type.IsNullableType()) + { + updatedMemberExpression = Expression.Convert(updatedMemberExpression, nullableReturnType); + } + + updatedMemberExpression = Expression.Condition( + Expression.Equal(expression, Expression.Default(expression.Type)), + Expression.Constant(null, nullableReturnType), + updatedMemberExpression); } - if (!_clientEval - && newExpression.Members == null) + return updatedMemberExpression; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// 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 MemberAssignment VisitMemberAssignment(MemberAssignment memberAssignment) + { + var expression = memberAssignment.Expression; + Expression visitedExpression; + if (_clientEval) { - return null; + visitedExpression = Visit(memberAssignment.Expression); } - - var newArguments = new Expression[newExpression.Arguments.Count]; - for (var i = 0; i < newArguments.Length; i++) + else { - if (_clientEval) + var projectionMember = _projectionMembers.Peek().Append(memberAssignment.Member); + _projectionMembers.Push(projectionMember); + + visitedExpression = Visit(memberAssignment.Expression); + if (visitedExpression == null) { - newArguments[i] = Visit(newExpression.Arguments[i]); + return null; } - else - { - var projectionMember = _projectionMembers.Peek().Append(newExpression.Members[i]); - _projectionMembers.Push(projectionMember); - newArguments[i] = Visit(newExpression.Arguments[i]); - if (newArguments[i] == null) - { - return null; - } - _projectionMembers.Pop(); - } + _projectionMembers.Pop(); } - return newExpression.Update(newArguments); + visitedExpression = MatchTypes(visitedExpression, expression.Type); + + return memberAssignment.Update(visitedExpression); } /// @@ -398,24 +458,111 @@ protected override Expression VisitMemberInit(MemberInitExpression memberInitExp /// 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 MemberAssignment VisitMemberAssignment(MemberAssignment memberAssignment) + protected override Expression VisitMethodCall(MethodCallExpression methodCallExpression) { - if (_clientEval) + var @object = Visit(methodCallExpression.Object); + var arguments = new Expression[methodCallExpression.Arguments.Count]; + for (var i = 0; i < methodCallExpression.Arguments.Count; i++) + { + var argument = methodCallExpression.Arguments[i]; + arguments[i] = MatchTypes(Visit(argument), argument.Type); + } + + Expression updatedMethodCallExpression = methodCallExpression.Update( + @object != null ? MatchTypes(@object, methodCallExpression.Object.Type) : @object, + arguments); + + if (@object?.Type.IsNullableType() == true + && !methodCallExpression.Object.Type.IsNullableType()) { - return memberAssignment.Update(Visit(memberAssignment.Expression)); + var nullableReturnType = methodCallExpression.Type.MakeNullable(); + if (!methodCallExpression.Type.IsNullableType()) + { + updatedMethodCallExpression = Expression.Convert(updatedMethodCallExpression, nullableReturnType); + } + + return Expression.Condition( + Expression.Equal(@object, Expression.Default(@object.Type)), + Expression.Constant(null, nullableReturnType), + updatedMethodCallExpression); } - var projectionMember = _projectionMembers.Peek().Append(memberAssignment.Member); - _projectionMembers.Push(projectionMember); + return updatedMethodCallExpression; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// 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 Expression VisitNew(NewExpression newExpression) + { + Check.NotNull(newExpression, nameof(newExpression)); + + if (newExpression.Arguments.Count == 0) + { + return newExpression; + } - var visitedExpression = Visit(memberAssignment.Expression); - if (visitedExpression == null) + if (!_clientEval + && newExpression.Members == null) { return null; } - _projectionMembers.Pop(); - return memberAssignment.Update(visitedExpression); + var newArguments = new Expression[newExpression.Arguments.Count]; + for (var i = 0; i < newArguments.Length; i++) + { + var argument = newExpression.Arguments[i]; + Expression visitedArgument; + if (_clientEval) + { + visitedArgument = Visit(argument); + } + else + { + var projectionMember = _projectionMembers.Peek().Append(newExpression.Members[i]); + _projectionMembers.Push(projectionMember); + visitedArgument = Visit(argument); + if (visitedArgument == null) + { + return null; + } + + _projectionMembers.Pop(); + } + + newArguments[i] = MatchTypes(visitedArgument, argument.Type); + } + + return newExpression.Update(newArguments); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// 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 Expression VisitNewArray(NewArrayExpression newArrayExpression) + => newArrayExpression.Update(newArrayExpression.Expressions.Select(e => MatchTypes(Visit(e), e.Type))); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// 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 Expression VisitUnary(UnaryExpression unaryExpression) + { + var operand = Visit(unaryExpression.Operand); + + return (unaryExpression.NodeType == ExpressionType.Convert + || unaryExpression.NodeType == ExpressionType.ConvertChecked) + && unaryExpression.Type == operand.Type + ? operand + : unaryExpression.Update(MatchTypes(operand, unaryExpression.Operand.Type)); } // TODO: Debugging @@ -426,5 +573,26 @@ private void VerifySelectExpression(ProjectionBindingExpression projectionBindin throw new InvalidOperationException(CoreStrings.QueryFailed(projectionBindingExpression.Print(), GetType().Name)); } } + + private static Expression MatchTypes(Expression expression, Type targetType) + { + if (targetType != expression.Type + && targetType.TryGetElementType(typeof(IQueryable<>)) == null) + { + if (targetType.MakeNullable() != expression.Type) + { + throw new InvalidFilterCriteriaException(); + } + + expression = Expression.Convert(expression, targetType); + } + + return expression; + } + +#pragma warning disable IDE0052 // Remove unread private members + private static T GetParameterValue(QueryContext queryContext, string parameterName) +#pragma warning restore IDE0052 // Remove unread private members + => (T)queryContext.ParameterValues[parameterName]; } } diff --git a/src/EFCore.Relational/Query/RelationalEntityShaperExpression.cs b/src/EFCore.Relational/Query/RelationalEntityShaperExpression.cs index 5f47d60c19e..2b244e2838d 100644 --- a/src/EFCore.Relational/Query/RelationalEntityShaperExpression.cs +++ b/src/EFCore.Relational/Query/RelationalEntityShaperExpression.cs @@ -80,7 +80,9 @@ protected override LambdaExpression GenerateMaterializationCondition(IEntityType for (var i = 0; i < concreteEntityTypes.Length; i++) { body = Condition( - valueBufferParameter.CreateValueBufferReadValueExpression(typeof(bool), i, property: null), + Equal( + valueBufferParameter.CreateValueBufferReadValueExpression(typeof(bool?), i, property: null), + Constant(true, typeof(bool?))), Constant(concreteEntityTypes[i], typeof(IEntityType)), body); } diff --git a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs index 5a64572dd11..c7c437979f7 100644 --- a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs @@ -198,8 +198,12 @@ protected override ShapedQueryExpression TranslateAll(ShapedQueryExpression sour } translation = _sqlExpressionFactory.Exists(selectExpression, true); - return source.Update(_sqlExpressionFactory.Select(translation), - new ProjectionBindingExpression(source.QueryExpression, new ProjectionMember(), typeof(bool))); + + return source.Update( + _sqlExpressionFactory.Select(translation), + Expression.Convert( + new ProjectionBindingExpression(source.QueryExpression, new ProjectionMember(), typeof(bool?)), + typeof(bool))); } /// @@ -223,8 +227,12 @@ protected override ShapedQueryExpression TranslateAny(ShapedQueryExpression sour } var translation = _sqlExpressionFactory.Exists(selectExpression, false); - return source.Update(_sqlExpressionFactory.Select(translation), - new ProjectionBindingExpression(source.QueryExpression, new ProjectionMember(), typeof(bool))); + + return source.Update( + _sqlExpressionFactory.Select(translation), + Expression.Convert( + new ProjectionBindingExpression(source.QueryExpression, new ProjectionMember(), typeof(bool?)), + typeof(bool))); } /// @@ -298,8 +306,12 @@ protected override ShapedQueryExpression TranslateContains(ShapedQueryExpression selectExpression.ApplyProjection(); translation = _sqlExpressionFactory.In(translation, selectExpression, false); - return source.Update(_sqlExpressionFactory.Select(translation), - new ProjectionBindingExpression(source.QueryExpression, new ProjectionMember(), typeof(bool))); + + return source.Update( + _sqlExpressionFactory.Select(translation), + Expression.Convert( + new ProjectionBindingExpression(source.QueryExpression, new ProjectionMember(), typeof(bool?)), + typeof(bool))); } /// @@ -331,7 +343,11 @@ protected override ShapedQueryExpression TranslateCount(ShapedQueryExpression so selectExpression.ClearOrdering(); selectExpression.ReplaceProjectionMapping(projectionMapping); - return source.UpdateShaperExpression(new ProjectionBindingExpression(source.QueryExpression, new ProjectionMember(), typeof(int))); + + return source.UpdateShaperExpression( + Expression.Convert( + new ProjectionBindingExpression(source.QueryExpression, new ProjectionMember(), typeof(int?)), + typeof(int))); } /// @@ -683,7 +699,11 @@ protected override ShapedQueryExpression TranslateLongCount(ShapedQueryExpressio selectExpression.ClearOrdering(); selectExpression.ReplaceProjectionMapping(projectionMapping); - return source.UpdateShaperExpression(new ProjectionBindingExpression(source.QueryExpression, new ProjectionMember(), typeof(long))); + + return source.UpdateShaperExpression( + Expression.Convert( + new ProjectionBindingExpression(source.QueryExpression, new ProjectionMember(), typeof(long?)), + typeof(long))); } /// @@ -1553,9 +1573,9 @@ private ShapedQueryExpression AggregateResultShaper( } else { - // Sum case. Projection is always non-null. We read non-nullable value (0 if empty) - shaper = new ProjectionBindingExpression(source.QueryExpression, new ProjectionMember(), projection.Type); - // Cast to nullable type if required + // Sum case. Projection is always non-null. We read nullable value. + shaper = new ProjectionBindingExpression(source.QueryExpression, new ProjectionMember(), projection.Type.MakeNullable()); + if (resultType != shaper.Type) { shaper = Expression.Convert(shaper, resultType); diff --git a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs index 40e97ee6a14..022ce202439 100644 --- a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs @@ -413,11 +413,11 @@ protected override Expression VisitExtension(Expression extensionExpression) var projection = _selectExpression.Projection[projectionIndex]; return CreateGetValueExpression( - _dataReaderParameter, - projectionIndex, - IsNullableProjection(projection), - projection.Expression.TypeMapping, - projectionBindingExpression.Type); + _dataReaderParameter, + projectionIndex, + IsNullableProjection(projection), + projection.Expression.TypeMapping, + projectionBindingExpression.Type); } case ProjectionBindingExpression projectionBindingExpression @@ -429,19 +429,20 @@ protected override Expression VisitExtension(Expression extensionExpression) return accessor; } - var valueParameter = Expression.Parameter(projectionBindingExpression.Type); - _variables.Add(valueParameter); - var projectionIndex = (int)GetProjectionIndex(projectionBindingExpression); var projection = _selectExpression.Projection[projectionIndex]; + var nullable = IsNullableProjection(projection); + + var valueParameter = Expression.Parameter(projectionBindingExpression.Type); + _variables.Add(valueParameter); _expressions.Add(Expression.Assign(valueParameter, CreateGetValueExpression( _dataReaderParameter, projectionIndex, - IsNullableProjection(projection), + nullable, projection.Expression.TypeMapping, - projectionBindingExpression.Type))); + valueParameter.Type))); if (_containsCollectionMaterialization) { @@ -828,10 +829,15 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp : _materializationContextBindings[mappingParameter][property]; var projection = _selectExpression.Projection[projectionIndex]; + var nullable = IsNullableProjection(projection); + + Check.DebugAssert(!nullable || property != null || methodCallExpression.Type.IsNullableType(), + "For nullable reads the return type must be null unless property is specified."); + return CreateGetValueExpression( _dataReaderParameter, projectionIndex, - IsNullableProjection(projection), + nullable, projection.Expression.TypeMapping, methodCallExpression.Type, property); @@ -907,6 +913,8 @@ private Expression CreateGetValueExpression( Type clrType, IPropertyBase property = null) { + Check.DebugAssert(property != null || clrType.IsNullableType(), "Must read nullable value from database if property is not specified."); + var getMethod = typeMapping.GetDataReaderMethod(); Expression indexExpression = Expression.Constant(index); @@ -923,35 +931,31 @@ Expression valueExpression getMethod, indexExpression); - if (_readerColumns != null) + if (_readerColumns != null + && _readerColumns[index] == null) { - var columnType = valueExpression.Type; + var bufferedReaderLambdaExpression = valueExpression; + var columnType = bufferedReaderLambdaExpression.Type; if (!columnType.IsValueType || !BufferedDataReader.IsSupportedValueType(columnType)) { columnType = typeof(object); - valueExpression = Expression.Convert(valueExpression, typeof(object)); + bufferedReaderLambdaExpression = Expression.Convert(bufferedReaderLambdaExpression, columnType); } - if (_readerColumns[index] == null) - { - _readerColumns[index] = ReaderColumn.Create( - columnType, - nullable, - _indexMapParameter != null ? ((ColumnExpression)_selectExpression.Projection[index].Expression).Name : null, - Expression.Lambda( - valueExpression, - dbDataReader, - _indexMapParameter ?? Expression.Parameter(typeof(int[]))).Compile()); - } + _readerColumns[index] = ReaderColumn.Create( + columnType, + nullable, + _indexMapParameter != null ? ((ColumnExpression)_selectExpression.Projection[index].Expression).Name : null, + Expression.Lambda( + bufferedReaderLambdaExpression, + dbDataReader, + _indexMapParameter ?? Expression.Parameter(typeof(int[]))).Compile()); if (getMethod.DeclaringType != typeof(DbDataReader)) { - valueExpression - = Expression.Call( - dbDataReader, - RelationalTypeMapping.GetDataReaderMethod(columnType), - indexExpression); + valueExpression = Expression.Call( + dbDataReader, RelationalTypeMapping.GetDataReaderMethod(columnType), indexExpression); } } @@ -977,35 +981,28 @@ Expression valueExpression valueExpression = Expression.Convert(valueExpression, clrType); } - var exceptionParameter - = Expression.Parameter(typeof(Exception), name: "e"); + if (nullable) + { + valueExpression = Expression.Condition( + Expression.Call(dbDataReader, _isDbNullMethod, indexExpression), + Expression.Default(valueExpression.Type), + valueExpression); + } if (_detailedErrorsEnabled) { - var catchBlock - = Expression - .Catch( - exceptionParameter, - Expression.Call( - _throwReadValueExceptionMethod - .MakeGenericMethod(valueExpression.Type), - exceptionParameter, - Expression.Call( - dbDataReader, - _getFieldValueMethod.MakeGenericMethod(typeof(object)), - indexExpression), - Expression.Constant(property, typeof(IPropertyBase)))); + var exceptionParameter = Expression.Parameter(typeof(Exception), name: "e"); - valueExpression = Expression.TryCatch(valueExpression, catchBlock); - } + var catchBlock = Expression.Catch( + exceptionParameter, + Expression.Call( + _throwReadValueExceptionMethod.MakeGenericMethod(valueExpression.Type), + exceptionParameter, + Expression.Call(dbDataReader, _getFieldValueMethod.MakeGenericMethod(typeof(object)), indexExpression), + Expression.Constant(valueExpression.Type.MakeNullable(nullable), typeof(Type)), + Expression.Constant(property, typeof(IPropertyBase)))); - if (nullable) - { - valueExpression - = Expression.Condition( - Expression.Call(dbDataReader, _isDbNullMethod, indexExpression), - Expression.Default(valueExpression.Type), - valueExpression); + valueExpression = Expression.TryCatch(valueExpression, catchBlock); } return valueExpression; @@ -1013,9 +1010,8 @@ var catchBlock [MethodImpl(MethodImplOptions.AggressiveInlining)] private static TValue ThrowReadValueException( - Exception exception, object value, IPropertyBase property = null) + Exception exception, object value, Type expectedType, IPropertyBase property = null) { - var expectedType = typeof(TValue); var actualType = value?.GetType(); string message; diff --git a/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs index 3799d0c3f9f..6ccbaeb2e5f 100644 --- a/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs @@ -552,6 +552,10 @@ static bool IsAggregateResultWithCustomShaper(MethodInfo method) } if (!(subqueryTranslation.ShaperExpression is ProjectionBindingExpression + || (subqueryTranslation.ShaperExpression is UnaryExpression unaryExpression + && unaryExpression.NodeType == ExpressionType.Convert + && unaryExpression.Type.MakeNullable() == unaryExpression.Operand.Type + && unaryExpression.Operand is ProjectionBindingExpression) || IsAggregateResultWithCustomShaper(methodCallExpression.Method))) { return null; @@ -573,7 +577,17 @@ static bool IsAggregateResultWithCustomShaper(MethodInfo method) return subquery.Projection[0].Expression; } - return new ScalarSubqueryExpression(subquery); + SqlExpression scalarSubqueryExpression = new ScalarSubqueryExpression(subquery); + + if (subqueryTranslation.ResultCardinality == ResultCardinality.SingleOrDefault + && !subqueryTranslation.ShaperExpression.Type.IsNullableType()) + { + scalarSubqueryExpression = _sqlExpressionFactory.Coalesce( + scalarSubqueryExpression, + (SqlExpression)Visit(subqueryTranslation.ShaperExpression.Type.GetDefaultValueConstant())); + } + + return scalarSubqueryExpression; } SqlExpression sqlObject = null; diff --git a/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs index e8066a6e0e3..32136c75215 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs @@ -1199,23 +1199,32 @@ public Expression AddSingleProjection([NotNull] ShapedQueryExpression shapedQuer if (!(innerExpression is EntityShaperExpression)) { var sentinelExpression = innerSelectExpression.Limit; + var sentinelNullableType = sentinelExpression.Type.MakeNullable(); ProjectionBindingExpression dummyProjection; if (innerSelectExpression.Projection.Any()) { var index = innerSelectExpression.AddToProjection(sentinelExpression); dummyProjection = new ProjectionBindingExpression( - innerSelectExpression, index, sentinelExpression.Type); + innerSelectExpression, index, sentinelNullableType); } else { innerSelectExpression._projectionMapping[new ProjectionMember()] = sentinelExpression; dummyProjection = new ProjectionBindingExpression( - innerSelectExpression, new ProjectionMember(), sentinelExpression.Type); + innerSelectExpression, new ProjectionMember(), sentinelNullableType); } + var defaultResult = shapedQueryExpression.ResultCardinality == ResultCardinality.SingleOrDefault + ? (Expression)Default(shaperExpression.Type) + : Block( + Throw(New( + typeof(InvalidOperationException).GetConstructors().Single(ci => ci.GetParameters().Count() == 1), + Constant(RelationalStrings.SequenceContainsNoElements))), + Default(shaperExpression.Type)); + shaperExpression = Condition( - Equal(dummyProjection, Default(dummyProjection.Type)), - Default(shaperExpression.Type), + Equal(dummyProjection, Default(sentinelNullableType)), + defaultResult, shaperExpression); } @@ -2716,6 +2725,19 @@ private bool Equals(SelectExpression selectExpression) return false; } + if (_projection.Count != selectExpression._projection.Count) + { + return false; + } + + for (var i = 0; i < _projection.Count; i++) + { + if (!_projection[i].Equals(selectExpression._projection[i])) + { + return false; + } + } + if (_projectionMapping.Count != selectExpression._projectionMapping.Count) { return false; @@ -2889,6 +2911,11 @@ public override int GetHashCode() var hash = new HashCode(); hash.Add(base.GetHashCode()); + foreach (var projection in _projection) + { + hash.Add(projection); + } + foreach (var projectionMapping in _projectionMapping) { hash.Add(projectionMapping.Key); diff --git a/src/EFCore/Infrastructure/ExpressionExtensions.cs b/src/EFCore/Infrastructure/ExpressionExtensions.cs index 888613fe5cf..82c41f8c910 100644 --- a/src/EFCore/Infrastructure/ExpressionExtensions.cs +++ b/src/EFCore/Infrastructure/ExpressionExtensions.cs @@ -319,7 +319,7 @@ private static TValue ValueBufferTryReadValue( #pragma warning disable IDE0060 // Remove unused parameter in ValueBuffer valueBuffer, int index, IPropertyBase property) #pragma warning restore IDE0060 // Remove unused parameter - => valueBuffer[index] is TValue value ? value : default; + => (TValue)valueBuffer[index]; /// /// diff --git a/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.cs b/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.cs index 8cb7aa1c5e8..f05d68fe555 100644 --- a/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.cs +++ b/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.cs @@ -709,7 +709,15 @@ private NavigationExpansionExpression ProcessDefaultIfEmpty(NavigationExpansionE QueryableMethods.DefaultIfEmptyWithoutArgument.MakeGenericMethod(source.SourceElementType), source.Source)); - _entityReferenceOptionalMarkingExpressionVisitor.Visit(source.PendingSelector); + var pendingSelector = source.PendingSelector; + _entityReferenceOptionalMarkingExpressionVisitor.Visit(pendingSelector); + if (!pendingSelector.Type.IsNullableType()) + { + pendingSelector = Expression.Coalesce( + Expression.Convert(pendingSelector, pendingSelector.Type.MakeNullable()), pendingSelector.Type.GetDefaultValueConstant()); + } + + source.ApplySelector(pendingSelector); return source; } diff --git a/src/Shared/ExpressionExtensions.cs b/src/Shared/ExpressionExtensions.cs index 27b6e2295c7..842ae62e5fd 100644 --- a/src/Shared/ExpressionExtensions.cs +++ b/src/Shared/ExpressionExtensions.cs @@ -10,12 +10,6 @@ namespace System.Linq.Expressions [DebuggerStepThrough] internal static class ExpressionExtensions { - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// 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. - /// public static bool IsNullConstantExpression([NotNull] this Expression expression) => RemoveConvert(expression) is ConstantExpression constantExpression && constantExpression.Value == null; diff --git a/src/Shared/SharedTypeExtensions.cs b/src/Shared/SharedTypeExtensions.cs index a01d0a8ae73..533ff2afad9 100644 --- a/src/Shared/SharedTypeExtensions.cs +++ b/src/Shared/SharedTypeExtensions.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Linq.Expressions; using System.Reflection; using System.Runtime.CompilerServices; using System.Text; @@ -466,12 +467,6 @@ private static void ProcessGenericType(StringBuilder builder, Type type, Type[] builder.Append('>'); } - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// 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. - /// public static IEnumerable GetNamespaces([NotNull] this Type type) { if (_builtInTypeNames.ContainsKey(type)) @@ -492,5 +487,14 @@ public static IEnumerable GetNamespaces([NotNull] this Type type) } } } + + public static ConstantExpression GetDefaultValueConstant(this Type type) + => (ConstantExpression)_generateDefaultValueConstantMethod + .MakeGenericMethod(type).Invoke(null, Array.Empty()); + + private static readonly MethodInfo _generateDefaultValueConstantMethod = + typeof(SharedTypeExtensions).GetTypeInfo().GetDeclaredMethod(nameof(GenerateDefaultValueConstant)); + + private static ConstantExpression GenerateDefaultValueConstant() => Expression.Constant(default(TDefault)); } } diff --git a/test/EFCore.Cosmos.FunctionalTests/BuiltInDataTypesCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/BuiltInDataTypesCosmosTest.cs index f685f4ac7de..7a1371feb4b 100644 --- a/test/EFCore.Cosmos.FunctionalTests/BuiltInDataTypesCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/BuiltInDataTypesCosmosTest.cs @@ -80,6 +80,12 @@ FROM root c WHERE ((c[""Discriminator""] = ""BuiltInDataTypes"") AND (c[""Id""] = 13))"); } + [ConditionalFact(Skip = "Issue#21678")] + public override void Optional_datetime_reading_null_from_database() + { + base.Optional_datetime_reading_null_from_database(); + } + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); diff --git a/test/EFCore.Cosmos.FunctionalTests/CustomConvertersCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/CustomConvertersCosmosTest.cs index cb0b78b1f46..70ca847e7db 100644 --- a/test/EFCore.Cosmos.FunctionalTests/CustomConvertersCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/CustomConvertersCosmosTest.cs @@ -146,6 +146,18 @@ FROM root c WHERE (c[""Discriminator""] IN (""Blog"", ""RssBlog"") AND NOT((c[""IndexerVisible""] = ""Aye"")))"); } + [ConditionalFact(Skip = "Issue#27678")] + public override void Optional_owned_with_converter_reading_non_nullable_column() + { + base.Optional_owned_with_converter_reading_non_nullable_column(); + } + + [ConditionalFact(Skip = "Issue#21678")] + public override void Optional_datetime_reading_null_from_database() + { + base.Optional_datetime_reading_null_from_database(); + } + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindMiscellaneousQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindMiscellaneousQueryCosmosTest.cs index 57fdd89e9a0..12c4ae26e4f 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindMiscellaneousQueryCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindMiscellaneousQueryCosmosTest.cs @@ -4151,6 +4151,18 @@ public override Task Select_distinct_Select_with_client_bindings(bool async) return base.Select_distinct_Select_with_client_bindings(async); } + [ConditionalTheory(Skip = "Issue#21678")] + public override Task Non_nullable_property_through_optional_navigation(bool async) + { + return base.Non_nullable_property_through_optional_navigation(async); + } + + [ConditionalTheory(Skip = "Issue#21678")] + public override Task Max_on_empty_sequence_throws(bool async) + { + return base.Max_on_empty_sequence_throws(async); + } + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/OwnedQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/OwnedQueryCosmosTest.cs index 73f076245cf..77d93f0df61 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/OwnedQueryCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/OwnedQueryCosmosTest.cs @@ -693,9 +693,10 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con { b.OwnsOne( e => e.Throned, b => b.HasData( - new { BartonId = 1, Property = "Property" })); + new { BartonId = 1, Property = "Property", Value = 42 })); b.HasData( - new Barton { Id = 1, Simple = "Simple" }); + new Barton { Id = 1, Simple = "Simple" }, + new Barton { Id = 2, Simple = "Not" }); }); modelBuilder.Entity().HasData( diff --git a/test/EFCore.InMemory.FunctionalTests/BuiltInDataTypesInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/BuiltInDataTypesInMemoryTest.cs index 4f3405c8fb0..20968ab30e3 100644 --- a/test/EFCore.InMemory.FunctionalTests/BuiltInDataTypesInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/BuiltInDataTypesInMemoryTest.cs @@ -3,6 +3,7 @@ using System; using Microsoft.EntityFrameworkCore.TestUtilities; +using Xunit; // ReSharper disable InconsistentNaming namespace Microsoft.EntityFrameworkCore @@ -14,6 +15,12 @@ public BuiltInDataTypesInMemoryTest(BuiltInDataTypesInMemoryFixture fixture) { } + [ConditionalFact(Skip = "Issue#21680")] + public override void Optional_datetime_reading_null_from_database() + { + base.Optional_datetime_reading_null_from_database(); + } + 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 dced737313b..549be923c08 100644 --- a/test/EFCore.InMemory.FunctionalTests/ConvertToProviderTypesInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/ConvertToProviderTypesInMemoryTest.cs @@ -3,6 +3,7 @@ using System; using Microsoft.EntityFrameworkCore.TestUtilities; +using Xunit; namespace Microsoft.EntityFrameworkCore { @@ -14,6 +15,12 @@ public ConvertToProviderTypesInMemoryTest(ConvertToProviderTypesInMemoryFixture { } + [ConditionalFact(Skip = "Issue#21680")] + public override void Optional_datetime_reading_null_from_database() + { + base.Optional_datetime_reading_null_from_database(); + } + 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 e3768b48814..ed278a74517 100644 --- a/test/EFCore.InMemory.FunctionalTests/CustomConvertersInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/CustomConvertersInMemoryTest.cs @@ -14,6 +14,12 @@ public CustomConvertersInMemoryTest(CustomConvertersInMemoryFixture fixture) { } + [ConditionalFact(Skip = "Issue#21680")] + public override void Optional_datetime_reading_null_from_database() + { + base.Optional_datetime_reading_null_from_database(); + } + // Disabled: In-memory database is case-sensitive public override void Can_insert_and_read_back_with_case_insensitive_string_key() { diff --git a/test/EFCore.InMemory.FunctionalTests/Query/ComplexNavigationsQueryInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/ComplexNavigationsQueryInMemoryTest.cs index eca74b677fe..06ee1121790 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/ComplexNavigationsQueryInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/ComplexNavigationsQueryInMemoryTest.cs @@ -44,5 +44,11 @@ public override Task Select_subquery_single_nested_subquery2(bool async) { return base.Select_subquery_single_nested_subquery2(async); } + + [ConditionalTheory(Skip = "issue #17539")] + public override Task Union_over_entities_with_different_nullability(bool async) + { + return base.Union_over_entities_with_different_nullability(async); + } } } diff --git a/test/EFCore.InMemory.FunctionalTests/Query/ComplexNavigationsWeakQueryInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/ComplexNavigationsWeakQueryInMemoryTest.cs index 5bf97e7336f..6c99a4389db 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/ComplexNavigationsWeakQueryInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/ComplexNavigationsWeakQueryInMemoryTest.cs @@ -42,6 +42,12 @@ public override Task Where_nav_prop_reference_optional1_via_DefaultIfEmpty(bool return base.Where_nav_prop_reference_optional1_via_DefaultIfEmpty(async); } + [ConditionalTheory(Skip = "Issue#17539")] + public override Task Where_nav_prop_reference_optional2_via_DefaultIfEmpty(bool async) + { + return base.Where_nav_prop_reference_optional2_via_DefaultIfEmpty(async); + } + [ConditionalTheory(Skip = "Issue#17539")] public override Task Optional_navigation_propagates_nullability_to_manually_created_left_join2(bool async) { diff --git a/test/EFCore.InMemory.FunctionalTests/Query/NorthwindAggregateOperatorsQueryInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/NorthwindAggregateOperatorsQueryInMemoryTest.cs index 6ffb0a6463e..f9ef1b06484 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/NorthwindAggregateOperatorsQueryInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/NorthwindAggregateOperatorsQueryInMemoryTest.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.Linq; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore.TestUtilities; using Xunit; @@ -24,17 +25,32 @@ public NorthwindAggregateOperatorsQueryInMemoryTest( // InMemory can throw server side exception public override void Average_no_data_subquery() { - Assert.Throws(() => base.Average_no_data_subquery()); + using var context = CreateContext(); + + Assert.Equal( + "Sequence contains no elements", + Assert.Throws( + () => context.Customers.Select(c => c.Orders.Where(o => o.OrderID == -1).Average(o => o.OrderID)).ToList()).Message); } public override void Max_no_data_subquery() { - Assert.Throws(() => base.Max_no_data_subquery()); + using var context = CreateContext(); + + Assert.Equal( + "Sequence contains no elements", + Assert.Throws( + () => context.Customers.Select(c => c.Orders.Where(o => o.OrderID == -1).Max(o => o.OrderID)).ToList()).Message); } public override void Min_no_data_subquery() { - Assert.Throws(() => base.Min_no_data_subquery()); + using var context = CreateContext(); + + Assert.Equal( + "Sequence contains no elements", + Assert.Throws( + () => context.Customers.Select(c => c.Orders.Where(o => o.OrderID == -1).Min(o => o.OrderID)).ToList()).Message); } public override Task Collection_Last_member_access_in_projection_translated(bool async) diff --git a/test/EFCore.InMemory.FunctionalTests/Query/NorthwindMiscellaneousQueryInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/NorthwindMiscellaneousQueryInMemoryTest.cs index ced0ba9b9a1..fa2cc302a5c 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/NorthwindMiscellaneousQueryInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/NorthwindMiscellaneousQueryInMemoryTest.cs @@ -2,7 +2,9 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Linq; using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore.TestModels.Northwind; using Microsoft.EntityFrameworkCore.TestUtilities; using Xunit; using Xunit.Abstractions; @@ -105,5 +107,17 @@ public override Task Using_string_Equals_with_StringComparison_throws_informativ [ConditionalTheory(Skip = "issue #17386")] public override Task Using_static_string_Equals_with_StringComparison_throws_informative_error(bool async) => base.Using_static_string_Equals_with_StringComparison_throws_informative_error(async); + + public override async Task Max_on_empty_sequence_throws(bool async) + { + using var context = CreateContext(); + var query = context.Set().Select(e => new { Max = e.Orders.Max(o => o.OrderID) }); + + var message = async + ? (await Assert.ThrowsAsync(() => query.ToListAsync())).Message + : Assert.Throws(() => query.ToList()).Message; + + Assert.Equal("Sequence contains no elements", message); + } } } diff --git a/test/EFCore.Relational.Specification.Tests/Query/NullSemanticsQueryTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/NullSemanticsQueryTestBase.cs index ceb992edfb0..f668c4edd77 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/NullSemanticsQueryTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/NullSemanticsQueryTestBase.cs @@ -885,8 +885,8 @@ public virtual Task Select_IndexOf(bool async) { return AssertQueryScalar( async, - ss => ss.Set().OrderBy(e => e.Id).Select(e => e.NullableStringA.IndexOf("oo")), - ss => ss.Set().OrderBy(e => e.Id).Select(e => e.NullableStringA.MaybeScalar(x => x.IndexOf("oo")) ?? 0), + ss => ss.Set().OrderBy(e => e.Id).Select(e => (int?)e.NullableStringA.IndexOf("oo")), + ss => ss.Set().OrderBy(e => e.Id).Select(e => e.NullableStringA.MaybeScalar(x => x.IndexOf("oo"))), assertOrder: true); } diff --git a/test/EFCore.Specification.Tests/BuiltInDataTypesTestBase.cs b/test/EFCore.Specification.Tests/BuiltInDataTypesTestBase.cs index ed6b31c8271..f7d284e2427 100644 --- a/test/EFCore.Specification.Tests/BuiltInDataTypesTestBase.cs +++ b/test/EFCore.Specification.Tests/BuiltInDataTypesTestBase.cs @@ -2078,6 +2078,22 @@ public virtual void Object_to_string_conversion() Assert.Equal(expected.Char, actual.Char); } + [ConditionalFact] + public virtual void Optional_datetime_reading_null_from_database() + { + using var context = CreateContext(); + var expected = context.Set().ToList() + .Select(e => new { DT = e.DateTimeOffset == null ? (DateTime?)null : e.DateTimeOffset.Value.DateTime.Date }).ToList(); + + var actual = context.Set() + .Select(e => new { DT = e.DateTimeOffset == null ? (DateTime?)null : e.DateTimeOffset.Value.DateTime.Date }).ToList(); + + for (var i = 0; i < expected.Count; i++) + { + Assert.Equal(expected[i].DT, actual[i].DT); + } + } + public abstract class BuiltInDataTypesFixtureBase : SharedStoreFixtureBase { protected override string StoreName { get; } = "BuiltInDataTypes"; @@ -2334,6 +2350,18 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con AnimalId = 1, Method = IdentificationMethod.EarTag }); + + modelBuilder.Entity() + .HasData( + new DateTimeEnclosure + { + Id = 1, + DateTimeOffset = new DateTimeOffset(2020, 3, 12, 1, 1, 1, new TimeSpan(3, 0, 0)) + }, + new DateTimeEnclosure + { + Id = 2 + }); } protected static void MakeRequired(ModelBuilder modelBuilder) @@ -3133,5 +3161,11 @@ protected enum IdentificationMethod EarTag, Rfid } + + protected class DateTimeEnclosure + { + public int Id { get; set; } + public DateTimeOffset? DateTimeOffset { get; set; } + } } } diff --git a/test/EFCore.Specification.Tests/CustomConvertersTestBase.cs b/test/EFCore.Specification.Tests/CustomConvertersTestBase.cs index c14a52e05a8..a6281c93e1d 100644 --- a/test/EFCore.Specification.Tests/CustomConvertersTestBase.cs +++ b/test/EFCore.Specification.Tests/CustomConvertersTestBase.cs @@ -667,6 +667,26 @@ protected enum Roles public override void Object_to_string_conversion() {} + [ConditionalFact] + public virtual void Optional_owned_with_converter_reading_non_nullable_column() + { + using var context = CreateContext(); + Assert.Equal( + "Nullable object must have a value.", + Assert.Throws( + () => context.Set().Select(e => new { e.OwnedWithConverter.Value }).ToList()).Message); + } + + protected class Parent + { + public int Id { get; set; } + public OwnedWithConverter OwnedWithConverter { get; set; } + } + + protected class OwnedWithConverter + { + public int Value { get; set; } + } public abstract class CustomConvertersFixtureBase : BuiltInDataTypesFixtureBase { protected override string StoreName { get; } = "CustomConverters"; @@ -1129,6 +1149,22 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con Roles = new List { Roles.Seller } }); }); + + modelBuilder.Entity( + b => + { + b.OwnsOne( + e => e.OwnedWithConverter, + ob => + { + ob.Property(i => i.Value).HasConversion(); + ob.HasData(new { ParentId = 1, Value = 42 }); + }); + + b.HasData( + new Parent { Id = 1 }, + new Parent { Id = 2 }); + }); } private static class StringToDictionarySerializer diff --git a/test/EFCore.Specification.Tests/Query/ComplexNavigationsQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/ComplexNavigationsQueryTestBase.cs index 5263946bc5b..deb16e8e76f 100644 --- a/test/EFCore.Specification.Tests/Query/ComplexNavigationsQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/ComplexNavigationsQueryTestBase.cs @@ -4709,14 +4709,14 @@ public virtual Task Union_over_entities_with_different_nullability(bool async) .Concat(ss.Set().GroupJoin(ss.Set(), l2 => l2.Level1_Optional_Id, l1 => l1.Id, (l2, l1s) => new { l2, l1s }) .SelectMany(g => g.l1s.DefaultIfEmpty(), (g, l1) => new { l1, g.l2 }) .Where(e => e.l1.Equals(null))) - .Select(e => e.l1.Id), + .Select(e => (int?)e.l1.Id), ss => ss.Set() .GroupJoin(ss.Set(), l1 => l1.Id, l2 => l2.Level1_Optional_Id, (l1, l2s) => new { l1, l2s }) .SelectMany(g => g.l2s.DefaultIfEmpty(), (g, l2) => new { g.l1, l2 }) .Concat(ss.Set().GroupJoin(ss.Set(), l2 => l2.Level1_Optional_Id, l1 => l1.Id, (l2, l1s) => new { l2, l1s }) .SelectMany(g => g.l1s.DefaultIfEmpty(), (g, l1) => new { l1, g.l2 }) .Where(e => e.l1 == null)) - .Select(e => e.l1.MaybeScalar(x => x.Id) ?? 0)); + .Select(e => e.l1.MaybeScalar(x => x.Id))); } [ConditionalTheory] diff --git a/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs index d80bb52c69b..18a1b68ac71 100644 --- a/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs @@ -5430,26 +5430,6 @@ public virtual Task Select_subquery_boolean_empty_with_pushdown(bool async) ss => ss.Set().Select(g => (bool?)null)); } - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual Task Select_subquery_boolean_empty_with_pushdown_without_convert_to_nullable1(bool async) - { - return AssertQueryScalar( - async, - ss => ss.Set().Select(g => g.Weapons.Where(w => w.Name == "BFG").OrderBy(w => w.Id).FirstOrDefault().IsAutomatic), - ss => ss.Set().Select(g => false)); - } - - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual Task Select_subquery_boolean_empty_with_pushdown_without_convert_to_nullable2(bool async) - { - return AssertQueryScalar( - async, - ss => ss.Set().Select(g => g.Weapons.Where(w => w.Name == "BFG").OrderBy(w => w.Id).FirstOrDefault().Id), - ss => ss.Set().Select(g => 0)); - } - [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Select_subquery_distinct_singleordefault_boolean1(bool async) @@ -6801,13 +6781,13 @@ public virtual Task GroupBy_with_boolean_groupin_key_thru_navigation_access(bool return AssertQuery( async, ss => ss.Set() - .GroupBy(t => new { t.Gear.HasSoulPatch, t.Gear.Squad.Name }) + .GroupBy(t => new { HasSoulPatch = (bool?)t.Gear.HasSoulPatch, t.Gear.Squad.Name }) .Select(g => new { g.Key.HasSoulPatch, Name = g.Key.Name.ToLower() }), ss => ss.Set() .GroupBy( t => new { - HasSoulPatch = t.Gear.MaybeScalar(x => x.HasSoulPatch) ?? false, + HasSoulPatch = t.Gear.MaybeScalar(x => x.HasSoulPatch), Name = t.Gear.Squad.Name }) .Select(g => new { g.Key.HasSoulPatch, Name = g.Key.Name.Maybe(x => x.ToLower()) }), diff --git a/test/EFCore.Specification.Tests/Query/NorthwindAggregateOperatorsQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindAggregateOperatorsQueryTestBase.cs index 4526697c810..cad533b3712 100644 --- a/test/EFCore.Specification.Tests/Query/NorthwindAggregateOperatorsQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/NorthwindAggregateOperatorsQueryTestBase.cs @@ -392,8 +392,11 @@ public virtual void Min_no_data_cast_to_nullable() public virtual void Min_no_data_subquery() { using var context = CreateContext(); - // Verify that it does not throw - context.Customers.Select(c => c.Orders.Where(o => o.OrderID == -1).Min(o => o.OrderID)).ToList(); + + Assert.Equal( + "Nullable object must have a value.", + Assert.Throws( + () => context.Customers.Select(c => c.Orders.Where(o => o.OrderID == -1).Min(o => o.OrderID)).ToList()).Message); } [ConditionalFact] @@ -421,8 +424,11 @@ public virtual void Max_no_data_cast_to_nullable() public virtual void Max_no_data_subquery() { using var context = CreateContext(); - // Verify that it does not throw - context.Customers.Select(c => c.Orders.Where(o => o.OrderID == -1).Max(o => o.OrderID)).ToList(); + + Assert.Equal( + "Nullable object must have a value.", + Assert.Throws( + () => context.Customers.Select(c => c.Orders.Where(o => o.OrderID == -1).Max(o => o.OrderID)).ToList()).Message); } [ConditionalFact] @@ -450,8 +456,11 @@ public virtual void Average_no_data_cast_to_nullable() public virtual void Average_no_data_subquery() { using var context = CreateContext(); - // Verify that it does not throw - context.Customers.Select(c => c.Orders.Where(o => o.OrderID == -1).Average(o => o.OrderID)).ToList(); + + Assert.Equal( + "Nullable object must have a value.", + Assert.Throws( + () => context.Customers.Select(c => c.Orders.Where(o => o.OrderID == -1).Average(o => o.OrderID)).ToList()).Message); } [ConditionalTheory] diff --git a/test/EFCore.Specification.Tests/Query/NorthwindMiscellaneousQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindMiscellaneousQueryTestBase.cs index 822f19f9710..c5b2ac18efe 100644 --- a/test/EFCore.Specification.Tests/Query/NorthwindMiscellaneousQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/NorthwindMiscellaneousQueryTestBase.cs @@ -15,6 +15,7 @@ using Microsoft.EntityFrameworkCore.TestUtilities; using Microsoft.EntityFrameworkCore.Utilities; using Xunit; +using Xunit.Sdk; #pragma warning disable RCS1202 // Avoid NullReferenceException. @@ -6021,7 +6022,7 @@ public virtual Task ToList_over_string(bool async) async, ss => ss.Set().OrderBy(c => c.CustomerID).Select(e => new { Property = e.City.ToList() }), assertOrder: true, - elementAsserter: (e,a) => Assert.True(e.Property.SequenceEqual(a.Property))); + elementAsserter: (e, a) => Assert.True(e.Property.SequenceEqual(a.Property))); } [ConditionalTheory] @@ -6047,5 +6048,31 @@ public virtual Task AsEnumerable_over_string(bool async) } private static int ClientMethod(int s) => s; + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task Non_nullable_property_through_optional_navigation(bool async) + { + Assert.Equal( + "Nullable object must have a value.", + (await Assert.ThrowsAsync( + () => AssertQuery( + async, + ss => ss.Set().Select(e => new { e.Region.Length })))).Message); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task Max_on_empty_sequence_throws(bool async) + { + using var context = CreateContext(); + var query = context.Set().Select(e => new { Max = e.Orders.Max(o => o.OrderID) }); + + var message = async + ? (await Assert.ThrowsAsync(() => query.ToListAsync())).Message + : Assert.Throws(() => query.ToList()).Message; + + Assert.Equal("Nullable object must have a value.", message); + } } } diff --git a/test/EFCore.Specification.Tests/Query/NorthwindSelectQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindSelectQueryTestBase.cs index 41f7ee23f2b..a957bb1ce26 100644 --- a/test/EFCore.Specification.Tests/Query/NorthwindSelectQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/NorthwindSelectQueryTestBase.cs @@ -946,10 +946,10 @@ public virtual Task Project_single_element_from_collection_with_OrderBy_Distinct async, ss => ss.Set() .Select(c => c.Orders.OrderBy(o => o.OrderID).Select(o => o.CustomerID).Distinct().FirstOrDefault()) - .Select(e => e.Length), + .Select(e => (int?)e.Length), ss => ss.Set() .Select(c => c.Orders.OrderBy(o => o.OrderID).Select(o => o.CustomerID).Distinct().FirstOrDefault()) - .Select(e => e == null ? 0 : e.Length)); + .Select(e => e.MaybeScalar(e => e.Length))); } [ConditionalTheory] @@ -1449,8 +1449,9 @@ public virtual Task Project_non_nullable_value_after_FirstOrDefault_on_empty_col return AssertQueryScalar( async, ss => ss.Set().Select( - c => ss.Set().Where(o => o.CustomerID == "John Doe").Select(o => o.CustomerID).FirstOrDefault().Length), - ss => ss.Set().Select(c => 0)); + c => (int?)ss.Set().Where(o => o.CustomerID == "John Doe").Select(o => o.CustomerID).FirstOrDefault().Length), + ss => ss.Set().Select( + c => (int?)ss.Set().Where(o => o.CustomerID == "John Doe").Select(o => o.CustomerID).FirstOrDefault().MaybeScalar(e => e.Length))); } [ConditionalTheory] diff --git a/test/EFCore.Specification.Tests/Query/OwnedQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/OwnedQueryTestBase.cs index 632133a1abc..33d21bba0df 100644 --- a/test/EFCore.Specification.Tests/Query/OwnedQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/OwnedQueryTestBase.cs @@ -810,6 +810,19 @@ public virtual Task GroupBy_with_multiple_aggregates_on_owned_navigation_propert })); } + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + + public virtual async Task Non_nullable_property_through_optional_navigation(bool async) + { + Assert.Equal( + "Nullable object must have a value.", + (await Assert.ThrowsAsync( + () => AssertQuery( + async, + ss => ss.Set().Select(e => new { e.Throned.Value })))).Message); + } + protected virtual DbContext CreateContext() => Fixture.CreateContext(); public abstract class OwnedQueryFixtureBase : SharedStoreFixtureBase, IQueryFixtureBase @@ -1078,6 +1091,7 @@ public IReadOnlyDictionary GetEntityAsserters() Assert.Equal(e == null, a == null); if (a != null) { + Assert.Equal(((Throned)e).Value, ((Throned)a).Value); Assert.Equal(((Throned)e).Property, ((Throned)a).Property); } } @@ -1291,9 +1305,10 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con { b.OwnsOne( e => e.Throned, b => b.HasData( - new { BartonId = 1, Property = "Property" })); + new { BartonId = 1, Property = "Property", Value = 42 })); b.HasData( - new Barton { Id = 1, Simple = "Simple" }); + new Barton { Id = 1, Simple = "Simple" }, + new Barton { Id = 2, Simple = "Not" }); }); modelBuilder.Entity().HasData( @@ -1509,7 +1524,12 @@ private static IReadOnlyList CreateBartons() { Id = 1, Simple = "Simple", - Throned = new Throned { Property = "Property" } + Throned = new Throned { Property = "Property", Value = 42 } + }, + new Barton + { + Id = 2, + Simple = "Not", } }; @@ -1744,6 +1764,7 @@ protected class Fink protected class Throned { + public int Value { get; set; } public string Property { get; set; } } } diff --git a/test/EFCore.Specification.Tests/Query/SpatialQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/SpatialQueryTestBase.cs index 5749dabd135..bc8b31e7cfd 100644 --- a/test/EFCore.Specification.Tests/Query/SpatialQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/SpatialQueryTestBase.cs @@ -340,19 +340,6 @@ public virtual Task Disjoint_with_cast_to_nullable(bool async) elementSorter: x => x.Id); } - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual Task Disjoint_without_cast_to_nullable(bool async) - { - var point = Fixture.GeometryFactory.CreatePoint(new Coordinate(1, 1)); - - return AssertQuery( - async, - ss => ss.Set().Select(e => new { e.Id, Disjoint = e.Polygon.Disjoint(point) }), - ss => ss.Set().Select(e => new { e.Id, Disjoint = (e.Polygon == null ? false : e.Polygon.Disjoint(point)) }), - elementSorter: x => x.Id); - } - [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Disjoint_with_null_check(bool async) @@ -365,28 +352,6 @@ public virtual Task Disjoint_with_null_check(bool async) elementSorter: x => x.Id); } - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual Task Distance_without_null_check(bool async) - { - var point = Fixture.GeometryFactory.CreatePoint(new Coordinate(0, 1)); - - return AssertQuery( - async, - ss => ss.Set().Select(e => new { e.Id, Distance = e.Point.Distance(point) }), - ss => ss.Set().Select(e => new { e.Id, Distance = e.Point == null ? default : e.Point.Distance(point) }), - elementSorter: e => e.Id, - elementAsserter: (e, a) => - { - Assert.Equal(e.Id, a.Id); - - if (AssertDistances) - { - Assert.Equal(e.Distance, a.Distance); - } - }); - } - [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Distance_with_null_check(bool async) diff --git a/test/EFCore.SqlServer.FunctionalTests/BuiltInDataTypesSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/BuiltInDataTypesSqlServerTest.cs index 4ec43cb9be2..f6a70026ec2 100644 --- a/test/EFCore.SqlServer.FunctionalTests/BuiltInDataTypesSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/BuiltInDataTypesSqlServerTest.cs @@ -2593,6 +2593,8 @@ public virtual void Columns_have_expected_data_types() BuiltInNullableDataTypesShadow.TestNullableUnsignedInt32 ---> [nullable bigint] [Precision = 19 Scale = 0] BuiltInNullableDataTypesShadow.TestNullableUnsignedInt64 ---> [nullable decimal] [Precision = 20 Scale = 0] BuiltInNullableDataTypesShadow.TestString ---> [nullable nvarchar] [MaxLength = -1] +DateTimeEnclosure.DateTimeOffset ---> [nullable datetimeoffset] [Precision = 7] +DateTimeEnclosure.Id ---> [int] [Precision = 10 Scale = 0] EmailTemplate.Id ---> [uniqueidentifier] EmailTemplate.TemplateType ---> [int] [Precision = 10 Scale = 0] MappedDataTypes.BoolAsBit ---> [bit] diff --git a/test/EFCore.SqlServer.FunctionalTests/ConvertToProviderTypesSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/ConvertToProviderTypesSqlServerTest.cs index 78d856b2df1..3908752dfc5 100644 --- a/test/EFCore.SqlServer.FunctionalTests/ConvertToProviderTypesSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/ConvertToProviderTypesSqlServerTest.cs @@ -143,6 +143,8 @@ public virtual void Columns_have_expected_data_types() BuiltInNullableDataTypesShadow.TestNullableUnsignedInt32 ---> [nullable decimal] [Precision = 20 Scale = 0] BuiltInNullableDataTypesShadow.TestNullableUnsignedInt64 ---> [nullable decimal] [Precision = 20 Scale = 0] BuiltInNullableDataTypesShadow.TestString ---> [nullable nvarchar] [MaxLength = -1] +DateTimeEnclosure.DateTimeOffset ---> [nullable datetimeoffset] [Precision = 7] +DateTimeEnclosure.Id ---> [int] [Precision = 10 Scale = 0] EmailTemplate.Id ---> [uniqueidentifier] EmailTemplate.TemplateType ---> [int] [Precision = 10 Scale = 0] MaxLengthDataTypes.ByteArray5 ---> [nullable nvarchar] [MaxLength = 8] diff --git a/test/EFCore.SqlServer.FunctionalTests/CustomConvertersSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/CustomConvertersSqlServerTest.cs index 5dfc070f089..e9c8c18cde2 100644 --- a/test/EFCore.SqlServer.FunctionalTests/CustomConvertersSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/CustomConvertersSqlServerTest.cs @@ -154,6 +154,8 @@ public virtual void Columns_have_expected_data_types() CollectionEnum.Roles ---> [nullable nvarchar] [MaxLength = -1] CollectionScalar.Id ---> [int] [Precision = 10 Scale = 0] CollectionScalar.Tags ---> [nullable nvarchar] [MaxLength = -1] +DateTimeEnclosure.DateTimeOffset ---> [nullable datetimeoffset] [Precision = 7] +DateTimeEnclosure.Id ---> [int] [Precision = 10 Scale = 0] EmailTemplate.Id ---> [uniqueidentifier] EmailTemplate.TemplateType ---> [int] [Precision = 10 Scale = 0] EntityWithValueWrapper.Id ---> [int] [Precision = 10 Scale = 0] @@ -169,6 +171,8 @@ public virtual void Columns_have_expected_data_types() NonNullableDependent.PrincipalId ---> [int] [Precision = 10 Scale = 0] NullablePrincipal.Id ---> [int] [Precision = 10 Scale = 0] Order.Id ---> [nvarchar] [MaxLength = 450] +Parent.Id ---> [int] [Precision = 10 Scale = 0] +Parent.OwnedWithConverter_Value ---> [nullable nvarchar] [MaxLength = 64] Person.Id ---> [int] [Precision = 10 Scale = 0] Person.Name ---> [nullable nvarchar] [MaxLength = -1] Person.SSN ---> [nullable int] [Precision = 10 Scale = 0] diff --git a/test/EFCore.SqlServer.FunctionalTests/EverythingIsBytesSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/EverythingIsBytesSqlServerTest.cs index 8a68e987538..b7017df1f37 100644 --- a/test/EFCore.SqlServer.FunctionalTests/EverythingIsBytesSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/EverythingIsBytesSqlServerTest.cs @@ -147,6 +147,8 @@ public virtual void Columns_have_expected_data_types() BuiltInNullableDataTypesShadow.TestNullableUnsignedInt32 ---> [nullable varbinary] [MaxLength = 4] BuiltInNullableDataTypesShadow.TestNullableUnsignedInt64 ---> [nullable varbinary] [MaxLength = 8] BuiltInNullableDataTypesShadow.TestString ---> [nullable varbinary] [MaxLength = -1] +DateTimeEnclosure.DateTimeOffset ---> [nullable varbinary] [MaxLength = 12] +DateTimeEnclosure.Id ---> [varbinary] [MaxLength = 4] EmailTemplate.Id ---> [varbinary] [MaxLength = 16] EmailTemplate.TemplateType ---> [varbinary] [MaxLength = 4] MaxLengthDataTypes.ByteArray5 ---> [nullable varbinary] [MaxLength = 5] diff --git a/test/EFCore.SqlServer.FunctionalTests/EverythingIsStringsSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/EverythingIsStringsSqlServerTest.cs index 487bfe6bca6..e3ad946a16b 100644 --- a/test/EFCore.SqlServer.FunctionalTests/EverythingIsStringsSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/EverythingIsStringsSqlServerTest.cs @@ -148,6 +148,8 @@ public virtual void Columns_have_expected_data_types() BuiltInNullableDataTypesShadow.TestNullableUnsignedInt32 ---> [nullable nvarchar] [MaxLength = 64] BuiltInNullableDataTypesShadow.TestNullableUnsignedInt64 ---> [nullable nvarchar] [MaxLength = 64] BuiltInNullableDataTypesShadow.TestString ---> [nullable nvarchar] [MaxLength = -1] +DateTimeEnclosure.DateTimeOffset ---> [nullable nvarchar] [MaxLength = 48] +DateTimeEnclosure.Id ---> [nvarchar] [MaxLength = 64] EmailTemplate.Id ---> [nvarchar] [MaxLength = 36] EmailTemplate.TemplateType ---> [nvarchar] [MaxLength = -1] MaxLengthDataTypes.ByteArray5 ---> [nullable nvarchar] [MaxLength = 8] diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NavigationTest.cs b/test/EFCore.SqlServer.FunctionalTests/NavigationTest.cs similarity index 98% rename from test/EFCore.SqlServer.FunctionalTests/Query/NavigationTest.cs rename to test/EFCore.SqlServer.FunctionalTests/NavigationTest.cs index b76b7956113..23c028ca4fc 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NavigationTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/NavigationTest.cs @@ -9,8 +9,7 @@ using Microsoft.Extensions.DependencyInjection; using Xunit; -// ReSharper disable InconsistentNaming -namespace Microsoft.EntityFrameworkCore.Query +namespace Microsoft.EntityFrameworkCore { public class NavigationTest : IClassFixture { diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs index cb3361de315..0801f4292a4 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs @@ -2176,10 +2176,10 @@ public override async Task Select_join_with_key_selector_being_a_subquery(bool a AssertSql( @"SELECT [l].[Id], [l].[Date], [l].[Name], [l].[OneToMany_Optional_Self_Inverse1Id], [l].[OneToMany_Required_Self_Inverse1Id], [l].[OneToOne_Optional_Self1Id], [l0].[Id], [l0].[Date], [l0].[Level1_Optional_Id], [l0].[Level1_Required_Id], [l0].[Name], [l0].[OneToMany_Optional_Inverse2Id], [l0].[OneToMany_Optional_Self_Inverse2Id], [l0].[OneToMany_Required_Inverse2Id], [l0].[OneToMany_Required_Self_Inverse2Id], [l0].[OneToOne_Optional_PK_Inverse2Id], [l0].[OneToOne_Optional_Self2Id] FROM [LevelOne] AS [l] -INNER JOIN [LevelTwo] AS [l0] ON [l].[Id] = ( +INNER JOIN [LevelTwo] AS [l0] ON [l].[Id] = COALESCE(( SELECT TOP(1) [l1].[Id] FROM [LevelTwo] AS [l1] - ORDER BY [l1].[Id])"); + ORDER BY [l1].[Id]), 0)"); } public override async Task Contains_with_subquery_optional_navigation_and_constant_item(bool async) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs index e2217dd8b97..432db54bd79 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs @@ -529,29 +529,23 @@ public override async Task Where_enum_has_flag_subquery(bool async) AssertSql( @"SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] FROM [Gears] AS [g] -WHERE (([g].[Rank] & ( +WHERE ([g].[Rank] & COALESCE(( SELECT TOP(1) [g0].[Rank] FROM [Gears] AS [g0] - ORDER BY [g0].[Nickname], [g0].[SquadId])) = ( - SELECT TOP(1) [g0].[Rank] - FROM [Gears] AS [g0] - ORDER BY [g0].[Nickname], [g0].[SquadId])) OR ( + ORDER BY [g0].[Nickname], [g0].[SquadId]), 0)) = COALESCE(( SELECT TOP(1) [g0].[Rank] FROM [Gears] AS [g0] - ORDER BY [g0].[Nickname], [g0].[SquadId]) IS NULL", + ORDER BY [g0].[Nickname], [g0].[SquadId]), 0)", // @"SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] FROM [Gears] AS [g] -WHERE ((2 & ( - SELECT TOP(1) [g0].[Rank] - FROM [Gears] AS [g0] - ORDER BY [g0].[Nickname], [g0].[SquadId])) = ( +WHERE (2 & COALESCE(( SELECT TOP(1) [g0].[Rank] FROM [Gears] AS [g0] - ORDER BY [g0].[Nickname], [g0].[SquadId])) OR ( + ORDER BY [g0].[Nickname], [g0].[SquadId]), 0)) = COALESCE(( SELECT TOP(1) [g0].[Rank] FROM [Gears] AS [g0] - ORDER BY [g0].[Nickname], [g0].[SquadId]) IS NULL"); + ORDER BY [g0].[Nickname], [g0].[SquadId]), 0)"); } public override async Task Where_enum_has_flag_subquery_with_pushdown(bool async) @@ -1263,11 +1257,11 @@ public override async Task Where_subquery_boolean(bool async) AssertSql( @"SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] FROM [Gears] AS [g] -WHERE ( +WHERE COALESCE(( SELECT TOP(1) [w].[IsAutomatic] FROM [Weapons] AS [w] WHERE [g].[FullName] = [w].[OwnerFullName] - ORDER BY [w].[Id]) = CAST(1 AS bit)"); + ORDER BY [w].[Id]), CAST(0 AS bit)) = CAST(1 AS bit)"); } public override async Task Where_subquery_boolean_with_pushdown(bool async) @@ -1291,14 +1285,14 @@ public override async Task Where_subquery_distinct_firstordefault_boolean(bool a AssertSql( @"SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] FROM [Gears] AS [g] -WHERE ([g].[HasSoulPatch] = CAST(1 AS bit)) AND (( +WHERE ([g].[HasSoulPatch] = CAST(1 AS bit)) AND (COALESCE(( SELECT TOP(1) [t].[IsAutomatic] FROM ( SELECT DISTINCT [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId] FROM [Weapons] AS [w] WHERE [g].[FullName] = [w].[OwnerFullName] ) AS [t] - ORDER BY [t].[Id]) = CAST(1 AS bit))"); + ORDER BY [t].[Id]), CAST(0 AS bit)) = CAST(1 AS bit))"); } public override async Task Where_subquery_distinct_firstordefault_boolean_with_pushdown(bool async) @@ -1343,13 +1337,13 @@ public override async Task Where_subquery_distinct_singleordefault_boolean1(bool AssertSql( @"SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] FROM [Gears] AS [g] -WHERE ([g].[HasSoulPatch] = CAST(1 AS bit)) AND (( +WHERE ([g].[HasSoulPatch] = CAST(1 AS bit)) AND (COALESCE(( SELECT TOP(1) [t].[IsAutomatic] FROM ( SELECT DISTINCT [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId] FROM [Weapons] AS [w] WHERE ([g].[FullName] = [w].[OwnerFullName]) AND ([w].[Name] LIKE N'%Lancer%') - ) AS [t]) = CAST(1 AS bit)) + ) AS [t]), CAST(0 AS bit)) = CAST(1 AS bit)) ORDER BY [g].[Nickname]"); } @@ -1360,10 +1354,10 @@ public override async Task Where_subquery_distinct_singleordefault_boolean2(bool AssertSql( @"SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] FROM [Gears] AS [g] -WHERE ([g].[HasSoulPatch] = CAST(1 AS bit)) AND (( +WHERE ([g].[HasSoulPatch] = CAST(1 AS bit)) AND (COALESCE(( SELECT DISTINCT TOP(1) [w].[IsAutomatic] FROM [Weapons] AS [w] - WHERE ([g].[FullName] = [w].[OwnerFullName]) AND ([w].[Name] LIKE N'%Lancer%')) = CAST(1 AS bit)) + WHERE ([g].[FullName] = [w].[OwnerFullName]) AND ([w].[Name] LIKE N'%Lancer%')), CAST(0 AS bit)) = CAST(1 AS bit)) ORDER BY [g].[Nickname]"); } @@ -1427,14 +1421,14 @@ public override async Task Where_subquery_distinct_orderby_firstordefault_boolea AssertSql( @"SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] FROM [Gears] AS [g] -WHERE ([g].[HasSoulPatch] = CAST(1 AS bit)) AND (( +WHERE ([g].[HasSoulPatch] = CAST(1 AS bit)) AND (COALESCE(( SELECT TOP(1) [t].[IsAutomatic] FROM ( SELECT DISTINCT [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId] FROM [Weapons] AS [w] WHERE [g].[FullName] = [w].[OwnerFullName] ) AS [t] - ORDER BY [t].[Id]) = CAST(1 AS bit))"); + ORDER BY [t].[Id]), CAST(0 AS bit)) = CAST(1 AS bit))"); } public override async Task Where_subquery_distinct_orderby_firstordefault_boolean_with_pushdown(bool async) @@ -4704,10 +4698,10 @@ public override async Task Project_one_value_type_from_empty_collection(bool asy await base.Project_one_value_type_from_empty_collection(async); AssertSql( - @"SELECT [s].[Name], ( + @"SELECT [s].[Name], COALESCE(( SELECT TOP(1) [g].[SquadId] FROM [Gears] AS [g] - WHERE ([s].[Id] = [g].[SquadId]) AND ([g].[HasSoulPatch] = CAST(1 AS bit))) AS [SquadId] + WHERE ([s].[Id] = [g].[SquadId]) AND ([g].[HasSoulPatch] = CAST(1 AS bit))), 0) AS [SquadId] FROM [Squads] AS [s] WHERE [s].[Name] = N'Kilo'"); } @@ -4763,10 +4757,10 @@ public override async Task Select_subquery_projecting_single_constant_int(bool a await base.Select_subquery_projecting_single_constant_int(async); AssertSql( - @"SELECT [s].[Name], ( + @"SELECT [s].[Name], COALESCE(( SELECT TOP(1) 42 FROM [Gears] AS [g] - WHERE ([s].[Id] = [g].[SquadId]) AND ([g].[HasSoulPatch] = CAST(1 AS bit))) AS [Gear] + WHERE ([s].[Id] = [g].[SquadId]) AND ([g].[HasSoulPatch] = CAST(1 AS bit))), 0) AS [Gear] FROM [Squads] AS [s]"); } @@ -4787,10 +4781,10 @@ public override async Task Select_subquery_projecting_single_constant_bool(bool await base.Select_subquery_projecting_single_constant_bool(async); AssertSql( - @"SELECT [s].[Name], ( + @"SELECT [s].[Name], COALESCE(( SELECT TOP(1) CAST(1 AS bit) FROM [Gears] AS [g] - WHERE ([s].[Id] = [g].[SquadId]) AND ([g].[HasSoulPatch] = CAST(1 AS bit))) AS [Gear] + WHERE ([s].[Id] = [g].[SquadId]) AND ([g].[HasSoulPatch] = CAST(1 AS bit))), CAST(0 AS bit)) AS [Gear] FROM [Squads] AS [s]"); } @@ -4928,11 +4922,11 @@ public override async Task Include_collection_with_complex_OrderBy3(bool async) FROM [Gears] AS [g] LEFT JOIN [Gears] AS [g0] ON ([g].[Nickname] = [g0].[LeaderNickname]) AND ([g].[SquadId] = [g0].[LeaderSquadId]) WHERE [g].[Discriminator] = N'Officer' -ORDER BY ( +ORDER BY COALESCE(( SELECT TOP(1) [w].[IsAutomatic] FROM [Weapons] AS [w] WHERE [g].[FullName] = [w].[OwnerFullName] - ORDER BY [w].[Id]), [g].[Nickname], [g].[SquadId], [g0].[Nickname], [g0].[SquadId]"); + ORDER BY [w].[Id]), CAST(0 AS bit)), [g].[Nickname], [g].[SquadId], [g0].[Nickname], [g0].[SquadId]"); } public override async Task Correlated_collection_with_complex_OrderBy(bool async) @@ -4970,10 +4964,10 @@ FROM [Gears] AS [g0] ORDER BY ( SELECT COUNT(*) FROM [Weapons] AS [w] - WHERE ([g].[FullName] = [w].[OwnerFullName]) AND ([w].[IsAutomatic] = ( + WHERE ([g].[FullName] = [w].[OwnerFullName]) AND ([w].[IsAutomatic] = COALESCE(( SELECT TOP(1) [g1].[HasSoulPatch] FROM [Gears] AS [g1] - WHERE [g1].[Nickname] = N'Marcus'))), [g].[Nickname], [g].[SquadId], [t].[Nickname], [t].[SquadId]"); + WHERE [g1].[Nickname] = N'Marcus'), CAST(0 AS bit)))), [g].[Nickname], [g].[SquadId], [t].[Nickname], [t].[SquadId]"); } public override async Task Cast_to_derived_type_after_OfType_works(bool async) @@ -4991,11 +4985,11 @@ public override async Task Select_subquery_boolean(bool async) await base.Select_subquery_boolean(async); AssertSql( - @"SELECT ( + @"SELECT COALESCE(( SELECT TOP(1) [w].[IsAutomatic] FROM [Weapons] AS [w] WHERE [g].[FullName] = [w].[OwnerFullName] - ORDER BY [w].[Id]) + ORDER BY [w].[Id]), CAST(0 AS bit)) FROM [Gears] AS [g]"); } @@ -5030,11 +5024,11 @@ public override async Task Select_subquery_int_with_outside_cast_and_coalesce(bo await base.Select_subquery_int_with_outside_cast_and_coalesce(async); AssertSql( - @"SELECT COALESCE(( + @"SELECT COALESCE(COALESCE(( SELECT TOP(1) [w].[Id] FROM [Weapons] AS [w] WHERE [g].[FullName] = [w].[OwnerFullName] - ORDER BY [w].[Id]), 42) + ORDER BY [w].[Id]), 0), 42) FROM [Gears] AS [g]"); } @@ -5073,11 +5067,11 @@ public override async Task Select_subquery_boolean_empty(bool async) await base.Select_subquery_boolean_empty(async); AssertSql( - @"SELECT ( + @"SELECT COALESCE(( SELECT TOP(1) [w].[IsAutomatic] FROM [Weapons] AS [w] WHERE ([g].[FullName] = [w].[OwnerFullName]) AND ([w].[Name] = N'BFG') - ORDER BY [w].[Id]) + ORDER BY [w].[Id]), CAST(0 AS bit)) FROM [Gears] AS [g]"); } @@ -5094,44 +5088,18 @@ FROM [Weapons] AS [w] FROM [Gears] AS [g]"); } - public override async Task Select_subquery_boolean_empty_with_pushdown_without_convert_to_nullable1(bool async) - { - await base.Select_subquery_boolean_empty_with_pushdown_without_convert_to_nullable1(async); - - AssertSql( - @"SELECT ( - SELECT TOP(1) [w].[IsAutomatic] - FROM [Weapons] AS [w] - WHERE ([g].[FullName] = [w].[OwnerFullName]) AND ([w].[Name] = N'BFG') - ORDER BY [w].[Id]) -FROM [Gears] AS [g]"); - } - - public override async Task Select_subquery_boolean_empty_with_pushdown_without_convert_to_nullable2(bool async) - { - await base.Select_subquery_boolean_empty_with_pushdown_without_convert_to_nullable2(async); - - AssertSql( - @"SELECT ( - SELECT TOP(1) [w].[Id] - FROM [Weapons] AS [w] - WHERE ([g].[FullName] = [w].[OwnerFullName]) AND ([w].[Name] = N'BFG') - ORDER BY [w].[Id]) -FROM [Gears] AS [g]"); - } - public override async Task Select_subquery_distinct_singleordefault_boolean1(bool async) { await base.Select_subquery_distinct_singleordefault_boolean1(async); AssertSql( - @"SELECT ( + @"SELECT COALESCE(( SELECT TOP(1) [t].[IsAutomatic] FROM ( SELECT DISTINCT [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId] FROM [Weapons] AS [w] WHERE ([g].[FullName] = [w].[OwnerFullName]) AND ([w].[Name] LIKE N'%Lancer%') - ) AS [t]) + ) AS [t]), CAST(0 AS bit)) FROM [Gears] AS [g] WHERE [g].[HasSoulPatch] = CAST(1 AS bit)"); } @@ -5141,10 +5109,10 @@ public override async Task Select_subquery_distinct_singleordefault_boolean2(boo await base.Select_subquery_distinct_singleordefault_boolean2(async); AssertSql( - @"SELECT ( + @"SELECT COALESCE(( SELECT DISTINCT TOP(1) [w].[IsAutomatic] FROM [Weapons] AS [w] - WHERE ([g].[FullName] = [w].[OwnerFullName]) AND ([w].[Name] LIKE N'%Lancer%')) + WHERE ([g].[FullName] = [w].[OwnerFullName]) AND ([w].[Name] LIKE N'%Lancer%')), CAST(0 AS bit)) FROM [Gears] AS [g] WHERE [g].[HasSoulPatch] = CAST(1 AS bit)"); } @@ -5170,13 +5138,13 @@ public override async Task Select_subquery_distinct_singleordefault_boolean_empt await base.Select_subquery_distinct_singleordefault_boolean_empty1(async); AssertSql( - @"SELECT ( + @"SELECT COALESCE(( SELECT TOP(1) [t].[IsAutomatic] FROM ( SELECT DISTINCT [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId] FROM [Weapons] AS [w] WHERE ([g].[FullName] = [w].[OwnerFullName]) AND ([w].[Name] = N'BFG') - ) AS [t]) + ) AS [t]), CAST(0 AS bit)) FROM [Gears] AS [g] WHERE [g].[HasSoulPatch] = CAST(1 AS bit)"); } @@ -5186,10 +5154,10 @@ public override async Task Select_subquery_distinct_singleordefault_boolean_empt await base.Select_subquery_distinct_singleordefault_boolean_empty2(async); AssertSql( - @"SELECT ( + @"SELECT COALESCE(( SELECT DISTINCT TOP(1) [w].[IsAutomatic] FROM [Weapons] AS [w] - WHERE ([g].[FullName] = [w].[OwnerFullName]) AND ([w].[Name] = N'BFG')) + WHERE ([g].[FullName] = [w].[OwnerFullName]) AND ([w].[Name] = N'BFG')), CAST(0 AS bit)) FROM [Gears] AS [g] WHERE [g].[HasSoulPatch] = CAST(1 AS bit)"); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs index ebc9f621a0d..a0031f14a35 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs @@ -441,7 +441,7 @@ public override async Task Default_if_empty_top_level_projection(bool async) await base.Default_if_empty_top_level_projection(async); AssertSql( - @"SELECT [t].[EmployeeID] + @"SELECT COALESCE([t].[EmployeeID], 0) FROM ( SELECT NULL AS [empty] ) AS [empty] diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindNavigationsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindNavigationsQuerySqlServerTest.cs index 03fbc7054d3..5bcb32ff181 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindNavigationsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindNavigationsQuerySqlServerTest.cs @@ -896,11 +896,11 @@ public override async Task Project_single_scalar_value_subquery_in_query_with_op AssertSql( @"@__p_0='3' -SELECT [t].[OrderID], ( +SELECT [t].[OrderID], COALESCE(( SELECT TOP(1) [o].[OrderID] FROM [Order Details] AS [o] WHERE [t].[OrderID] = [o].[OrderID] - ORDER BY [o].[OrderID], [o].[ProductID]) AS [OrderDetail], [c].[City] + ORDER BY [o].[OrderID], [o].[ProductID]), 0) AS [OrderDetail], [c].[City] FROM ( SELECT TOP(@__p_0) [o0].[OrderID], [o0].[CustomerID], [o0].[EmployeeID], [o0].[OrderDate] FROM [Orders] AS [o0] diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSelectQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSelectQuerySqlServerTest.cs index 52c397f74db..24525477e43 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSelectQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSelectQuerySqlServerTest.cs @@ -371,13 +371,13 @@ public override void Select_nested_collection_multi_level4() base.Select_nested_collection_multi_level4(); AssertSql( - @"SELECT ( + @"SELECT COALESCE(( SELECT TOP(1) ( SELECT COUNT(*) FROM [Order Details] AS [o] WHERE ([o0].[OrderID] = [o].[OrderID]) AND ([o].[OrderID] > 10)) FROM [Orders] AS [o0] - WHERE ([c].[CustomerID] = [o0].[CustomerID]) AND ([o0].[OrderID] < 10500)) AS [Order] + WHERE ([c].[CustomerID] = [o0].[CustomerID]) AND ([o0].[OrderID] < 10500)), 0) AS [Order] FROM [Customers] AS [c] WHERE [c].[CustomerID] LIKE N'A%'"); } @@ -387,8 +387,8 @@ public override void Select_nested_collection_multi_level5() base.Select_nested_collection_multi_level5(); AssertSql( - @"SELECT ( - SELECT TOP(1) ( + @"SELECT COALESCE(( + SELECT TOP(1) COALESCE(( SELECT TOP(1) [o].[ProductID] FROM [Order Details] AS [o] WHERE ([o1].[OrderID] = [o].[OrderID]) AND (([o].[OrderID] <> ( @@ -397,9 +397,9 @@ FROM [Orders] AS [o0] WHERE [c].[CustomerID] = [o0].[CustomerID])) OR ( SELECT COUNT(*) FROM [Orders] AS [o0] - WHERE [c].[CustomerID] = [o0].[CustomerID]) IS NULL)) + WHERE [c].[CustomerID] = [o0].[CustomerID]) IS NULL)), 0) FROM [Orders] AS [o1] - WHERE ([c].[CustomerID] = [o1].[CustomerID]) AND ([o1].[OrderID] < 10500)) AS [Order] + WHERE ([c].[CustomerID] = [o1].[CustomerID]) AND ([o1].[OrderID] < 10500)), 0) AS [Order] FROM [Customers] AS [c] WHERE [c].[CustomerID] LIKE N'A%'"); } @@ -409,13 +409,13 @@ public override void Select_nested_collection_multi_level6() base.Select_nested_collection_multi_level6(); AssertSql( - @"SELECT ( - SELECT TOP(1) ( + @"SELECT COALESCE(( + SELECT TOP(1) COALESCE(( SELECT TOP(1) [o].[ProductID] FROM [Order Details] AS [o] - WHERE ([o0].[OrderID] = [o].[OrderID]) AND ([o].[OrderID] <> CAST(LEN([c].[CustomerID]) AS int))) + WHERE ([o0].[OrderID] = [o].[OrderID]) AND ([o].[OrderID] <> CAST(LEN([c].[CustomerID]) AS int))), 0) FROM [Orders] AS [o0] - WHERE ([c].[CustomerID] = [o0].[CustomerID]) AND ([o0].[OrderID] < 10500)) AS [Order] + WHERE ([c].[CustomerID] = [o0].[CustomerID]) AND ([o0].[OrderID] < 10500)), 0) AS [Order] FROM [Customers] AS [c] WHERE [c].[CustomerID] LIKE N'A%'"); } @@ -774,7 +774,7 @@ public override async Task Project_single_element_from_collection_with_OrderBy_o await base.Project_single_element_from_collection_with_OrderBy_over_navigation_Take_and_FirstOrDefault(async); AssertSql( - @"SELECT ( + @"SELECT COALESCE(( SELECT TOP(1) [t].[OrderID] FROM ( SELECT TOP(1) [o].[OrderID], [o].[ProductID], [p].[ProductID] AS [ProductID0], [p].[ProductName] @@ -783,7 +783,7 @@ FROM [Order Details] AS [o] WHERE [o0].[OrderID] = [o].[OrderID] ORDER BY [p].[ProductName] ) AS [t] - ORDER BY [t].[ProductName]) + ORDER BY [t].[ProductName]), 0) FROM [Orders] AS [o0] WHERE [o0].[OrderID] < 10300"); } @@ -1127,11 +1127,11 @@ public override async Task FirstOrDefault_over_empty_collection_of_value_type_re await base.FirstOrDefault_over_empty_collection_of_value_type_returns_correct_results(async); AssertSql( - @"SELECT [c].[CustomerID], ( + @"SELECT [c].[CustomerID], COALESCE(( SELECT TOP(1) [o].[OrderID] FROM [Orders] AS [o] WHERE [c].[CustomerID] = [o].[CustomerID] - ORDER BY [o].[OrderID]) AS [OrderId] + ORDER BY [o].[OrderID]), 0) AS [OrderId] FROM [Customers] AS [c] WHERE [c].[CustomerID] = N'FISSA'"); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/OwnedQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/OwnedQuerySqlServerTest.cs index b24a79bc4b1..f8c8a4de9cf 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/OwnedQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/OwnedQuerySqlServerTest.cs @@ -153,14 +153,14 @@ public override async Task Navigation_rewrite_on_owned_collection_with_compositi await base.Navigation_rewrite_on_owned_collection_with_composition(async); AssertSql( - @"SELECT ( + @"SELECT COALESCE(( SELECT TOP(1) CASE WHEN [o].[Id] <> 42 THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END FROM [Order] AS [o] WHERE [o0].[Id] = [o].[ClientId] - ORDER BY [o].[Id]) + ORDER BY [o].[Id]), CAST(0 AS bit)) FROM [OwnedPerson] AS [o0] ORDER BY [o0].[Id]"); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs index bfab233df86..0770508dbf1 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs @@ -6293,14 +6293,14 @@ public virtual void Let_multiple_references_with_reference_to_outer_2() @"SELECT [a0].[Id], [a0].[ActivityTypeId], [a0].[DateTime], [a0].[Points], ( SELECT TOP(1) [c].[Id] FROM [CompetitionSeasons] AS [c] - WHERE ([c].[StartDate] <= [a0].[DateTime]) AND ([a0].[DateTime] < [c].[EndDate])) AS [CompetitionSeasonId], COALESCE([a0].[Points], ( + WHERE ([c].[StartDate] <= [a0].[DateTime]) AND ([a0].[DateTime] < [c].[EndDate])) AS [CompetitionSeasonId], COALESCE([a0].[Points], COALESCE(( SELECT TOP(1) [a].[Points] FROM [ActivityTypePoints12456] AS [a] INNER JOIN [CompetitionSeasons] AS [c0] ON [a].[CompetitionSeasonId] = [c0].[Id] WHERE ([a1].[Id] = [a].[ActivityTypeId]) AND ([c0].[Id] = ( SELECT TOP(1) [c1].[Id] FROM [CompetitionSeasons] AS [c1] - WHERE ([c1].[StartDate] <= [a0].[DateTime]) AND ([a0].[DateTime] < [c1].[EndDate]))))) AS [Points] + WHERE ([c1].[StartDate] <= [a0].[DateTime]) AND ([a0].[DateTime] < [c1].[EndDate])))), 0)) AS [Points] FROM [Activities] AS [a0] INNER JOIN [ActivityType12456] AS [a1] ON [a0].[ActivityTypeId] = [a1].[Id]"); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/SpatialQuerySqlServerGeographyTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/SpatialQuerySqlServerGeographyTest.cs index 75e85d501a7..cf7fc87474f 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/SpatialQuerySqlServerGeographyTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/SpatialQuerySqlServerGeographyTest.cs @@ -188,17 +188,6 @@ public override async Task Disjoint_with_cast_to_nullable(bool async) AssertSql( @"@__point_0='0xE6100000010C000000000000F03F000000000000F03F' (Size = 22) (DbType = Object) -SELECT [p].[Id], [p].[Polygon].STDisjoint(@__point_0) AS [Disjoint] -FROM [PolygonEntity] AS [p]"); - } - - public override async Task Disjoint_without_cast_to_nullable(bool async) - { - await base.Disjoint_without_cast_to_nullable(async); - - AssertSql( - @"@__point_0='0xE6100000010C000000000000F03F000000000000F03F' (Size = 22) (DbType = Object) - SELECT [p].[Id], [p].[Polygon].STDisjoint(@__point_0) AS [Disjoint] FROM [PolygonEntity] AS [p]"); } @@ -217,17 +206,6 @@ END AS [Disjoint] FROM [PolygonEntity] AS [p]"); } - public override async Task Distance_without_null_check(bool async) - { - await base.Distance_without_null_check(async); - - AssertSql( - @"@__point_0='0xE6100000010C000000000000F03F0000000000000000' (Size = 22) (DbType = Object) - -SELECT [p].[Id], [p].[Point].STDistance(@__point_0) AS [Distance] -FROM [PointEntity] AS [p]"); - } - public override async Task Distance_with_null_check(bool async) { await base.Distance_with_null_check(async); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/SpatialQuerySqlServerGeometryTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/SpatialQuerySqlServerGeometryTest.cs index ff96b769104..61716294eab 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/SpatialQuerySqlServerGeometryTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/SpatialQuerySqlServerGeometryTest.cs @@ -275,17 +275,6 @@ public override async Task Disjoint_with_cast_to_nullable(bool async) AssertSql( @"@__point_0='0x00000000010C000000000000F03F000000000000F03F' (Size = 22) (DbType = Object) -SELECT [p].[Id], [p].[Polygon].STDisjoint(@__point_0) AS [Disjoint] -FROM [PolygonEntity] AS [p]"); - } - - public override async Task Disjoint_without_cast_to_nullable(bool async) - { - await base.Disjoint_without_cast_to_nullable(async); - - AssertSql( - @"@__point_0='0x00000000010C000000000000F03F000000000000F03F' (Size = 22) (DbType = Object) - SELECT [p].[Id], [p].[Polygon].STDisjoint(@__point_0) AS [Disjoint] FROM [PolygonEntity] AS [p]"); } @@ -304,17 +293,6 @@ END AS [Disjoint] FROM [PolygonEntity] AS [p]"); } - public override async Task Distance_without_null_check(bool async) - { - await base.Distance_without_null_check(async); - - AssertSql( - @"@__point_0='0x00000000010C0000000000000000000000000000F03F' (Size = 22) (DbType = Object) - -SELECT [p].[Id], [p].[Point].STDistance(@__point_0) AS [Distance] -FROM [PointEntity] AS [p]"); - } - public override async Task Distance_with_null_check(bool async) { await base.Distance_with_null_check(async); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs index e026b220ee5..93ec44fcd12 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs @@ -719,19 +719,15 @@ ELSE CAST(0 AS bit) END AS [IsOfficer] FROM [Gears] AS [g] LEFT JOIN [Officers] AS [o] ON ([g].[Nickname] = [o].[Nickname]) AND ([g].[SquadId] = [o].[SquadId]) -WHERE (([g].[Rank] & ( - SELECT TOP(1) [g0].[Rank] - FROM [Gears] AS [g0] - LEFT JOIN [Officers] AS [o0] ON ([g0].[Nickname] = [o0].[Nickname]) AND ([g0].[SquadId] = [o0].[SquadId]) - ORDER BY [g0].[Nickname], [g0].[SquadId])) = ( +WHERE ([g].[Rank] & COALESCE(( SELECT TOP(1) [g0].[Rank] FROM [Gears] AS [g0] LEFT JOIN [Officers] AS [o0] ON ([g0].[Nickname] = [o0].[Nickname]) AND ([g0].[SquadId] = [o0].[SquadId]) - ORDER BY [g0].[Nickname], [g0].[SquadId])) OR ( + ORDER BY [g0].[Nickname], [g0].[SquadId]), 0)) = COALESCE(( SELECT TOP(1) [g0].[Rank] FROM [Gears] AS [g0] LEFT JOIN [Officers] AS [o0] ON ([g0].[Nickname] = [o0].[Nickname]) AND ([g0].[SquadId] = [o0].[SquadId]) - ORDER BY [g0].[Nickname], [g0].[SquadId]) IS NULL", + ORDER BY [g0].[Nickname], [g0].[SquadId]), 0)", // @"SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank], CASE WHEN [o].[Nickname] IS NOT NULL THEN CAST(1 AS bit) @@ -739,19 +735,15 @@ ELSE CAST(0 AS bit) END AS [IsOfficer] FROM [Gears] AS [g] LEFT JOIN [Officers] AS [o] ON ([g].[Nickname] = [o].[Nickname]) AND ([g].[SquadId] = [o].[SquadId]) -WHERE ((2 & ( +WHERE (2 & COALESCE(( SELECT TOP(1) [g0].[Rank] FROM [Gears] AS [g0] LEFT JOIN [Officers] AS [o0] ON ([g0].[Nickname] = [o0].[Nickname]) AND ([g0].[SquadId] = [o0].[SquadId]) - ORDER BY [g0].[Nickname], [g0].[SquadId])) = ( + ORDER BY [g0].[Nickname], [g0].[SquadId]), 0)) = COALESCE(( SELECT TOP(1) [g0].[Rank] FROM [Gears] AS [g0] LEFT JOIN [Officers] AS [o0] ON ([g0].[Nickname] = [o0].[Nickname]) AND ([g0].[SquadId] = [o0].[SquadId]) - ORDER BY [g0].[Nickname], [g0].[SquadId])) OR ( - SELECT TOP(1) [g0].[Rank] - FROM [Gears] AS [g0] - LEFT JOIN [Officers] AS [o0] ON ([g0].[Nickname] = [o0].[Nickname]) AND ([g0].[SquadId] = [o0].[SquadId]) - ORDER BY [g0].[Nickname], [g0].[SquadId]) IS NULL"); + ORDER BY [g0].[Nickname], [g0].[SquadId]), 0)"); } public override async Task Where_enum_has_flag_subquery_with_pushdown(bool async) @@ -1670,11 +1662,11 @@ ELSE CAST(0 AS bit) END AS [IsOfficer] FROM [Gears] AS [g] LEFT JOIN [Officers] AS [o] ON ([g].[Nickname] = [o].[Nickname]) AND ([g].[SquadId] = [o].[SquadId]) -WHERE ( +WHERE COALESCE(( SELECT TOP(1) [w].[IsAutomatic] FROM [Weapons] AS [w] WHERE [g].[FullName] = [w].[OwnerFullName] - ORDER BY [w].[Id]) = CAST(1 AS bit)"); + ORDER BY [w].[Id]), CAST(0 AS bit)) = CAST(1 AS bit)"); } public override async Task Where_subquery_boolean_with_pushdown(bool async) @@ -1706,14 +1698,14 @@ ELSE CAST(0 AS bit) END AS [IsOfficer] FROM [Gears] AS [g] LEFT JOIN [Officers] AS [o] ON ([g].[Nickname] = [o].[Nickname]) AND ([g].[SquadId] = [o].[SquadId]) -WHERE ([g].[HasSoulPatch] = CAST(1 AS bit)) AND (( +WHERE ([g].[HasSoulPatch] = CAST(1 AS bit)) AND (COALESCE(( SELECT TOP(1) [t].[IsAutomatic] FROM ( SELECT DISTINCT [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId] FROM [Weapons] AS [w] WHERE [g].[FullName] = [w].[OwnerFullName] ) AS [t] - ORDER BY [t].[Id]) = CAST(1 AS bit))"); + ORDER BY [t].[Id]), CAST(0 AS bit)) = CAST(1 AS bit))"); } public override async Task Where_subquery_distinct_firstordefault_boolean_with_pushdown(bool async) @@ -1770,13 +1762,13 @@ ELSE CAST(0 AS bit) END AS [IsOfficer] FROM [Gears] AS [g] LEFT JOIN [Officers] AS [o] ON ([g].[Nickname] = [o].[Nickname]) AND ([g].[SquadId] = [o].[SquadId]) -WHERE ([g].[HasSoulPatch] = CAST(1 AS bit)) AND (( +WHERE ([g].[HasSoulPatch] = CAST(1 AS bit)) AND (COALESCE(( SELECT TOP(1) [t].[IsAutomatic] FROM ( SELECT DISTINCT [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId] FROM [Weapons] AS [w] WHERE ([g].[FullName] = [w].[OwnerFullName]) AND ([w].[Name] LIKE N'%Lancer%') - ) AS [t]) = CAST(1 AS bit)) + ) AS [t]), CAST(0 AS bit)) = CAST(1 AS bit)) ORDER BY [g].[Nickname]"); } @@ -1791,10 +1783,10 @@ ELSE CAST(0 AS bit) END AS [IsOfficer] FROM [Gears] AS [g] LEFT JOIN [Officers] AS [o] ON ([g].[Nickname] = [o].[Nickname]) AND ([g].[SquadId] = [o].[SquadId]) -WHERE ([g].[HasSoulPatch] = CAST(1 AS bit)) AND (( +WHERE ([g].[HasSoulPatch] = CAST(1 AS bit)) AND (COALESCE(( SELECT DISTINCT TOP(1) [w].[IsAutomatic] FROM [Weapons] AS [w] - WHERE ([g].[FullName] = [w].[OwnerFullName]) AND ([w].[Name] LIKE N'%Lancer%')) = CAST(1 AS bit)) + WHERE ([g].[FullName] = [w].[OwnerFullName]) AND ([w].[Name] LIKE N'%Lancer%')), CAST(0 AS bit)) = CAST(1 AS bit)) ORDER BY [g].[Nickname]"); } @@ -1874,14 +1866,14 @@ ELSE CAST(0 AS bit) END AS [IsOfficer] FROM [Gears] AS [g] LEFT JOIN [Officers] AS [o] ON ([g].[Nickname] = [o].[Nickname]) AND ([g].[SquadId] = [o].[SquadId]) -WHERE ([g].[HasSoulPatch] = CAST(1 AS bit)) AND (( +WHERE ([g].[HasSoulPatch] = CAST(1 AS bit)) AND (COALESCE(( SELECT TOP(1) [t].[IsAutomatic] FROM ( SELECT DISTINCT [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId] FROM [Weapons] AS [w] WHERE [g].[FullName] = [w].[OwnerFullName] ) AS [t] - ORDER BY [t].[Id]) = CAST(1 AS bit))"); + ORDER BY [t].[Id]), CAST(0 AS bit)) = CAST(1 AS bit))"); } public override async Task Where_subquery_distinct_orderby_firstordefault_boolean_with_pushdown(bool async) @@ -6087,11 +6079,11 @@ public override async Task Project_one_value_type_from_empty_collection(bool asy await base.Project_one_value_type_from_empty_collection(async); AssertSql( - @"SELECT [s].[Name], ( + @"SELECT [s].[Name], COALESCE(( SELECT TOP(1) [g].[SquadId] FROM [Gears] AS [g] LEFT JOIN [Officers] AS [o] ON ([g].[Nickname] = [o].[Nickname]) AND ([g].[SquadId] = [o].[SquadId]) - WHERE ([s].[Id] = [g].[SquadId]) AND ([g].[HasSoulPatch] = CAST(1 AS bit))) AS [SquadId] + WHERE ([s].[Id] = [g].[SquadId]) AND ([g].[HasSoulPatch] = CAST(1 AS bit))), 0) AS [SquadId] FROM [Squads] AS [s] WHERE [s].[Name] = N'Kilo'"); } @@ -6149,11 +6141,11 @@ public override async Task Select_subquery_projecting_single_constant_int(bool a await base.Select_subquery_projecting_single_constant_int(async); AssertSql( - @"SELECT [s].[Name], ( + @"SELECT [s].[Name], COALESCE(( SELECT TOP(1) 42 FROM [Gears] AS [g] LEFT JOIN [Officers] AS [o] ON ([g].[Nickname] = [o].[Nickname]) AND ([g].[SquadId] = [o].[SquadId]) - WHERE ([s].[Id] = [g].[SquadId]) AND ([g].[HasSoulPatch] = CAST(1 AS bit))) AS [Gear] + WHERE ([s].[Id] = [g].[SquadId]) AND ([g].[HasSoulPatch] = CAST(1 AS bit))), 0) AS [Gear] FROM [Squads] AS [s]"); } @@ -6175,11 +6167,11 @@ public override async Task Select_subquery_projecting_single_constant_bool(bool await base.Select_subquery_projecting_single_constant_bool(async); AssertSql( - @"SELECT [s].[Name], ( + @"SELECT [s].[Name], COALESCE(( SELECT TOP(1) CAST(1 AS bit) FROM [Gears] AS [g] LEFT JOIN [Officers] AS [o] ON ([g].[Nickname] = [o].[Nickname]) AND ([g].[SquadId] = [o].[SquadId]) - WHERE ([s].[Id] = [g].[SquadId]) AND ([g].[HasSoulPatch] = CAST(1 AS bit))) AS [Gear] + WHERE ([s].[Id] = [g].[SquadId]) AND ([g].[HasSoulPatch] = CAST(1 AS bit))), CAST(0 AS bit)) AS [Gear] FROM [Squads] AS [s]"); } @@ -6353,11 +6345,11 @@ FROM [Gears] AS [g0] LEFT JOIN [Officers] AS [o0] ON ([g0].[Nickname] = [o0].[Nickname]) AND ([g0].[SquadId] = [o0].[SquadId]) ) AS [t] ON ([g].[Nickname] = [t].[LeaderNickname]) AND ([g].[SquadId] = [t].[LeaderSquadId]) WHERE [o].[Nickname] IS NOT NULL -ORDER BY ( +ORDER BY COALESCE(( SELECT TOP(1) [w].[IsAutomatic] FROM [Weapons] AS [w] WHERE [g].[FullName] = [w].[OwnerFullName] - ORDER BY [w].[Id]), [g].[Nickname], [g].[SquadId], [t].[Nickname], [t].[SquadId]"); + ORDER BY [w].[Id]), CAST(0 AS bit)), [g].[Nickname], [g].[SquadId], [t].[Nickname], [t].[SquadId]"); } public override async Task Correlated_collection_with_complex_OrderBy(bool async) @@ -6405,11 +6397,11 @@ WHERE [o].[Nickname] IS NOT NULL ORDER BY ( SELECT COUNT(*) FROM [Weapons] AS [w] - WHERE ([g].[FullName] = [w].[OwnerFullName]) AND ([w].[IsAutomatic] = ( + WHERE ([g].[FullName] = [w].[OwnerFullName]) AND ([w].[IsAutomatic] = COALESCE(( SELECT TOP(1) [g1].[HasSoulPatch] FROM [Gears] AS [g1] LEFT JOIN [Officers] AS [o1] ON ([g1].[Nickname] = [o1].[Nickname]) AND ([g1].[SquadId] = [o1].[SquadId]) - WHERE [g1].[Nickname] = N'Marcus'))), [g].[Nickname], [g].[SquadId], [t].[Nickname], [t].[SquadId]"); + WHERE [g1].[Nickname] = N'Marcus'), CAST(0 AS bit)))), [g].[Nickname], [g].[SquadId], [t].[Nickname], [t].[SquadId]"); } public override async Task Cast_to_derived_type_after_OfType_works(bool async) @@ -6428,11 +6420,11 @@ public override async Task Select_subquery_boolean(bool async) await base.Select_subquery_boolean(async); AssertSql( - @"SELECT ( + @"SELECT COALESCE(( SELECT TOP(1) [w].[IsAutomatic] FROM [Weapons] AS [w] WHERE [g].[FullName] = [w].[OwnerFullName] - ORDER BY [w].[Id]) + ORDER BY [w].[Id]), CAST(0 AS bit)) FROM [Gears] AS [g] LEFT JOIN [Officers] AS [o] ON ([g].[Nickname] = [o].[Nickname]) AND ([g].[SquadId] = [o].[SquadId])"); } @@ -6470,11 +6462,11 @@ public override async Task Select_subquery_int_with_outside_cast_and_coalesce(bo await base.Select_subquery_int_with_outside_cast_and_coalesce(async); AssertSql( - @"SELECT COALESCE(( + @"SELECT COALESCE(COALESCE(( SELECT TOP(1) [w].[Id] FROM [Weapons] AS [w] WHERE [g].[FullName] = [w].[OwnerFullName] - ORDER BY [w].[Id]), 42) + ORDER BY [w].[Id]), 0), 42) FROM [Gears] AS [g] LEFT JOIN [Officers] AS [o] ON ([g].[Nickname] = [o].[Nickname]) AND ([g].[SquadId] = [o].[SquadId])"); } @@ -6516,11 +6508,11 @@ public override async Task Select_subquery_boolean_empty(bool async) await base.Select_subquery_boolean_empty(async); AssertSql( - @"SELECT ( + @"SELECT COALESCE(( SELECT TOP(1) [w].[IsAutomatic] FROM [Weapons] AS [w] WHERE ([g].[FullName] = [w].[OwnerFullName]) AND ([w].[Name] = N'BFG') - ORDER BY [w].[Id]) + ORDER BY [w].[Id]), CAST(0 AS bit)) FROM [Gears] AS [g] LEFT JOIN [Officers] AS [o] ON ([g].[Nickname] = [o].[Nickname]) AND ([g].[SquadId] = [o].[SquadId])"); } @@ -6539,46 +6531,18 @@ FROM [Gears] AS [g] LEFT JOIN [Officers] AS [o] ON ([g].[Nickname] = [o].[Nickname]) AND ([g].[SquadId] = [o].[SquadId])"); } - public override async Task Select_subquery_boolean_empty_with_pushdown_without_convert_to_nullable1(bool async) - { - await base.Select_subquery_boolean_empty_with_pushdown_without_convert_to_nullable1(async); - - AssertSql( - @"SELECT ( - SELECT TOP(1) [w].[IsAutomatic] - FROM [Weapons] AS [w] - WHERE ([g].[FullName] = [w].[OwnerFullName]) AND ([w].[Name] = N'BFG') - ORDER BY [w].[Id]) -FROM [Gears] AS [g] -LEFT JOIN [Officers] AS [o] ON ([g].[Nickname] = [o].[Nickname]) AND ([g].[SquadId] = [o].[SquadId])"); - } - - public override async Task Select_subquery_boolean_empty_with_pushdown_without_convert_to_nullable2(bool async) - { - await base.Select_subquery_boolean_empty_with_pushdown_without_convert_to_nullable2(async); - - AssertSql( - @"SELECT ( - SELECT TOP(1) [w].[Id] - FROM [Weapons] AS [w] - WHERE ([g].[FullName] = [w].[OwnerFullName]) AND ([w].[Name] = N'BFG') - ORDER BY [w].[Id]) -FROM [Gears] AS [g] -LEFT JOIN [Officers] AS [o] ON ([g].[Nickname] = [o].[Nickname]) AND ([g].[SquadId] = [o].[SquadId])"); - } - public override async Task Select_subquery_distinct_singleordefault_boolean1(bool async) { await base.Select_subquery_distinct_singleordefault_boolean1(async); AssertSql( - @"SELECT ( + @"SELECT COALESCE(( SELECT TOP(1) [t].[IsAutomatic] FROM ( SELECT DISTINCT [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId] FROM [Weapons] AS [w] WHERE ([g].[FullName] = [w].[OwnerFullName]) AND ([w].[Name] LIKE N'%Lancer%') - ) AS [t]) + ) AS [t]), CAST(0 AS bit)) FROM [Gears] AS [g] LEFT JOIN [Officers] AS [o] ON ([g].[Nickname] = [o].[Nickname]) AND ([g].[SquadId] = [o].[SquadId]) WHERE [g].[HasSoulPatch] = CAST(1 AS bit)"); @@ -6589,10 +6553,10 @@ public override async Task Select_subquery_distinct_singleordefault_boolean2(boo await base.Select_subquery_distinct_singleordefault_boolean2(async); AssertSql( - @"SELECT ( + @"SELECT COALESCE(( SELECT DISTINCT TOP(1) [w].[IsAutomatic] FROM [Weapons] AS [w] - WHERE ([g].[FullName] = [w].[OwnerFullName]) AND ([w].[Name] LIKE N'%Lancer%')) + WHERE ([g].[FullName] = [w].[OwnerFullName]) AND ([w].[Name] LIKE N'%Lancer%')), CAST(0 AS bit)) FROM [Gears] AS [g] LEFT JOIN [Officers] AS [o] ON ([g].[Nickname] = [o].[Nickname]) AND ([g].[SquadId] = [o].[SquadId]) WHERE [g].[HasSoulPatch] = CAST(1 AS bit)"); @@ -6620,13 +6584,13 @@ public override async Task Select_subquery_distinct_singleordefault_boolean_empt await base.Select_subquery_distinct_singleordefault_boolean_empty1(async); AssertSql( - @"SELECT ( + @"SELECT COALESCE(( SELECT TOP(1) [t].[IsAutomatic] FROM ( SELECT DISTINCT [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId] FROM [Weapons] AS [w] WHERE ([g].[FullName] = [w].[OwnerFullName]) AND ([w].[Name] = N'BFG') - ) AS [t]) + ) AS [t]), CAST(0 AS bit)) FROM [Gears] AS [g] LEFT JOIN [Officers] AS [o] ON ([g].[Nickname] = [o].[Nickname]) AND ([g].[SquadId] = [o].[SquadId]) WHERE [g].[HasSoulPatch] = CAST(1 AS bit)"); @@ -6637,10 +6601,10 @@ public override async Task Select_subquery_distinct_singleordefault_boolean_empt await base.Select_subquery_distinct_singleordefault_boolean_empty2(async); AssertSql( - @"SELECT ( + @"SELECT COALESCE(( SELECT DISTINCT TOP(1) [w].[IsAutomatic] FROM [Weapons] AS [w] - WHERE ([g].[FullName] = [w].[OwnerFullName]) AND ([w].[Name] = N'BFG')) + WHERE ([g].[FullName] = [w].[OwnerFullName]) AND ([w].[Name] = N'BFG')), CAST(0 AS bit)) FROM [Gears] AS [g] LEFT JOIN [Officers] AS [o] ON ([g].[Nickname] = [o].[Nickname]) AND ([g].[SquadId] = [o].[SquadId]) WHERE [g].[HasSoulPatch] = CAST(1 AS bit)"); diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/SpatialQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/SpatialQuerySqliteTest.cs index 769e2d04487..88400ee7310 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/SpatialQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/SpatialQuerySqliteTest.cs @@ -256,19 +256,6 @@ public override async Task Disjoint_with_cast_to_nullable(bool async) AssertSql( @"@__point_0='0x000100000000000000000000F03F000000000000F03F000000000000F03F0000...' (Size = 60) (DbType = String) -SELECT ""p"".""Id"", CASE - WHEN ""p"".""Polygon"" IS NOT NULL THEN Disjoint(""p"".""Polygon"", @__point_0) -END AS ""Disjoint"" -FROM ""PolygonEntity"" AS ""p"""); - } - - public override async Task Disjoint_without_cast_to_nullable(bool async) - { - await base.Disjoint_without_cast_to_nullable(async); - - AssertSql( - @"@__point_0='0x000100000000000000000000F03F000000000000F03F000000000000F03F0000...' (Size = 60) (DbType = String) - SELECT ""p"".""Id"", CASE WHEN ""p"".""Polygon"" IS NOT NULL THEN Disjoint(""p"".""Polygon"", @__point_0) END AS ""Disjoint"" @@ -289,17 +276,6 @@ END AS ""Disjoint"" FROM ""PolygonEntity"" AS ""p"""); } - public override async Task Distance_without_null_check(bool async) - { - await base.Distance_without_null_check(async); - - AssertSql( - @"@__point_0='0x0001000000000000000000000000000000000000F03F00000000000000000000...' (Size = 60) (DbType = String) - -SELECT ""p"".""Id"", Distance(""p"".""Point"", @__point_0) AS ""Distance"" -FROM ""PointEntity"" AS ""p"""); - } - public override async Task Distance_with_null_check(bool async) { await base.Distance_with_null_check(async);