Skip to content

Difference in query translation compared to Pomelo 8.0 #360

Description

@crjc

Describe the bug
In Pomelo's 8.x, the following query would be translated differently. Upgrading to Pomelo's 9.x release or the latest release of this fork produces a different query which can provide unexpected results.

// User.GroupId is of type `Guid?`
string? value = "71c9eacb-468b-41a9-81c5-579d4119264e";
context.Users.Where(x => x.GroupId.ToString() == value).First();

(EXPECTED BEHAVIOUR) In 8.0, the translated query was:

SELECT `u`.`Id`, `u`.`GroupId`, `u`.`Name`
FROM `Users` AS `u`
WHERE CAST(`u`.`GroupId` AS char) = '71c9eacb-468b-41a9-81c5-579d4119264e'

In 9.0+, the translation is:

SELECT `u`.`Id`, `u`.`GroupId`, `u`.`Name`
FROM `Users` AS `u`
WHERE COALESCE(CAST(`u`.`GroupId` AS char), '') = '6507e45c-9468-4154-87ff-9a4544a242f0'

However, if we set string? value = null, the queries become:

(EXPECTED BEHAVIOUR) 8.0:

SELECT `u`.`Id`, `u`.`GroupId`, `u`.`Name`
FROM `Users` AS `u`
WHERE `u`.`GroupId` IS NULL

9.0+

SELECT `u`.`Id`, `u`.`GroupId`, `u`.`Name`
FROM `Users` AS `u`
WHERE FALSE

To Reproduce

This test code will log an error, but not on older 8.0 versions:

using Microsoft.EntityFrameworkCore;

var connectionString = "...";

var options = new DbContextOptionsBuilder<ApplicationDbContext>()
    .UseMySql(connectionString, ServerVersion.AutoDetect(connectionString))
    .Options;

try
{
    using var context = new ApplicationDbContext(options);

    await context.Database.EnsureDeletedAsync();
    await context.Database.EnsureCreatedAsync();

    // create entities we can query
    var groupId = Guid.NewGuid();
    context.Users.Add(new User()
    {
        Id = Guid.NewGuid(),
        GroupId = groupId,
        Name = "John"
    });
    context.Users.Add(new User()
    {
        Id = Guid.NewGuid(),
        GroupId = null,
        Name = "Bob"
    });
    await context.SaveChangesAsync();

    // `FirstAsync` will throw an exception as no entities match the new translated query
    string? groupIdAsString = null;
    await context.Users.Where(x => x.GroupId.ToString() == groupIdAsString).FirstAsync();
    Console.WriteLine("   ✓ Query successful!");
}
catch (Exception ex)
{ 
    Console.WriteLine($"Message: {ex.Message}");
    if (ex.InnerException != null)
    {
        Console.WriteLine($"Inner Exception: {ex.InnerException.Message}");
    }
    Console.WriteLine($"\nStack Trace:\n{ex.StackTrace}");
    return 1;
}

return 0;

public class User
{
    public Guid Id { get; set; }
    public Guid? GroupId { get; set; }
    public required string Name { get; set; }
}

public class ApplicationDbContext : DbContext
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
        : base(options)
    {
    }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        builder.Entity<User>().HasKey(x => x.Id);
        base.OnModelCreating(builder);
    }

    public DbSet<User> Users { get; set; }
}

Additional context

The COALESCE method originates from this line of code:

In v8.0, this method is not present:
https://github.com/PomeloFoundation/Pomelo.EntityFrameworkCore.MySql/blob/401acc215fe7db320447c22a6e4337f09008e619/src/EFCore.MySql/Query/Internal/MySqlObjectToStringTranslator.cs#L87

I have confirmed removing the COALESCE expression brings back the old expected behaviour, but I have not looked into the rationale of the original change yet.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions