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
25 changes: 18 additions & 7 deletions src/EFCore.PG/Query/NpgsqlSqlExpressionFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,7 @@ public virtual PgFunctionExpression AggregateFunction(
sqlExpression = sqlExpression switch
{
SqlBinaryExpression e => ApplyTypeMappingOnSqlBinary(e, typeMapping),
LikeExpression e => ApplyTypeMappingOnLike(e),

// PostgreSQL-specific expression types
PgAnyExpression e => ApplyTypeMappingOnAny(e),
Expand Down Expand Up @@ -710,20 +711,30 @@ private SqlExpression ApplyTypeMappingOnArraySlice(
array.TypeMapping);
}

// The base implementation is private, so duplicate it here to keep the escape character's type mapping independent.
private SqlExpression ApplyTypeMappingOnLike(LikeExpression likeExpression)
{
var inferredTypeMapping = ExpressionExtensions.InferTypeMapping(likeExpression.Match, likeExpression.Pattern)
?? _typeMappingSource.FindMapping(likeExpression.Match.Type, Dependencies.Model);

return new LikeExpression(
ApplyTypeMapping(likeExpression.Match, inferredTypeMapping),
ApplyTypeMapping(likeExpression.Pattern, inferredTypeMapping),
// The escape character must not inherit the match or pattern's value converter (#3888).
ApplyDefaultTypeMapping(likeExpression.EscapeChar),
_boolTypeMapping);
Comment thread
roji marked this conversation as resolved.
}

private SqlExpression ApplyTypeMappingOnILike(PgILikeExpression ilikeExpression)
{
var inferredTypeMapping = (ilikeExpression.EscapeChar is null
? ExpressionExtensions.InferTypeMapping(
ilikeExpression.Match, ilikeExpression.Pattern)
: ExpressionExtensions.InferTypeMapping(
ilikeExpression.Match, ilikeExpression.Pattern,
ilikeExpression.EscapeChar))
var inferredTypeMapping = ExpressionExtensions.InferTypeMapping(ilikeExpression.Match, ilikeExpression.Pattern)
?? _typeMappingSource.FindMapping(ilikeExpression.Match.Type, Dependencies.Model);

return new PgILikeExpression(
ApplyTypeMapping(ilikeExpression.Match, inferredTypeMapping),
ApplyTypeMapping(ilikeExpression.Pattern, inferredTypeMapping),
ApplyTypeMapping(ilikeExpression.EscapeChar, inferredTypeMapping),
// The escape character must not inherit the match or pattern's value converter (#3888).
ApplyDefaultTypeMapping(ilikeExpression.EscapeChar),
_boolTypeMapping);
Comment thread
roji marked this conversation as resolved.
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,57 @@ public static DateTime Modify(DateTime date)
=> throw new NotSupportedException();
}
}

[ConditionalFact]
public virtual async Task Like_with_implicit_escape_does_not_apply_value_converter()
{
var contextFactory = await InitializeNonSharedTest<Context3888>(
seed: async context =>
{
context.Entities.Add(new Context3888.Entity { Value = "ABC" });
await context.SaveChangesAsync();
});

await using var context = contextFactory.CreateDbContext();

var pattern = "%ABC%";

Assert.Equal(1, await context.Entities.CountAsync(e => EF.Functions.Like(e.Value!, pattern)));
Assert.Equal(1, await context.Entities.CountAsync(e => EF.Functions.ILike(e.Value!, pattern)));

AssertSql(
"""
@pattern='%ABC%'

SELECT count(*)::int
FROM "Entities" AS e
WHERE e."Value" LIKE @pattern ESCAPE ''
""",
//
"""
@pattern='%ABC%'

SELECT count(*)::int
FROM "Entities" AS e
WHERE e."Value" ILIKE @pattern ESCAPE ''
""");
}

protected class Context3888(DbContextOptions options) : DbContext(options)
{
public DbSet<Entity> Entities { get; set; } = null!;

protected override void OnModelCreating(ModelBuilder modelBuilder)
=> modelBuilder.Entity<Entity>()
.Property(e => e.Value)
.HasConversion(
value => string.IsNullOrWhiteSpace(value) ? null : value,
value => value ?? string.Empty);

public class Entity
{
public int Id { get; set; }
public string? Value { get; set; }
}
}
}
Loading