-
Notifications
You must be signed in to change notification settings - Fork 3.3k
Description
After upgrading to EF Core 8, my custom converter for Enum
no longer work. For readability in the database, we have decided to use nvarchar(1)
fields to represent enum values. It's up to the developer to define some sort of char
per enum field, for example
public enum EnumProperty { FieldA = 'A', FieldB = 'B' }
This was working fine with EF Core 6 and EF7, using a custom converter:
internal class EnumValueConverter<T> : ValueConverter<T, char> where T : Enum, IConvertible
{
public EnumValueConverter() : base(p => p.ToChar(null), p => (T)Enum.Parse(typeof(T), Convert.ToInt32(p).ToString())) { }
}
Stacktrace
With the new Contains
LINQ conversion and OPENJSON
syntax however, EF8 fails to generate SQL and instead crashes with the following exception:
System.InvalidCastException
HResult=0x80004002
Message=Unable to cast object of type 'System.String' to type 'System.Char'.
Source=Microsoft.EntityFrameworkCore
StackTrace:
at Microsoft.EntityFrameworkCore.Storage.Json.JsonConvertedValueReaderWriter`2.ToJsonTyped(Utf8JsonWriter writer, TModel value)
at Microsoft.EntityFrameworkCore.Storage.Json.JsonCollectionReaderWriter`3.ToJsonTyped(Utf8JsonWriter writer, IEnumerable`1 value)
at Microsoft.EntityFrameworkCore.Storage.Json.JsonValueReaderWriter`1.ToJson(Utf8JsonWriter writer, Object value)
at Microsoft.EntityFrameworkCore.Storage.Json.JsonValueReaderWriter.ToJsonString(Object value)
at Microsoft.EntityFrameworkCore.Storage.ValueConversion.ValueConverter`2.<>c__DisplayClass6_0`2.<SanitizeConverter>b__1(Object v)
at Microsoft.EntityFrameworkCore.Storage.RelationalTypeMapping.CreateParameter(DbCommand command, String name, Object value, Nullable`1 nullable, ParameterDirection direction)
at Microsoft.EntityFrameworkCore.Storage.Internal.TypeMappedRelationalParameter.AddDbParameter(DbCommand command, Object value)
at Microsoft.EntityFrameworkCore.Storage.Internal.RelationalParameterBase.AddDbParameter(DbCommand command, IReadOnlyDictionary`2 parameterValues)
at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.CreateDbCommand(RelationalCommandParameterObject parameterObject, Guid commandId, DbCommandMethod commandMethod)
at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.CreateDbCommand()
at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.ToQueryString()
at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToQueryString(IQueryable source)
at Program.<<Main>$>d__0.MoveNext() in Program.cs:line 13
at Program.<<Main>$>d__0.MoveNext() in Program.cs:line 14
at Program.<Main>(String[] args)
This can be worked around by falling back to options.UseCompatibilityLevel(120);
and not using OPENJSON
.
Include your code
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
await using var context = new BlogContext();
await context.Database.EnsureDeletedAsync();
await context.Database.EnsureCreatedAsync();
var items = new List<EnumProperty>() { EnumProperty.FieldA, EnumProperty.FieldB };
var query = context.Set<Person>().Where(p => items.Contains(p.EnumProperty)).ToQueryString();
Console.WriteLine(query);
public class BlogContext : DbContext
{
DbSet<Person> People => Set<Person>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Person>().Property(p => p.EnumProperty).HasConversion(new EnumValueConverter<EnumProperty>());
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
optionsBuilder.UseSqlServer("Data Source=.;Initial Catalog=BugRepo;Integrated Security=True;TrustServerCertificate=True");
}
}
internal class EnumValueConverter<T> : ValueConverter<T, char> where T : Enum, IConvertible
{
public EnumValueConverter()
: base(p => p.ToChar(null), p => (T)Enum.Parse(typeof(T), Convert.ToInt32(p).ToString()))
{
}
}
public class Person
{
public int Id { get; set; }
public EnumProperty EnumProperty { get; set; }
}
public enum EnumProperty
{
FieldA = 'A',
FieldB = 'B',
FieldC = 'C',
}
Include verbose output
Using assembly 'EfCoreContainsBug'.
Using startup assembly 'EfCoreContainsBug'.
Using root namespace 'EfCoreContainsBug'.
Remaining arguments: .
Finding DbContext classes...
Finding IDesignTimeDbContextFactory implementations...
Finding application service provider in assembly 'EfCoreContainsBug'...
Finding Microsoft.Extensions.Hosting service provider...
No static method 'CreateHostBuilder(string[])' was found on class 'Program'.
No application service provider was found.
Finding DbContext classes in the project...
Found DbContext 'BlogContext'.
BlogContext
Include provider and version information
EF Core version: 8.0.0
Database provider: Microsoft.EntityFrameworkCore.SqlServer
Target framework: .NET 8.0
Operating system: Win10
IDE: Visual Studio 2022 17.8
dsteinweg