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.41k stars 3.11k forks source link

```dotnet ef dbcontext optimize``` generates invalid code when using an inner type converter #33710

Open TonyValenti opened 2 weeks ago

TonyValenti commented 2 weeks ago

File a bug

Run dotnet ef dbcontext optimize on this code:

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace ConsoleApp20 {
    internal class Program {
        static void Main(string[] args) {
            Console.WriteLine("Hello, World!");
        }
    }

    public class MyContextFactory : IDesignTimeDbContextFactory<MyContext> {
        public MyContext CreateDbContext(string[] args) {
            var Builder = new DbContextOptionsBuilder<MyContext>()
                .UseSqlServer()
                ;

            var Options = Builder.Options;

            return new MyContext(Options);
        }
    }

    public class MyContext : DbContext {
        public MyContext(DbContextOptions options) : base(options) {
        }

        public DbSet<Class1> MyClasses => this.Set<Class1>();

        protected override void OnModelCreating(ModelBuilder modelBuilder) {
            base.OnModelCreating(modelBuilder);

            modelBuilder.Entity<Class1>()
                .Property(x => x.Enum)
                //.HasConversion<string>()
                .HasConversion(Test.SafeStringToEnumConverter<MidpointRounding>.Default)
                ;

        }

    }

    public class Class1 {
        public long Id { get; set; }
        public string Name { get; set; }
        public MidpointRounding Enum { get; set; }

    }

    internal static class Test {

        internal class SafeStringToEnumConverter<TEnum> : ValueConverter<TEnum, string> where TEnum : struct {

            public static SafeStringToEnumConverter<TEnum> Default { get; } = new();

            public SafeStringToEnumConverter(TEnum UnknownValue = default, ConverterMappingHints? mappingHints = default) : base(ConvertToString(), ConvertToEnum(UnknownValue), mappingHints) {

            }

            protected static Expression<Func<TEnum, string>> ConvertToString() {
                return v => v.ToString()!;
            }

            protected static Expression<Func<string, TEnum>> ConvertToEnum(TEnum UnknownValue) {
                return v => ConvertToEnum(v, UnknownValue);
            }

            public static TEnum ConvertToEnum(string value, TEnum UnknownValue) {

                if (Enum.TryParse<TEnum>(value, out var result)) {
                    return result;
                } else if (Enum.TryParse(value, true, out result)) {
                    return result;
                } else if (ulong.TryParse(value, out var ulongValue)) {
                    return (TEnum)(object)ulongValue;
                } else if (long.TryParse(value, out var longValue)) {
                    return (TEnum)(object)longValue;
                } else {
                    return UnknownValue;
                }

            }
        }
    }
}

Include verbose output

Errors in the code generated will look like this: (string v) => Test.SafeStringToEnumConverter`1.ConvertToEnum(v, MidpointRounding.ToEven)),

Specifically, notice that it uses SafeStringToEnumConverter`1 and not the full name of the type.

Include provider and version information

EF Core version: 8.0.4 Database provider: Microsoft.EntityFrameworkCore.SqlServer Target framework: NET 8.0 Operating system: WIN IDE: 17.9.7