dotnet / efcore

EF Core is a modern object-database mapper for .NET. It supports LINQ queries, change tracking, updates, and schema migrations.
https://docs.microsoft.com/ef/
MIT License
13.8k stars 3.2k forks source link

GroupBy over column converted by Convert.ToString creates only one group #30882

Open GertArnold opened 1 year ago

GertArnold commented 1 year ago

While investigating some other issue, I noticed that using GroupBy over a column converted by Convert.ToString() creates only one group while multiple groups are expected.

Code to reproduce (in Linqpad, using Microsoft.EntityFrameworkCore.SqlServer 7.0.5).

void Main()
{
    newDb();
    using (var db = getContext())
    {
        db.Things.Select(c => Convert.ToString(c.Name))
            .GroupBy(x => x).Dump(); // Query 1
        db.Things.Select(c => c.Name)
            .GroupBy(x => x).Dump(); // Query 2
    }
}

void newDb()
{
    using var db = getContext();
    db.Database.EnsureDeleted();
    db.Database.EnsureCreated();
    var things = new[]
    {
        new Thing { Name = "Thing 1" },
        new Thing { Name = "Thing 1" },
        new Thing { Name = "Thing 2" },
        new Thing { Name = "Thing 2" },
        new Thing { Name = "Thing 3" },
        new Thing { Name = "Thing 3" },
    };

    db.Things.AddRange(things);
    db.SaveChanges();
}

MyContext getContext()
{
    var connectionString = @$"Server=(localDB)\MSSQLLocalDB;database=groupbyIssue;Integrated Security=true;Encrypt=true;TrustServerCertificate=true;Application Name=Linqpad";
    return new MyContext(connectionString);
}

public class Thing
{
    public int Id { get; set; }
    public string Name { get; set; }
}

class MyContext : DbContext
{
    private string _connString;
    public MyContext(string connectionString) : base()
    {
        _connString = connectionString;
    }

    public DbSet<Thing> Things { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer(_connString)
            .LogTo(s => s.Dump(), LogLevel.Information);
    }
}

Query 1 returns a Group having Thing 1 as Key and containing all items. It also has pretty convoluted generated SQL.

Query 2 returns 3 groups as expected.

ajcvickers commented 1 year ago

Note for triage: I am able to reproduce this; it appears to be an issue with client-side grouping as the final operator. Using AsEnumerable before the GroupBy results in correct grouping.

SQL:

      SELECT CONVERT(nvarchar(max), [t0].[Name]), [t0].[c]
      FROM (
          SELECT CONVERT(nvarchar(max), [t].[Name]) AS [c], [t].[Name]
          FROM [Things] AS [t]
      ) AS [t0]
      ORDER BY CONVERT(nvarchar(max), [t0].[Name])
      SELECT [t].[Name]
      FROM [Things] AS [t]