diff --git a/src/EFCore.Relational/Query/ISqlExpressionFactory.cs b/src/EFCore.Relational/Query/ISqlExpressionFactory.cs
index ccfc99a3c8e..8e0cf8114bf 100644
--- a/src/EFCore.Relational/Query/ISqlExpressionFactory.cs
+++ b/src/EFCore.Relational/Query/ISqlExpressionFactory.cs
@@ -37,28 +37,28 @@ public interface ISqlExpressionFactory
SqlExpression? ApplyDefaultTypeMapping(SqlExpression? sqlExpression);
///
- /// Creates a new with the given arguments.
+ /// Creates a new with the given arguments.
///
/// An representing SQL unary operator.
/// A to apply unary operator on.
/// The type of the created expression.
/// A type mapping to be assigned to the created expression.
- /// A with the given arguments.
- SqlUnaryExpression? MakeUnary(
+ /// A with the given arguments.
+ SqlExpression? MakeUnary(
ExpressionType operatorType,
SqlExpression operand,
Type type,
RelationalTypeMapping? typeMapping = null);
///
- /// Creates a new with the given arguments.
+ /// Creates a new with the given arguments.
///
/// An representing SQL unary operator.
/// The left operand of binary operation.
/// The right operand of binary operation.
/// A type mapping to be assigned to the created expression.
- /// A with the given arguments.
- SqlBinaryExpression? MakeBinary(
+ /// A with the given arguments.
+ SqlExpression? MakeBinary(
ExpressionType operatorType,
SqlExpression left,
SqlExpression right,
@@ -66,208 +66,208 @@ public interface ISqlExpressionFactory
// Comparison
///
- /// Creates a which represents an equality comparison.
+ /// Creates a which represents an equality comparison.
///
/// The left operand.
/// The right operand.
/// An expression representing a SQL equality comparison.
- SqlBinaryExpression Equal(SqlExpression left, SqlExpression right);
+ SqlExpression Equal(SqlExpression left, SqlExpression right);
///
- /// Creates a which represents an inequality comparison.
+ /// Creates a which represents an inequality comparison.
///
/// The left operand.
/// The right operand.
/// An expression representing a SQL inequality comparison.
- SqlBinaryExpression NotEqual(SqlExpression left, SqlExpression right);
+ SqlExpression NotEqual(SqlExpression left, SqlExpression right);
///
- /// Creates a which represents a greater than comparison.
+ /// Creates a which represents a greater than comparison.
///
/// The left operand.
/// The right operand.
/// An expression representing a SQL greater than comparison.
- SqlBinaryExpression GreaterThan(SqlExpression left, SqlExpression right);
+ SqlExpression GreaterThan(SqlExpression left, SqlExpression right);
///
- /// Creates a which represents a greater than or equal comparison.
+ /// Creates a which represents a greater than or equal comparison.
///
/// The left operand.
/// The right operand.
/// An expression representing a SQL greater than or equal comparison.
- SqlBinaryExpression GreaterThanOrEqual(SqlExpression left, SqlExpression right);
+ SqlExpression GreaterThanOrEqual(SqlExpression left, SqlExpression right);
///
- /// Creates a which represents a less than comparison.
+ /// Creates a which represents a less than comparison.
///
/// The left operand.
/// The right operand.
/// An expression representing a SQL less than comparison.
- SqlBinaryExpression LessThan(SqlExpression left, SqlExpression right);
+ SqlExpression LessThan(SqlExpression left, SqlExpression right);
///
- /// Creates a which represents a less than or equal comparison.
+ /// Creates a which represents a less than or equal comparison.
///
/// The left operand.
/// The right operand.
/// An expression representing a SQL less than or equal comparison.
- SqlBinaryExpression LessThanOrEqual(SqlExpression left, SqlExpression right);
+ SqlExpression LessThanOrEqual(SqlExpression left, SqlExpression right);
// Logical
///
- /// Creates a which represents a logical AND operation.
+ /// Creates a which represents a logical AND operation.
///
/// The left operand.
/// The right operand.
/// An expression representing a SQL AND operation.
- SqlBinaryExpression AndAlso(SqlExpression left, SqlExpression right);
+ SqlExpression AndAlso(SqlExpression left, SqlExpression right);
///
- /// Creates a which represents a logical OR operation.
+ /// Creates a which represents a logical OR operation.
///
/// The left operand.
/// The right operand.
/// An expression representing a SQL OR operation.
- SqlBinaryExpression OrElse(SqlExpression left, SqlExpression right);
+ SqlExpression OrElse(SqlExpression left, SqlExpression right);
// Arithmetic
///
- /// Creates a which represents an addition.
+ /// Creates a which represents an addition.
///
/// The left operand.
/// The right operand.
/// A type mapping to be assigned to the created expression.
/// An expression representing a SQL addition.
- SqlBinaryExpression Add(
+ SqlExpression Add(
SqlExpression left,
SqlExpression right,
RelationalTypeMapping? typeMapping = null);
///
- /// Creates a which represents a subtraction.
+ /// Creates a which represents a subtraction.
///
/// The left operand.
/// The right operand.
/// A type mapping to be assigned to the created expression.
/// An expression representing a SQL subtraction.
- SqlBinaryExpression Subtract(
+ SqlExpression Subtract(
SqlExpression left,
SqlExpression right,
RelationalTypeMapping? typeMapping = null);
///
- /// Creates a which represents a multiplication.
+ /// Creates a which represents a multiplication.
///
/// The left operand.
/// The right operand.
/// A type mapping to be assigned to the created expression.
/// An expression representing a SQL multiplication.
- SqlBinaryExpression Multiply(
+ SqlExpression Multiply(
SqlExpression left,
SqlExpression right,
RelationalTypeMapping? typeMapping = null);
///
- /// Creates a which represents a division.
+ /// Creates a which represents a division.
///
/// The left operand.
/// The right operand.
/// A type mapping to be assigned to the created expression.
/// An expression representing a SQL division.
- SqlBinaryExpression Divide(
+ SqlExpression Divide(
SqlExpression left,
SqlExpression right,
RelationalTypeMapping? typeMapping = null);
///
- /// Creates a which represents a modulo operation.
+ /// Creates a which represents a modulo operation.
///
/// The left operand.
/// The right operand.
/// A type mapping to be assigned to the created expression.
/// An expression representing a SQL modulo operation.
- SqlBinaryExpression Modulo(
+ SqlExpression Modulo(
SqlExpression left,
SqlExpression right,
RelationalTypeMapping? typeMapping = null);
// Bitwise
///
- /// Creates a which represents a bitwise AND operation.
+ /// Creates a which represents a bitwise AND operation.
///
/// The left operand.
/// The right operand.
/// A type mapping to be assigned to the created expression.
/// An expression representing a SQL bitwise AND operation.
- SqlBinaryExpression And(
+ SqlExpression And(
SqlExpression left,
SqlExpression right,
RelationalTypeMapping? typeMapping = null);
///
- /// Creates a which represents a bitwise OR operation.
+ /// Creates a which represents a bitwise OR operation.
///
/// The left operand.
/// The right operand.
/// A type mapping to be assigned to the created expression.
/// An expression representing a SQL bitwise OR operation.
- SqlBinaryExpression Or(
+ SqlExpression Or(
SqlExpression left,
SqlExpression right,
RelationalTypeMapping? typeMapping = null);
// Other
///
- /// Creates a which represents a COALESCE operation.
+ /// Creates a which represents a COALESCE operation.
///
/// The left operand.
/// The right operand.
/// A type mapping to be assigned to the created expression.
/// An expression representing a SQL COALESCE operation.
- SqlFunctionExpression Coalesce(
+ SqlExpression Coalesce(
SqlExpression left,
SqlExpression right,
RelationalTypeMapping? typeMapping = null);
///
- /// Creates a new which represent equality to null.
+ /// Creates a new which represent equality to null.
///
/// A to compare to null.
/// An expression representing IS NULL construct in a SQL tree.
- SqlUnaryExpression IsNull(SqlExpression operand);
+ SqlExpression IsNull(SqlExpression operand);
///
- /// Creates a new which represent inequality to null.
+ /// Creates a new which represent inequality to null.
///
/// A to compare to non null.
/// An expression representing IS NOT NULL construct in a SQL tree.
- SqlUnaryExpression IsNotNull(SqlExpression operand);
+ SqlExpression IsNotNull(SqlExpression operand);
///
- /// Creates a new which represent casting a SQL expression to different type.
+ /// Creates a new which represent casting a SQL expression to different type.
///
/// A to cast.
/// The return type of the expression after cast.
/// A relational type mapping to use for conversion.
/// An expression representing cast operation in a SQL tree.
- SqlUnaryExpression Convert(
+ SqlExpression Convert(
SqlExpression operand,
Type type,
RelationalTypeMapping? typeMapping = null);
///
- /// Creates a new which represent a NOT operation in a SQL tree.
+ /// Creates a new which represent a NOT operation in a SQL tree.
///
/// A to apply NOT on.
/// An expression representing a NOT operation in a SQL tree.
- SqlUnaryExpression Not(SqlExpression operand);
+ SqlExpression Not(SqlExpression operand);
///
- /// Creates a new which represent a negation operation in a SQL tree.
+ /// Creates a new which represent a negation operation in a SQL tree.
///
/// A to apply NOT on.
/// An expression representing a negation operation in a SQL tree.
- SqlUnaryExpression Negate(SqlExpression operand);
+ SqlExpression Negate(SqlExpression operand);
///
/// Creates a new which represent a CASE statement in a SQL tree.
@@ -276,7 +276,7 @@ SqlUnaryExpression Convert(
/// A list of to compare and get result from.
/// A value to return if no matches, if any.
/// An expression representing a CASE statement in a SQL tree.
- CaseExpression Case(
+ SqlExpression Case(
SqlExpression operand,
IReadOnlyList whenClauses,
SqlExpression? elseResult);
@@ -287,10 +287,10 @@ CaseExpression Case(
/// A list of to evaluate condition and get result from.
/// A value to return if no matches, if any.
/// An expression representing a CASE statement in a SQL tree.
- CaseExpression Case(IReadOnlyList whenClauses, SqlExpression? elseResult);
+ SqlExpression Case(IReadOnlyList whenClauses, SqlExpression? elseResult);
///
- /// Creates a new which represents a function call in a SQL tree.
+ /// Creates a new which represents a function call in a SQL tree.
///
/// The name of the function.
/// The arguments of the function.
@@ -299,7 +299,7 @@ CaseExpression Case(
/// The of the expression.
/// The associated with the expression.
/// An expression representing a function call in a SQL tree.
- SqlFunctionExpression Function(
+ SqlExpression Function(
string name,
IEnumerable arguments,
bool nullable,
@@ -308,7 +308,7 @@ SqlFunctionExpression Function(
RelationalTypeMapping? typeMapping = null);
///
- /// Creates a new which represents a function call in a SQL tree.
+ /// Creates a new which represents a function call in a SQL tree.
///
/// The schema in which the function is defined.
/// The name of the function.
@@ -318,7 +318,7 @@ SqlFunctionExpression Function(
/// The of the expression.
/// The associated with the expression.
/// An expression representing a function call in a SQL tree.
- SqlFunctionExpression Function(
+ SqlExpression Function(
string? schema,
string name,
IEnumerable arguments,
@@ -328,7 +328,7 @@ SqlFunctionExpression Function(
RelationalTypeMapping? typeMapping = null);
///
- /// Creates a new which represents a function call in a SQL tree.
+ /// Creates a new which represents a function call in a SQL tree.
///
/// An expression on which the function is applied.
/// The name of the function.
@@ -339,7 +339,7 @@ SqlFunctionExpression Function(
/// The of the expression.
/// The associated with the expression.
/// An expression representing a function call in a SQL tree.
- SqlFunctionExpression Function(
+ SqlExpression Function(
SqlExpression instance,
string name,
IEnumerable arguments,
@@ -350,21 +350,21 @@ SqlFunctionExpression Function(
RelationalTypeMapping? typeMapping = null);
///
- /// Creates a new which represents a niladic function call in a SQL tree.
+ /// Creates a new which represents a niladic function call in a SQL tree.
///
/// The name of the function.
/// A bool value indicating whether this function can return null.
/// The of the expression.
/// The associated with the expression.
/// An expression representing a function call in a SQL tree.
- SqlFunctionExpression NiladicFunction(
+ SqlExpression NiladicFunction(
string name,
bool nullable,
Type returnType,
RelationalTypeMapping? typeMapping = null);
///
- /// Creates a new which represents a niladic function call in a SQL tree.
+ /// Creates a new which represents a niladic function call in a SQL tree.
///
/// The schema in which the function is defined.
/// The name of the function.
@@ -372,7 +372,7 @@ SqlFunctionExpression NiladicFunction(
/// The of the expression.
/// The associated with the expression.
/// An expression representing a function call in a SQL tree.
- SqlFunctionExpression NiladicFunction(
+ SqlExpression NiladicFunction(
string schema,
string name,
bool nullable,
@@ -380,7 +380,7 @@ SqlFunctionExpression NiladicFunction(
RelationalTypeMapping? typeMapping = null);
///
- /// Creates a new which represents a niladic function call in a SQL tree.
+ /// Creates a new which represents a niladic function call in a SQL tree.
///
/// An expression on which the function is applied.
/// The name of the function.
@@ -389,7 +389,7 @@ SqlFunctionExpression NiladicFunction(
/// The of the expression.
/// The associated with the expression.
/// An expression representing a function call in a SQL tree.
- SqlFunctionExpression NiladicFunction(
+ SqlExpression NiladicFunction(
SqlExpression instance,
string name,
bool nullable,
@@ -402,7 +402,7 @@ SqlFunctionExpression NiladicFunction(
///
/// A subquery to check existence of.
/// An expression representing an EXISTS operation in a SQL tree.
- ExistsExpression Exists(SelectExpression subquery);
+ SqlExpression Exists(SelectExpression subquery);
///
/// Creates a new which represents an IN operation in a SQL tree.
@@ -410,7 +410,7 @@ SqlFunctionExpression NiladicFunction(
/// An item to look into values.
/// A subquery in which item is searched.
/// An expression representing an IN operation in a SQL tree.
- InExpression In(SqlExpression item, SelectExpression subquery);
+ SqlExpression In(SqlExpression item, SelectExpression subquery);
///
/// Creates a new which represents an IN operation in a SQL tree.
@@ -418,7 +418,7 @@ SqlFunctionExpression NiladicFunction(
/// An item to look into values.
/// A list of values in which item is searched.
/// An expression representing an IN operation in a SQL tree.
- InExpression In(SqlExpression item, IReadOnlyList values);
+ SqlExpression In(SqlExpression item, IReadOnlyList values);
///
/// Creates a new which represents an IN operation in a SQL tree.
@@ -426,7 +426,7 @@ SqlFunctionExpression NiladicFunction(
/// An item to look into values.
/// A parameterized list of values in which the item is searched.
/// An expression representing an IN operation in a SQL tree.
- InExpression In(SqlExpression item, SqlParameterExpression valuesParameter);
+ SqlExpression In(SqlExpression item, SqlParameterExpression valuesParameter);
///
/// Creates a new which represents a LIKE in a SQL tree.
@@ -435,31 +435,31 @@ SqlFunctionExpression NiladicFunction(
/// A pattern to search.
/// An optional escape character to use in LIKE.
/// An expression representing a LIKE in a SQL tree.
- LikeExpression Like(SqlExpression match, SqlExpression pattern, SqlExpression? escapeChar = null);
+ SqlExpression Like(SqlExpression match, SqlExpression pattern, SqlExpression? escapeChar = null);
///
- /// Creates a new which represents a constant in a SQL tree.
+ /// Creates a new which represents a constant in a SQL tree.
///
/// A value.
/// The associated with the expression.
/// An expression representing a constant in a SQL tree.
- SqlConstantExpression Constant(object value, RelationalTypeMapping? typeMapping = null);
+ SqlExpression Constant(object value, RelationalTypeMapping? typeMapping = null);
///
- /// Creates a new which represents a constant in a SQL tree.
+ /// Creates a new which represents a constant in a SQL tree.
///
/// A value.
/// The type for the constant. Useful when value is null.
/// The associated with the expression.
/// An expression representing a constant in a SQL tree.
- SqlConstantExpression Constant(object? value, Type type, RelationalTypeMapping? typeMapping = null);
+ SqlExpression Constant(object? value, Type type, RelationalTypeMapping? typeMapping = null);
///
- /// Creates a new which represents a SQL token.
+ /// Creates a new which represents a SQL token.
///
/// A string token to print in SQL tree.
/// An expression representing a SQL token.
- SqlFragmentExpression Fragment(string sql);
+ SqlExpression Fragment(string sql);
///
/// Attempts to creates a new expression that returns the smallest value from a list of expressions, e.g. an invocation of the
diff --git a/src/EFCore.Relational/Query/SqlExpressionFactory.cs b/src/EFCore.Relational/Query/SqlExpressionFactory.cs
index 0a7ae6228eb..eff6de9605c 100644
--- a/src/EFCore.Relational/Query/SqlExpressionFactory.cs
+++ b/src/EFCore.Relational/Query/SqlExpressionFactory.cs
@@ -390,7 +390,7 @@ private SqlExpression ApplyTypeMappingOnJsonScalar(
}
///
- public virtual SqlBinaryExpression? MakeBinary(
+ public virtual SqlExpression? MakeBinary(
ExpressionType operatorType,
SqlExpression left,
SqlExpression right,
@@ -416,125 +416,131 @@ private SqlExpression ApplyTypeMappingOnJsonScalar(
break;
}
- return (SqlBinaryExpression)ApplyTypeMapping(
+ return ApplyTypeMapping(
new SqlBinaryExpression(operatorType, left, right, returnType, null), typeMapping);
}
///
- public virtual SqlBinaryExpression Equal(SqlExpression left, SqlExpression right)
+ public virtual SqlExpression Equal(SqlExpression left, SqlExpression right)
=> MakeBinary(ExpressionType.Equal, left, right, null)!;
///
- public virtual SqlBinaryExpression NotEqual(SqlExpression left, SqlExpression right)
+ public virtual SqlExpression NotEqual(SqlExpression left, SqlExpression right)
=> MakeBinary(ExpressionType.NotEqual, left, right, null)!;
///
- public virtual SqlBinaryExpression GreaterThan(SqlExpression left, SqlExpression right)
+ public virtual SqlExpression GreaterThan(SqlExpression left, SqlExpression right)
=> MakeBinary(ExpressionType.GreaterThan, left, right, null)!;
///
- public virtual SqlBinaryExpression GreaterThanOrEqual(SqlExpression left, SqlExpression right)
+ public virtual SqlExpression GreaterThanOrEqual(SqlExpression left, SqlExpression right)
=> MakeBinary(ExpressionType.GreaterThanOrEqual, left, right, null)!;
///
- public virtual SqlBinaryExpression LessThan(SqlExpression left, SqlExpression right)
+ public virtual SqlExpression LessThan(SqlExpression left, SqlExpression right)
=> MakeBinary(ExpressionType.LessThan, left, right, null)!;
///
- public virtual SqlBinaryExpression LessThanOrEqual(SqlExpression left, SqlExpression right)
+ public virtual SqlExpression LessThanOrEqual(SqlExpression left, SqlExpression right)
=> MakeBinary(ExpressionType.LessThanOrEqual, left, right, null)!;
///
- public virtual SqlBinaryExpression AndAlso(SqlExpression left, SqlExpression right)
+ public virtual SqlExpression AndAlso(SqlExpression left, SqlExpression right)
=> MakeBinary(ExpressionType.AndAlso, left, right, null)!;
///
- public virtual SqlBinaryExpression OrElse(SqlExpression left, SqlExpression right)
+ public virtual SqlExpression OrElse(SqlExpression left, SqlExpression right)
=> MakeBinary(ExpressionType.OrElse, left, right, null)!;
///
- public virtual SqlBinaryExpression Add(SqlExpression left, SqlExpression right, RelationalTypeMapping? typeMapping = null)
+ public virtual SqlExpression Add(SqlExpression left, SqlExpression right, RelationalTypeMapping? typeMapping = null)
=> MakeBinary(ExpressionType.Add, left, right, typeMapping)!;
///
- public virtual SqlBinaryExpression Subtract(SqlExpression left, SqlExpression right, RelationalTypeMapping? typeMapping = null)
+ public virtual SqlExpression Subtract(SqlExpression left, SqlExpression right, RelationalTypeMapping? typeMapping = null)
=> MakeBinary(ExpressionType.Subtract, left, right, typeMapping)!;
///
- public virtual SqlBinaryExpression Multiply(SqlExpression left, SqlExpression right, RelationalTypeMapping? typeMapping = null)
+ public virtual SqlExpression Multiply(SqlExpression left, SqlExpression right, RelationalTypeMapping? typeMapping = null)
=> MakeBinary(ExpressionType.Multiply, left, right, typeMapping)!;
///
- public virtual SqlBinaryExpression Divide(SqlExpression left, SqlExpression right, RelationalTypeMapping? typeMapping = null)
+ public virtual SqlExpression Divide(SqlExpression left, SqlExpression right, RelationalTypeMapping? typeMapping = null)
=> MakeBinary(ExpressionType.Divide, left, right, typeMapping)!;
///
- public virtual SqlBinaryExpression Modulo(SqlExpression left, SqlExpression right, RelationalTypeMapping? typeMapping = null)
+ public virtual SqlExpression Modulo(SqlExpression left, SqlExpression right, RelationalTypeMapping? typeMapping = null)
=> MakeBinary(ExpressionType.Modulo, left, right, typeMapping)!;
///
- public virtual SqlBinaryExpression And(SqlExpression left, SqlExpression right, RelationalTypeMapping? typeMapping = null)
+ public virtual SqlExpression And(SqlExpression left, SqlExpression right, RelationalTypeMapping? typeMapping = null)
=> MakeBinary(ExpressionType.And, left, right, typeMapping)!;
///
- public virtual SqlBinaryExpression Or(SqlExpression left, SqlExpression right, RelationalTypeMapping? typeMapping = null)
+ public virtual SqlExpression Or(SqlExpression left, SqlExpression right, RelationalTypeMapping? typeMapping = null)
=> MakeBinary(ExpressionType.Or, left, right, typeMapping)!;
///
- public virtual SqlFunctionExpression Coalesce(SqlExpression left, SqlExpression right, RelationalTypeMapping? typeMapping = null)
+ public virtual SqlExpression Coalesce(SqlExpression left, SqlExpression right, RelationalTypeMapping? typeMapping = null)
{
var resultType = right.Type;
var inferredTypeMapping = typeMapping
?? ExpressionExtensions.InferTypeMapping(left, right)
?? _typeMappingSource.FindMapping(resultType, Dependencies.Model);
- var typeMappedArguments = new List
+ left = ApplyTypeMapping(left, inferredTypeMapping);
+ right = ApplyTypeMapping(right, inferredTypeMapping);
+
+ return left switch
{
- ApplyTypeMapping(left, inferredTypeMapping), ApplyTypeMapping(right, inferredTypeMapping)
+ SqlConstantExpression { Value: null } => right,
+
+ SqlConstantExpression { Value: not null } or
+ ColumnExpression { IsNullable: false } => left,
+
+ _ => new SqlFunctionExpression(
+ "COALESCE",
+ [left, right],
+ nullable: true,
+ // COALESCE is handled separately since it's only nullable if *all* arguments are null
+ argumentsPropagateNullability: [false, false],
+ resultType,
+ inferredTypeMapping)
};
-
- return new SqlFunctionExpression(
- "COALESCE",
- typeMappedArguments,
- nullable: true,
- // COALESCE is handled separately since it's only nullable if *all* arguments are null
- argumentsPropagateNullability: [false, false],
- resultType,
- inferredTypeMapping);
}
///
- public virtual SqlUnaryExpression? MakeUnary(
+ public virtual SqlExpression? MakeUnary(
ExpressionType operatorType,
SqlExpression operand,
Type type,
RelationalTypeMapping? typeMapping = null)
=> SqlUnaryExpression.IsValidOperator(operatorType)
- ? (SqlUnaryExpression)ApplyTypeMapping(new SqlUnaryExpression(operatorType, operand, type, null), typeMapping)
+ ? ApplyTypeMapping(new SqlUnaryExpression(operatorType, operand, type, null), typeMapping)
: null;
///
- public virtual SqlUnaryExpression IsNull(SqlExpression operand)
+ public virtual SqlExpression IsNull(SqlExpression operand)
=> MakeUnary(ExpressionType.Equal, operand, typeof(bool))!;
///
- public virtual SqlUnaryExpression IsNotNull(SqlExpression operand)
+ public virtual SqlExpression IsNotNull(SqlExpression operand)
=> MakeUnary(ExpressionType.NotEqual, operand, typeof(bool))!;
///
- public virtual SqlUnaryExpression Convert(SqlExpression operand, Type type, RelationalTypeMapping? typeMapping = null)
+ public virtual SqlExpression Convert(SqlExpression operand, Type type, RelationalTypeMapping? typeMapping = null)
=> MakeUnary(ExpressionType.Convert, operand, type.UnwrapNullableType(), typeMapping)!;
///
- public virtual SqlUnaryExpression Not(SqlExpression operand)
+ public virtual SqlExpression Not(SqlExpression operand)
=> MakeUnary(ExpressionType.Not, operand, operand.Type, operand.TypeMapping)!;
///
- public virtual SqlUnaryExpression Negate(SqlExpression operand)
+ public virtual SqlExpression Negate(SqlExpression operand)
=> MakeUnary(ExpressionType.Negate, operand, operand.Type, operand.TypeMapping)!;
///
- public virtual CaseExpression Case(SqlExpression? operand, IReadOnlyList whenClauses, SqlExpression? elseResult)
+ public virtual SqlExpression Case(SqlExpression? operand, IReadOnlyList whenClauses, SqlExpression? elseResult)
{
var operandTypeMapping = operand!.TypeMapping
?? whenClauses.Select(wc => wc.Test.TypeMapping).FirstOrDefault(t => t != null)
@@ -563,7 +569,7 @@ public virtual CaseExpression Case(SqlExpression? operand, IReadOnlyList
- public virtual CaseExpression Case(IReadOnlyList whenClauses, SqlExpression? elseResult)
+ public virtual SqlExpression Case(IReadOnlyList whenClauses, SqlExpression? elseResult)
{
var resultTypeMapping = elseResult?.TypeMapping
?? whenClauses.Select(wc => wc.Result.TypeMapping).FirstOrDefault(t => t != null);
@@ -583,7 +589,7 @@ public virtual CaseExpression Case(IReadOnlyList whenClauses, Sq
}
///
- public virtual SqlFunctionExpression Function(
+ public virtual SqlExpression Function(
string name,
IEnumerable arguments,
bool nullable,
@@ -602,7 +608,7 @@ public virtual SqlFunctionExpression Function(
}
///
- public virtual SqlFunctionExpression Function(
+ public virtual SqlExpression Function(
string? schema,
string name,
IEnumerable arguments,
@@ -622,7 +628,7 @@ public virtual SqlFunctionExpression Function(
}
///
- public virtual SqlFunctionExpression Function(
+ public virtual SqlExpression Function(
SqlExpression instance,
string name,
IEnumerable arguments,
@@ -645,64 +651,64 @@ public virtual SqlFunctionExpression Function(
}
///
- public virtual SqlFunctionExpression NiladicFunction(
+ public virtual SqlExpression NiladicFunction(
string name,
bool nullable,
Type returnType,
RelationalTypeMapping? typeMapping = null)
- => new(name, nullable, returnType, typeMapping);
+ => new SqlFunctionExpression(name, nullable, returnType, typeMapping);
///
- public virtual SqlFunctionExpression NiladicFunction(
+ public virtual SqlExpression NiladicFunction(
string schema,
string name,
bool nullable,
Type returnType,
RelationalTypeMapping? typeMapping = null)
- => new(schema, name, nullable, returnType, typeMapping);
+ => new SqlFunctionExpression(schema, name, nullable, returnType, typeMapping);
///
- public virtual SqlFunctionExpression NiladicFunction(
+ public virtual SqlExpression NiladicFunction(
SqlExpression instance,
string name,
bool nullable,
bool instancePropagatesNullability,
Type returnType,
RelationalTypeMapping? typeMapping = null)
- => new(
+ => new SqlFunctionExpression(
ApplyDefaultTypeMapping(instance), name, nullable, instancePropagatesNullability, returnType, typeMapping);
///
- public virtual ExistsExpression Exists(SelectExpression subquery)
- => new(subquery, _boolTypeMapping);
+ public virtual SqlExpression Exists(SelectExpression subquery)
+ => new ExistsExpression(subquery, _boolTypeMapping);
///
- public virtual InExpression In(SqlExpression item, SelectExpression subquery)
+ public virtual SqlExpression In(SqlExpression item, SelectExpression subquery)
=> ApplyTypeMappingOnIn(new InExpression(item, subquery, _boolTypeMapping));
///
- public virtual InExpression In(SqlExpression item, IReadOnlyList values)
+ public virtual SqlExpression In(SqlExpression item, IReadOnlyList values)
=> ApplyTypeMappingOnIn(new InExpression(item, values, _boolTypeMapping));
///
- public virtual InExpression In(SqlExpression item, SqlParameterExpression valuesParameter)
+ public virtual SqlExpression In(SqlExpression item, SqlParameterExpression valuesParameter)
=> ApplyTypeMappingOnIn(new InExpression(item, valuesParameter, _boolTypeMapping));
///
- public virtual LikeExpression Like(SqlExpression match, SqlExpression pattern, SqlExpression? escapeChar = null)
- => (LikeExpression)ApplyDefaultTypeMapping(new LikeExpression(match, pattern, escapeChar, null));
+ public virtual SqlExpression Like(SqlExpression match, SqlExpression pattern, SqlExpression? escapeChar = null)
+ => ApplyDefaultTypeMapping(new LikeExpression(match, pattern, escapeChar, null));
///
- public virtual SqlFragmentExpression Fragment(string sql)
- => new(sql);
+ public virtual SqlExpression Fragment(string sql)
+ => new SqlFragmentExpression(sql);
///
- public virtual SqlConstantExpression Constant(object value, RelationalTypeMapping? typeMapping = null)
- => new(value, typeMapping);
+ public virtual SqlExpression Constant(object value, RelationalTypeMapping? typeMapping = null)
+ => new SqlConstantExpression(value, typeMapping);
///
- public virtual SqlConstantExpression Constant(object? value, Type type, RelationalTypeMapping? typeMapping = null)
- => new(value, type, typeMapping);
+ public virtual SqlExpression Constant(object? value, Type type, RelationalTypeMapping? typeMapping = null)
+ => new SqlConstantExpression(value, type, typeMapping);
///
public virtual bool TryCreateLeast(
diff --git a/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs b/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs
index c8fb53f61cf..fe23e7f94fe 100644
--- a/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs
+++ b/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs
@@ -801,7 +801,7 @@ protected virtual SqlExpression VisitIn(InExpression inExpression, bool allowOpt
subquery.Offset,
subquery.Limit);
- var predicate = VisitSqlBinary(
+ var predicate = Visit(
_sqlExpressionFactory.Equal(subqueryProjection, item), allowOptimizedExpansion: true, out _);
subquery.ApplyPredicate(predicate);
subquery.ClearOrdering();
@@ -908,7 +908,7 @@ protected virtual SqlExpression VisitIn(InExpression inExpression, bool allowOpt
result,
(expr, nullableValue) => _sqlExpressionFactory.OrElse(
expr,
- VisitSqlBinary(_sqlExpressionFactory.Equal(item, nullableValue), allowOptimizedExpansion, out _)));
+ Visit(_sqlExpressionFactory.Equal(item, nullableValue), allowOptimizedExpansion, out _)));
InExpression ProcessInExpressionValues(
InExpression inExpression,
@@ -1873,8 +1873,13 @@ private SqlExpression RewriteNullSemantics(
return sqlBinaryExpression.Update(left, right);
}
- private SqlExpression SimplifyLogicalSqlBinaryExpression(SqlBinaryExpression sqlBinaryExpression)
+ private SqlExpression SimplifyLogicalSqlBinaryExpression(SqlExpression expression)
{
+ if (expression is not SqlBinaryExpression sqlBinaryExpression)
+ {
+ return expression;
+ }
+
if (sqlBinaryExpression is
{
Left: SqlUnaryExpression { OperatorType: ExpressionType.Equal or ExpressionType.NotEqual } leftUnary,
@@ -1930,13 +1935,14 @@ private SqlExpression SimplifyLogicalSqlBinaryExpression(SqlBinaryExpression sql
///
/// Attempts to simplify a unary not operation on a non-nullable operand.
///
- /// The expression to simplify.
+ /// The expression to simplify.
/// The simplified expression, or the original expression if it cannot be simplified.
- protected virtual SqlExpression OptimizeNonNullableNotExpression(SqlUnaryExpression sqlUnaryExpression)
+ protected virtual SqlExpression OptimizeNonNullableNotExpression(SqlExpression expression)
{
- if (sqlUnaryExpression.OperatorType != ExpressionType.Not)
+ if (expression is not SqlUnaryExpression sqlUnaryExpression
+ || sqlUnaryExpression.OperatorType != ExpressionType.Not)
{
- return sqlUnaryExpression;
+ return expression;
}
switch (sqlUnaryExpression.Operand)
@@ -2207,8 +2213,13 @@ protected virtual TableExpressionBase UpdateParameterCollection(
SqlParameterExpression newCollectionParameter)
=> throw new InvalidOperationException();
- private SqlExpression ProcessNullNotNull(SqlUnaryExpression sqlUnaryExpression, bool operandNullable)
+ private SqlExpression ProcessNullNotNull(SqlExpression sqlExpression, bool operandNullable)
{
+ if (sqlExpression is not SqlUnaryExpression sqlUnaryExpression)
+ {
+ return sqlExpression;
+ }
+
if (!operandNullable)
{
// when we know that operand is non-nullable:
diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerSqlTranslatingExpressionVisitor.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerSqlTranslatingExpressionVisitor.cs
index 8b04e50989d..873ca89fa1d 100644
--- a/src/EFCore.SqlServer/Query/Internal/SqlServerSqlTranslatingExpressionVisitor.cs
+++ b/src/EFCore.SqlServer/Query/Internal/SqlServerSqlTranslatingExpressionVisitor.cs
@@ -241,14 +241,7 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp
// CONCAT_WS filters out nulls, but string.Join treats them as empty strings; so coalesce (which is a no-op for non-nullable
// arguments).
- arguments[i + 1] = sqlArgument switch
- {
- ColumnExpression { IsNullable: false } => sqlArgument,
- SqlConstantExpression constantExpression => constantExpression.Value is null
- ? _sqlExpressionFactory.Constant(string.Empty)
- : constantExpression,
- _ => Dependencies.SqlExpressionFactory.Coalesce(sqlArgument, _sqlExpressionFactory.Constant(string.Empty))
- };
+ arguments[i + 1] = Dependencies.SqlExpressionFactory.Coalesce(sqlArgument, _sqlExpressionFactory.Constant(string.Empty));
}
// CONCAT_WS never returns null; a null delimiter is interpreted as an empty string, and null arguments are skipped
diff --git a/src/EFCore.SqlServer/Query/Internal/Translators/SqlServerDateTimeMemberTranslator.cs b/src/EFCore.SqlServer/Query/Internal/Translators/SqlServerDateTimeMemberTranslator.cs
index b2abb4be5cf..01fcba5be4e 100644
--- a/src/EFCore.SqlServer/Query/Internal/Translators/SqlServerDateTimeMemberTranslator.cs
+++ b/src/EFCore.SqlServer/Query/Internal/Translators/SqlServerDateTimeMemberTranslator.cs
@@ -112,7 +112,7 @@ public class SqlServerDateTimeMemberTranslator(
_ => null
};
- SqlFunctionExpression DatePart(string part)
+ SqlExpression DatePart(string part)
=> sqlExpressionFactory.Function(
"DATEPART",
arguments: [sqlExpressionFactory.Fragment(part), instance!],
diff --git a/src/EFCore.SqlServer/Query/Internal/Translators/SqlServerStringAggregateMethodTranslator.cs b/src/EFCore.SqlServer/Query/Internal/Translators/SqlServerStringAggregateMethodTranslator.cs
index c217cebd548..f2bb4a7d19d 100644
--- a/src/EFCore.SqlServer/Query/Internal/Translators/SqlServerStringAggregateMethodTranslator.cs
+++ b/src/EFCore.SqlServer/Query/Internal/Translators/SqlServerStringAggregateMethodTranslator.cs
@@ -80,14 +80,10 @@ public SqlServerStringAggregateMethodTranslator(
}
}
- // STRING_AGG filters out nulls, but string.Join treats them as empty strings; coalesce unless we know we're aggregating over
- // a non-nullable column.
- if (sqlExpression is not ColumnExpression { IsNullable: false })
- {
- sqlExpression = _sqlExpressionFactory.Coalesce(
- sqlExpression,
- _sqlExpressionFactory.Constant(string.Empty, typeof(string)));
- }
+ // STRING_AGG filters out nulls, but string.Join treats them as empty strings.
+ sqlExpression = _sqlExpressionFactory.Coalesce(
+ sqlExpression,
+ _sqlExpressionFactory.Constant(string.Empty, typeof(string)));
// STRING_AGG returns null when there are no rows (or non-null values), but string.Join returns an empty string.
return
diff --git a/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlExpressionFactory.cs b/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlExpressionFactory.cs
index 14a7330f642..a80c1647f5a 100644
--- a/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlExpressionFactory.cs
+++ b/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlExpressionFactory.cs
@@ -35,7 +35,7 @@ public SqliteSqlExpressionFactory(SqlExpressionFactoryDependencies dependencies)
/// 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 virtual SqlFunctionExpression Strftime(
+ public virtual SqlExpression Strftime(
Type returnType,
string format,
SqlExpression timestring,
@@ -82,7 +82,7 @@ public virtual SqlFunctionExpression Strftime(
/// 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 virtual SqlFunctionExpression Date(
+ public virtual SqlExpression Date(
Type returnType,
SqlExpression timestring,
IEnumerable? modifiers = null,
diff --git a/src/EFCore.Sqlite.Core/Query/Internal/Translators/SqliteStringAggregateMethodTranslator.cs b/src/EFCore.Sqlite.Core/Query/Internal/Translators/SqliteStringAggregateMethodTranslator.cs
index 621fc1ef222..b588eaa7fdf 100644
--- a/src/EFCore.Sqlite.Core/Query/Internal/Translators/SqliteStringAggregateMethodTranslator.cs
+++ b/src/EFCore.Sqlite.Core/Query/Internal/Translators/SqliteStringAggregateMethodTranslator.cs
@@ -60,12 +60,9 @@ public SqliteStringAggregateMethodTranslator(ISqlExpressionFactory sqlExpression
return null;
}
- if (sqlExpression is not ColumnExpression { IsNullable: false })
- {
- sqlExpression = _sqlExpressionFactory.Coalesce(
- sqlExpression,
- _sqlExpressionFactory.Constant(string.Empty, typeof(string)));
- }
+ sqlExpression = _sqlExpressionFactory.Coalesce(
+ sqlExpression,
+ _sqlExpressionFactory.Constant(string.Empty, typeof(string)));
if (source.Predicate != null)
{
diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs
index f948f7fc776..098b7169466 100644
--- a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs
@@ -6378,7 +6378,7 @@ public override async Task GetValueOrDefault_in_filter_non_nullable_column(bool
"""
SELECT [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId]
FROM [Weapons] AS [w]
-WHERE COALESCE([w].[Id], 0) = 0
+WHERE [w].[Id] = 0
""");
}
diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs
index 22e58c54fc5..bafb8cd54ca 100644
--- a/test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs
@@ -8662,7 +8662,7 @@ public override async Task GetValueOrDefault_in_filter_non_nullable_column(bool
"""
SELECT [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId]
FROM [Weapons] AS [w]
-WHERE COALESCE([w].[Id], 0) = 0
+WHERE [w].[Id] = 0
""");
}
diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs
index cadd97ff667..d906429c4fe 100644
--- a/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs
@@ -7260,7 +7260,7 @@ public override async Task GetValueOrDefault_in_filter_non_nullable_column(bool
"""
SELECT [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId]
FROM [Weapons] AS [w]
-WHERE COALESCE([w].[Id], 0) = 0
+WHERE [w].[Id] = 0
""");
}
diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs
index efe49c9bce9..7374c5a47ee 100644
--- a/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs
@@ -7311,7 +7311,7 @@ public override async Task GetValueOrDefault_in_filter_non_nullable_column(bool
"""
SELECT [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[PeriodEnd], [w].[PeriodStart], [w].[SynergyWithId]
FROM [Weapons] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [w]
-WHERE COALESCE([w].[Id], 0) = 0
+WHERE [w].[Id] = 0
""");
}
diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs
index a01b1bcc00d..d1a14d2e820 100644
--- a/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs
+++ b/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs
@@ -3096,7 +3096,7 @@ public override async Task GetValueOrDefault_in_filter_non_nullable_column(bool
"""
SELECT "w"."Id", "w"."AmmunitionType", "w"."IsAutomatic", "w"."Name", "w"."OwnerFullName", "w"."SynergyWithId"
FROM "Weapons" AS "w"
-WHERE COALESCE("w"."Id", 0) = 0
+WHERE "w"."Id" = 0
""");
}