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
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