Skip to content

ToString() on an enum with a string value converter fails in projections on EF 11 ("No coercion operator is defined"), possible regression from #38156 #39146

Closed

Description

@jufa2401

Bug description

Projecting ToString() on an enum property configured with HasConversion<string>() throws at query compilation in EF Core 11:

System.InvalidOperationException: No coercion operator is defined between types 'Role' and 'System.String'.

This worked in EF Core 10, and it fails the same way on SQLite, SQL Server and PostgreSQL (Npgsql). Filtering with ToString() (Where(x => x.Role.ToString() == "Admin")) still translates; only projections fail.

Query EF Core 10 EF Core 11 RC1 / latest RC2 daily
Select(x => x.Role.ToString()) SELECT CAST("t"."Role" AS TEXT) (SQLite)
SELECT CAST([t].[Role] AS nvarchar(max)) (SQL Server)
SELECT t."Role"::text (Npgsql)
InvalidOperationException: No coercion operator is defined between types 'Role' and 'System.String'.
Select(x => x.NullableRole.ToString()) SELECT t."NullableRole"::text Same exception
Select(x => Convert.ToString(x.Role)), (string)(object)x.Role Translate to the same CAST Same exception

This looks like a side effect of #38156 ("Strip no-op SQL CASTs"). Because the column is stored as text, the CAST(column AS text) that the ToString() translation produces has the same store type as its operand, so it gets stripped. The projection is then the bare column, still carrying the enum's EnumToStringConverter, and the shaper ends up trying to convert Role to string. That is the scenario the provider-facing note for #38156 (dotnet/EntityFramework.Docs#5351) describes ("relied on a wrapping Convert to paper over it -- the Convert now gets stripped, leaving the imprecise type exposed"), but here the affected translation is EF's own ToString() translation rather than a provider's.

Evidence that the stripped CAST is the cause: wrapping the result in a no-op concatenation, x.Role.ToString() + "", translates on EF 11 as SELECT "t"."Role" || ''. The CAST is gone, but the result type is now a plain string expression and the query works.

The pattern is common in apps that store enums as strings and use AutoMapper ProjectTo (or hand-written projections) to map the enum to a string DTO property. Our application has about 40 such maps, which all fail after upgrading.

Your code

using Microsoft.EntityFrameworkCore;

await using var context = new BlogContext();

// Throws on EF Core 11: No coercion operator is defined between types 'Role' and 'System.String'.
// On EF Core 10 this prints: SELECT CAST("u"."Role" AS TEXT) FROM "Users" AS "u"
// UseSqlServer(...) and UseNpgsql(...) fail identically.
Console.WriteLine(context.Users.Select(u => u.Role.ToString()).ToQueryString());

public enum Role { User, Admin }

public class User
{
    public int Id { get; set; }
    public Role Role { get; set; }
}

public class BlogContext : DbContext
{
    public DbSet<User> Users => Set<User>();

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        => optionsBuilder.UseSqlite("Data Source=:memory:");

    protected override void OnModelCreating(ModelBuilder modelBuilder)
        => modelBuilder.Entity<User>().Property(u => u.Role).HasConversion<string>();
}

Stack traces

System.InvalidOperationException: No coercion operator is defined between types 'Role' and 'System.String'.
   at System.Linq.Expressions.Expression.GetUserDefinedCoercionOrThrow(ExpressionType coercionType, Expression expression, Type convertToType)
   at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.CreateGetValueExpression(ParameterExpression dbDataReader, Int32 index, Boolean nullable, RelationalTypeMapping typeMapping, Type type, IPropertyBase property)
   at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.VisitExtension(Expression extensionExpression)
   at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.ProcessShaper(Expression shaperExpression, Expression& relationalCommandResolver, IReadOnlyList`1& readerColumns, LambdaExpression& relatedDataLoaders, Int32& collectionId)
   at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.VisitShapedQuery(ShapedQueryExpression shapedQueryExpression)
   at Microsoft.EntityFrameworkCore.Query.ShapedQueryCompilingExpressionVisitor.VisitExtension(Expression extensionExpression)
   at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.VisitExtension(Expression extensionExpression)
   at Microsoft.EntityFrameworkCore.Query.QueryCompilationContext.CreateQueryExecutorExpression[TResult](Expression query)
   at Microsoft.EntityFrameworkCore.Query.QueryCompilationContext.CreateQueryExecutor[TResult](Expression query)
   at Microsoft.EntityFrameworkCore.Storage.Database.CompileQuery[TResult](Expression query, Boolean async)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.CompileQueryCore[TResult](IDatabase database, Expression query, IModel model, Boolean async)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.ExecuteCore[TResult](Expression query, Boolean async, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.Execute[TResult](Expression expression)
   at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToQueryString(IQueryable source)

Verbose output


EF Core version

11.0.0-rc.1.26425.128

Database provider

Microsoft.EntityFrameworkCore.Sqlite, Microsoft.EntityFrameworkCore.SqlServer, Npgsql.EntityFrameworkCore.PostgreSQL 11.0.0-rc.1.1

Target framework

.NET 11.0 (SDK 11.0.100-rc.1.26425.128)

Operating system

macOS 27.0 (26A428, arm64); Linux x64 in CI: Ubuntu 26.04.1 LTS (mcr.microsoft.com/dotnet/sdk:11.0-resolute)

IDE

Jetbrains Rider

Activity

  1. z0rimo commented on Oct 6, 2026

    @z0rimo
    Contributor

    I'd be interested in working on this.

    I traced the current path and this does seem related to the interaction between #33733 and #38156:

    • EnumMethodTranslator translates ToString() over an enum using EnumToStringConverter<> as a Convert(instance, typeof(string)). The comment there already points to Allow changing CLR type in query translation without additional SQL #33733, since the SQL conversion itself isn't really needed but the CLR type change is.
    • Strip no-op SQL CASTs #38156 then strips that Convert when its store type matches the operand's store type.
    • The projection binding still expects a string, but after simplification the projected expression is the original enum column with its EnumToStringConverter.
    • In CreateGetValueExpression, the shaper applies the converter back from the provider string to the enum model type, and then attempts to coerce that enum to the projection's string type, which is where the exception is thrown.

    A narrow fix I'm considering is at the projection materialization boundary: when a projection explicitly expects the converter's provider CLR type (and that differs from the converter's model CLR type), materialize the provider value directly instead of applying ConvertFromProvider. This would preserve the no-op CAST removal from #38156 rather than reintroducing the CAST just for projections.

    I'd add regression coverage for both non-nullable and nullable enum-to-string converted properties, with SQLite and SQL Server SQL baselines, and make sure the existing predicate/Contains behavior remains unchanged.

    Does that sound like the right layer to address this, or would you prefer this to be handled closer to translation/type mapping (e.g. as part of #33733)?

  2. added theissue type on Oct 7, 2026
  3. AndriySvyryd commented on Oct 7, 2026

    @AndriySvyryd
    Member

    @z0rimo We can't assume that ConvertFromProvider is no-op, so the full fix should be done as part of #33733

  4. added this to the 11.0.0 milestone on Oct 8, 2026
  5. added a commit that references this issue on Oct 9, 2026
    0abd386
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions