diff --git a/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs b/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs index 69654029563..62348e5cffa 100644 --- a/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs +++ b/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs @@ -578,7 +578,94 @@ protected virtual SqlExpression VisitCase(CaseExpression caseExpression, bool al elseResult = null; } + // optimize expressions such as expr != null ? expr : null and expr == null ? null : expr + if (testIsCondition && whenClauses is [var clause] && (elseResult == null || IsNull(clause.Result))) + { + HashSet nullPropagatedOperands = []; + SqlExpression test, expr; + + if (elseResult == null) + { + expr = clause.Result; + test = clause.Test; + } + else + { + expr = elseResult; + test = _sqlExpressionFactory.Not(clause.Test); + } + + NullPropagatedOperands(expr, nullPropagatedOperands); + test = DropNotNullChecks(test, nullPropagatedOperands); + + if (IsTrue(test)) + { + return expr; + } + + if (elseResult != null) + { + test = _sqlExpressionFactory.Not(test); + } + + whenClauses = [new(test, clause.Result)]; + } + return caseExpression.Update(operand, whenClauses, elseResult); + + SqlExpression DropNotNullChecks(SqlExpression expression, HashSet nullPropagatedOperands) + => expression switch + { + SqlUnaryExpression { OperatorType: ExpressionType.NotEqual } isNotNull + when nullPropagatedOperands.Contains(isNotNull.Operand) + => _sqlExpressionFactory.Constant(true, expression.Type, expression.TypeMapping), + + SqlBinaryExpression { OperatorType: ExpressionType.AndAlso } binary + => _sqlExpressionFactory.MakeBinary( + ExpressionType.AndAlso, + DropNotNullChecks(binary.Left, nullPropagatedOperands), + DropNotNullChecks(binary.Right, nullPropagatedOperands), + expression.TypeMapping, + expression)!, + + _ => expression, + }; + + // FIXME: unify nullability computations + static void NullPropagatedOperands(SqlExpression expression, HashSet operands) + { + operands.Add(expression); + + if (expression is SqlUnaryExpression unary + && unary.OperatorType is ExpressionType.Not or ExpressionType.Negate or ExpressionType.Convert) + { + NullPropagatedOperands(unary.Operand, operands); + } + else if (expression is SqlBinaryExpression binary + && binary.OperatorType is not (ExpressionType.AndAlso or ExpressionType.OrElse)) + { + NullPropagatedOperands(binary.Left, operands); + NullPropagatedOperands(binary.Right, operands); + } + else if (expression is SqlFunctionExpression { IsNullable: true } func) + { + if (func.InstancePropagatesNullability == true) + { + NullPropagatedOperands(func.Instance!, operands); + } + + if (!func.IsNiladic) + { + for (var i = 0; i < func.ArgumentsPropagateNullability.Count; i++) + { + if (func.ArgumentsPropagateNullability[i]) + { + NullPropagatedOperands(func.Arguments[i], operands); + } + } + } + } + } } ///