Repository navigation
Description
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
z0rimo commentedon Oct 6, 2026
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:
EnumMethodTranslatortranslatesToString()over an enum usingEnumToStringConverter<>as aConvert(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.Convertwhen its store type matches the operand's store type.string, but after simplification the projected expression is the original enum column with itsEnumToStringConverter.CreateGetValueExpression, the shaper applies the converter back from the providerstringto the enum model type, and then attempts to coerce that enum to the projection'sstringtype, 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/
Containsbehavior 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)?
AndriySvyryd commentedon Oct 7, 2026
@z0rimo We can't assume that
ConvertFromProvideris no-op, so the full fix should be done as part of #33733[release/11.0] Preserve value-converter cast when simplifying project…