Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,10 @@ namespace Microsoft.EntityFrameworkCore.Query.Internal;
/// 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.
/// </summary>
public class SqlExpressionSimplifyingExpressionVisitor : ExpressionVisitor
public class SqlExpressionSimplifyingExpressionVisitor(
ISqlExpressionFactory _sqlExpressionFactory,
bool _useRelationalNulls) : ExpressionVisitor
{
private readonly ISqlExpressionFactory _sqlExpressionFactory;
private readonly bool _useRelationalNulls;

/// <summary>
/// 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.
/// </summary>
public SqlExpressionSimplifyingExpressionVisitor(ISqlExpressionFactory sqlExpressionFactory, bool useRelationalNulls)
{
_sqlExpressionFactory = sqlExpressionFactory;
_useRelationalNulls = useRelationalNulls;
}

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand All @@ -37,51 +25,66 @@ public SqlExpressionSimplifyingExpressionVisitor(ISqlExpressionFactory sqlExpres
/// </summary>
protected override Expression VisitExtension(Expression extensionExpression)
{
if (extensionExpression is ShapedQueryExpression shapedQueryExpression)
switch (extensionExpression)
{
var newQueryExpression = Visit(shapedQueryExpression.QueryExpression);
var newShaperExpression = Visit(shapedQueryExpression.ShaperExpression);
case ShapedQueryExpression shapedQueryExpression:
{
var newQueryExpression = Visit(shapedQueryExpression.QueryExpression);
var newShaperExpression = Visit(shapedQueryExpression.ShaperExpression);

return shapedQueryExpression.Update(newQueryExpression, newShaperExpression);
}
return shapedQueryExpression.Update(newQueryExpression, newShaperExpression);
}

if (extensionExpression is SqlBinaryExpression sqlBinaryExpression)
{
return SimplifySqlBinary(sqlBinaryExpression);
}
// Strip no-op SQL CASTs: when the Convert's store type matches the operand's store type,
// the CAST would be a no-op in SQL (e.g. CAST(column AS nvarchar(max)) when column is already nvarchar(max)).
// This can occur in various situations, e.g. when a C# implicit conversion exists for a value-converted type
// (see #36247 for more context on why we don't refrain from creating the CAST node during translation).
// However, CASTs around constants are preserved: they serve to explicitly type the constant in SQL
// (e.g. CAST(100 AS int)), which is important for some queries.
case SqlUnaryExpression
{
OperatorType: ExpressionType.Convert,
Operand: not SqlConstantExpression and { TypeMapping.StoreType: var operandStoreType } operand,
TypeMapping.StoreType: var convertStoreType
} when convertStoreType == operandStoreType:
return Visit(operand);

if (extensionExpression is SqlFunctionExpression sqlFunctionExpression
&& IsCoalesce(sqlFunctionExpression))
{
var arguments = new List<SqlExpression>();
foreach (var argument in sqlFunctionExpression.Arguments!)
case SqlBinaryExpression sqlBinaryExpression:
return SimplifySqlBinary(sqlBinaryExpression);

case SqlFunctionExpression sqlFunctionExpression when IsCoalesce(sqlFunctionExpression):
{
var newArgument = (SqlExpression)Visit(argument);
if (IsCoalesce(newArgument))
var arguments = new List<SqlExpression>();
foreach (var argument in sqlFunctionExpression.Arguments!)
{
arguments.AddRange(((SqlFunctionExpression)newArgument).Arguments!);
}
else
{
arguments.Add(newArgument);
var newArgument = (SqlExpression)Visit(argument);
if (IsCoalesce(newArgument))
{
arguments.AddRange(((SqlFunctionExpression)newArgument).Arguments!);
}
else
{
arguments.Add(newArgument);
}
}

var distinctArguments = arguments.Distinct().ToList();

return distinctArguments.Count > 1
? new SqlFunctionExpression(
sqlFunctionExpression.Name,
distinctArguments,
sqlFunctionExpression.IsNullable,
argumentsPropagateNullability: distinctArguments.Select(_ => false).ToArray(),
sqlFunctionExpression.Type,
sqlFunctionExpression.TypeMapping)
: distinctArguments[0];
}

var distinctArguments = arguments.Distinct().ToList();

return distinctArguments.Count > 1
? new SqlFunctionExpression(
sqlFunctionExpression.Name,
distinctArguments,
sqlFunctionExpression.IsNullable,
argumentsPropagateNullability: distinctArguments.Select(_ => false).ToArray(),
sqlFunctionExpression.Type,
sqlFunctionExpression.TypeMapping)
: distinctArguments[0];
default:
return base.VisitExtension(extensionExpression);
}

return base.VisitExtension(extensionExpression);

static bool IsCoalesce(SqlExpression sqlExpression)
=> sqlExpression is SqlFunctionExpression { IsBuiltIn: true, Instance: null } sqlFunctionExpression
&& string.Equals(sqlFunctionExpression.Name, "COALESCE", StringComparison.OrdinalIgnoreCase)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public class SqlServerIsDateFunctionTranslator(ISqlExpressionFactory sqlExpressi
[arguments[1]],
nullable: true,
argumentsPropagateNullability: Statics.TrueArrays[1],
method.ReturnType),
typeof(int)),
method.ReturnType)
: null;
}
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,53 @@ public class BlogDetails
}

#endregion

#region 36247

[ConditionalTheory, MemberData(nameof(IsAsyncData))]
public virtual async Task Like_on_value_converted_string_column_does_not_produce_cast(bool async)
{
var contextFactory = await InitializeNonSharedTest<Context36247>(
seed: async ctx =>
{
ctx.Users.AddRange(
new Context36247.User { Name = new Context36247.FullName("Name1") },
new Context36247.User { Name = new Context36247.FullName("Name2") });
await ctx.SaveChangesAsync();
});
using var context = contextFactory.CreateDbContext();

var query = context.Users.Where(x => EF.Functions.Like(x.Name, "Name%"));

var result = async
? await query.ToListAsync()
: [.. query];

Assert.Equal(2, result.Count);
}

protected class Context36247(DbContextOptions options) : DbContext(options)
{
public DbSet<User> Users { get; set; }

protected override void OnModelCreating(ModelBuilder modelBuilder)
=> modelBuilder.Entity<User>().Property(e => e.Name)
.HasConversion(v => v.Value, v => new FullName(v));

public class User
{
public int Id { get; set; }
public FullName Name { get; set; }
}

public readonly record struct FullName(string Value)
{
public static implicit operator string(FullName fullName)
=> fullName.Value;
}
}

#endregion
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2211,7 +2211,7 @@ public override async Task Null_check_removal_in_ternary_maintain_appropriate_ca

AssertSql(
"""
SELECT CAST([f].[Taste] AS tinyint) AS [Bar]
SELECT [f].[Taste] AS [Bar]
FROM [Foods] AS [f]
""");
}
Expand Down Expand Up @@ -2668,6 +2668,18 @@ ELSE N'B'
END AS [Foo]
FROM [Data] AS [d]
ORDER BY [d].[Id]
""");
}

public override async Task Like_on_value_converted_string_column_does_not_produce_cast(bool async)
{
await base.Like_on_value_converted_string_column_does_not_produce_cast(async);

AssertSql(
"""
SELECT [u].[Id], [u].[Name]
FROM [Users] AS [u]
WHERE [u].[Name] LIKE N'Name%'
""");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3546,7 +3546,7 @@ public override async Task ToString_enum_contains(bool async)
"""
SELECT [m].[CodeName]
FROM [Missions] AS [m]
WHERE CAST([m].[Difficulty] AS nvarchar(max)) LIKE N'%Med%'
WHERE [m].[Difficulty] LIKE N'%Med%'
""");
}

Expand Down Expand Up @@ -7429,7 +7429,7 @@ public override async Task Enum_flags_closure_typed_as_different_type_generates_

SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank]
FROM [Gears] AS [g]
WHERE @prm & CAST([g].[Rank] AS int) = CAST([g].[Rank] AS int)
WHERE @prm & [g].[Rank] = [g].[Rank]
""");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4730,7 +4730,7 @@ public override async Task ToString_enum_contains(bool async)
"""
SELECT [m].[CodeName]
FROM [Missions] AS [m]
WHERE CAST([m].[Difficulty] AS nvarchar(max)) LIKE N'%Med%'
WHERE [m].[Difficulty] LIKE N'%Med%'
""");
}

Expand Down Expand Up @@ -9915,7 +9915,7 @@ UNION ALL
SELECT [o].[Nickname], [o].[SquadId], [o].[AssignedCityName], [o].[CityOfBirthName], [o].[FullName], [o].[HasSoulPatch], [o].[LeaderNickname], [o].[LeaderSquadId], [o].[Rank], N'Officer' AS [Discriminator]
FROM [Officers] AS [o]
) AS [u]
WHERE @prm & CAST([u].[Rank] AS int) = CAST([u].[Rank] AS int)
WHERE @prm & [u].[Rank] = [u].[Rank]
""");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4139,7 +4139,7 @@ public override async Task ToString_enum_contains(bool async)
"""
SELECT [m].[CodeName]
FROM [Missions] AS [m]
WHERE CAST([m].[Difficulty] AS nvarchar(max)) LIKE N'%Med%'
WHERE [m].[Difficulty] LIKE N'%Med%'
""");
}

Expand Down Expand Up @@ -8377,7 +8377,7 @@ WHEN [o].[Nickname] IS NOT NULL THEN N'Officer'
END AS [Discriminator]
FROM [Gears] AS [g]
LEFT JOIN [Officers] AS [o] ON [g].[Nickname] = [o].[Nickname] AND [g].[SquadId] = [o].[SquadId]
WHERE @prm & CAST([g].[Rank] AS int) = CAST([g].[Rank] AS int)
WHERE @prm & [g].[Rank] = [g].[Rank]
""");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,11 @@ public override async Task SqlQuery_composed_Join(bool async)

AssertSql(
"""
SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate], CAST([s].[Value] AS int) AS [p]
SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate], [s].[Value] AS [p]
FROM [Orders] AS [o]
INNER JOIN (
SELECT "ProductID" AS "Value" FROM "Products"
) AS [s] ON [o].[OrderID] = CAST([s].[Value] AS int)
) AS [s] ON [o].[OrderID] = [s].[Value]
""");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,7 @@ public override async Task Inline_collection_with_single_parameter_element_Count
FROM [PrimitiveCollectionsEntity] AS [p]
WHERE (
SELECT COUNT(*)
FROM (VALUES (CAST(@i AS int))) AS [v]([Value])
FROM (VALUES (@i)) AS [v]([Value])
WHERE [v].[Value] > [p].[Id]) = 1
""");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,7 @@ public override async Task Inline_collection_with_single_parameter_element_Count
FROM [PrimitiveCollectionsEntity] AS [p]
WHERE (
SELECT COUNT(*)
FROM (VALUES (CAST(@i AS int))) AS [v]([Value])
FROM (VALUES (@i)) AS [v]([Value])
WHERE [v].[Value] > [p].[Id]) = 1
""");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public override async Task Inline_collection_with_single_parameter_element_Count
FROM [PrimitiveCollectionsEntity] AS [p]
WHERE (
SELECT COUNT(*)
FROM (VALUES (CAST(@i AS int))) AS [v]([Value])
FROM (VALUES (@i)) AS [v]([Value])
WHERE [v].[Value] > [p].[Id]) = 1
""");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -443,7 +443,7 @@ public override async Task Inline_collection_with_single_parameter_element_Count
FROM [PrimitiveCollectionsEntity] AS [p]
WHERE (
SELECT COUNT(*)
FROM (VALUES (CAST(@i AS int))) AS [v]([Value])
FROM (VALUES (@i)) AS [v]([Value])
WHERE [v].[Value] > [p].[Id]) = 1
""");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4239,7 +4239,7 @@ public override async Task ToString_enum_contains(bool async)
"""
SELECT [m].[CodeName]
FROM [Missions] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [m]
WHERE CAST([m].[Difficulty] AS nvarchar(max)) LIKE N'%Med%'
WHERE [m].[Difficulty] LIKE N'%Med%'
""");
}

Expand Down Expand Up @@ -5350,7 +5350,7 @@ public override async Task Enum_flags_closure_typed_as_different_type_generates_

SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[PeriodEnd], [g].[PeriodStart], [g].[Rank]
FROM [Gears] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [g]
WHERE @prm & CAST([g].[Rank] AS int) = CAST([g].[Rank] AS int)
WHERE @prm & [g].[Rank] = [g].[Rank]
""");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ public override async Task FromTimeSpan_compared_to_property()
"""
SELECT [b].[Id], [b].[Bool], [b].[Byte], [b].[ByteArray], [b].[DateOnly], [b].[DateTime], [b].[DateTimeOffset], [b].[Decimal], [b].[Double], [b].[Enum], [b].[FlagsEnum], [b].[Float], [b].[Guid], [b].[Int], [b].[Long], [b].[Short], [b].[String], [b].[TimeOnly], [b].[TimeSpan]
FROM [BasicTypesEntities] AS [b]
WHERE CAST([b].[TimeSpan] AS time) < [b].[TimeOnly]
WHERE [b].[TimeSpan] < [b].[TimeOnly]
""");
}

Expand All @@ -202,7 +202,7 @@ public override async Task FromTimeSpan_compared_to_parameter()

SELECT [b].[Id], [b].[Bool], [b].[Byte], [b].[ByteArray], [b].[DateOnly], [b].[DateTime], [b].[DateTimeOffset], [b].[Decimal], [b].[Double], [b].[Enum], [b].[FlagsEnum], [b].[Float], [b].[Guid], [b].[Int], [b].[Long], [b].[Short], [b].[String], [b].[TimeOnly], [b].[TimeSpan]
FROM [BasicTypesEntities] AS [b]
WHERE CAST([b].[TimeSpan] AS time) = @time
WHERE [b].[TimeSpan] = @time
""");
}

Expand All @@ -214,7 +214,7 @@ public override async Task Order_by_FromTimeSpan()
"""
SELECT [b].[Id], [b].[Bool], [b].[Byte], [b].[ByteArray], [b].[DateOnly], [b].[DateTime], [b].[DateTimeOffset], [b].[Decimal], [b].[Double], [b].[Enum], [b].[FlagsEnum], [b].[Float], [b].[Guid], [b].[Int], [b].[Long], [b].[Short], [b].[String], [b].[TimeOnly], [b].[TimeSpan]
FROM [BasicTypesEntities] AS [b]
ORDER BY CAST([b].[TimeSpan] AS time)
ORDER BY [b].[TimeSpan]
""");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1770,7 +1770,7 @@ public override async Task Object_to_string_conversion()

AssertSql(
"""
SELECT CAST("b"."TestSignedByte" AS TEXT), CAST("b"."TestByte" AS TEXT), CAST("b"."TestInt16" AS TEXT), CAST("b"."TestUnsignedInt16" AS TEXT), CAST("b"."TestInt32" AS TEXT), CAST("b"."TestUnsignedInt32" AS TEXT), CAST("b"."TestInt64" AS TEXT), "b"."TestUnsignedInt64", CAST("b"."TestSingle" AS TEXT), CAST("b"."TestDouble" AS TEXT), CAST("b"."TestDecimal" AS TEXT), CAST("b"."TestCharacter" AS TEXT), CAST("b"."TestDateTime" AS TEXT), CAST("b"."TestDateTimeOffset" AS TEXT), CAST("b"."TestTimeSpan" AS TEXT), CAST("b"."TestDateOnly" AS TEXT), CAST("b"."TestTimeOnly" AS TEXT)
SELECT CAST("b"."TestSignedByte" AS TEXT), CAST("b"."TestByte" AS TEXT), CAST("b"."TestInt16" AS TEXT), CAST("b"."TestUnsignedInt16" AS TEXT), CAST("b"."TestInt32" AS TEXT), CAST("b"."TestUnsignedInt32" AS TEXT), CAST("b"."TestInt64" AS TEXT), "b"."TestUnsignedInt64", CAST("b"."TestSingle" AS TEXT), CAST("b"."TestDouble" AS TEXT), "b"."TestDecimal", "b"."TestCharacter", "b"."TestDateTime", "b"."TestDateTimeOffset", "b"."TestTimeSpan", "b"."TestDateOnly", "b"."TestTimeOnly"
FROM "BuiltInDataTypes" AS "b"
WHERE "b"."Id" = 13
""");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1069,7 +1069,7 @@ public override async Task Update_Where_using_navigation_2_set_constant(bool asy
@p='1'

UPDATE "Order Details" AS "o"
SET "Quantity" = CAST(@p AS INTEGER)
SET "Quantity" = @p
FROM "Orders" AS "o0"
LEFT JOIN "Customers" AS "c" ON "o0"."CustomerID" = "c"."CustomerID"
WHERE "c"."City" = 'Seattle' AND "o"."OrderID" = "o0"."OrderID"
Expand Down Expand Up @@ -1546,7 +1546,7 @@ public override async Task Update_with_PK_pushdown_and_join_and_multiple_setters
@p2='10'

UPDATE "Order Details" AS "o2"
SET "Quantity" = CAST(@p AS INTEGER),
SET "Quantity" = @p,
"UnitPrice" = @p2
FROM (
SELECT "o1"."OrderID", "o1"."ProductID"
Expand Down
Loading
Loading