Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

"ef dbcontext optimize --nativeaot" generates uncompilable code for arrays of enums #35767

Open
Dreamescaper opened this issue Mar 11, 2025 · 0 comments

Comments

@Dreamescaper
Copy link

Dreamescaper commented Mar 11, 2025

Bug description

I use enums with PostgresDB. In one of model types I have an array of enum values.
An attempt to run "optimize --nativeaot" results in "A lambda expression with a statement body cannot be converted to an expression tree" compiler error.

Your code

namespace Repro;

public class TestModel
{
    public Guid Id { get; set; }
    public required TestEnum[] TestEnum { get; set; }
}

public enum TestEnum { A, B, C }
Generated TestModelEntityType
// <auto-generated />
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.ChangeTracking.Internal;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Storage.Json;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping;
using OnlineLessons.ApiService.Models;

#pragma warning disable 219, 612, 618
#nullable disable

namespace Repro
{
    [EntityFrameworkInternal]
    public partial class TestModelEntityType
    {
        public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType baseEntityType = null)
        {
            var runtimeEntityType = model.AddEntityType(
                "OnlineLessons.ApiService.Models.TestModel",
                typeof(TestModel),
                baseEntityType,
                propertyCount: 2,
                keyCount: 1);

            var id = runtimeEntityType.AddProperty(
                "Id",
                typeof(Guid),
                propertyInfo: typeof(TestModel).GetProperty("Id", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly),
                fieldInfo: typeof(TestModel).GetField("<Id>k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly),
                valueGenerated: ValueGenerated.OnAdd,
                afterSaveBehavior: PropertySaveBehavior.Throw,
                sentinel: new Guid("00000000-0000-0000-0000-000000000000"));
            id.SetGetter(
                Guid (TestModel entity) => TestModelUnsafeAccessors.Id(entity),
                bool (TestModel entity) => TestModelUnsafeAccessors.Id(entity) == new Guid("00000000-0000-0000-0000-000000000000"),
                Guid (TestModel instance) => TestModelUnsafeAccessors.Id(instance),
                bool (TestModel instance) => TestModelUnsafeAccessors.Id(instance) == new Guid("00000000-0000-0000-0000-000000000000"));
            id.SetSetter(
                (TestModel entity, Guid value) => TestModelUnsafeAccessors.Id(entity) = value);
            id.SetMaterializationSetter(
                (TestModel entity, Guid value) => TestModelUnsafeAccessors.Id(entity) = value);
            id.SetAccessors(
                Guid (InternalEntityEntry entry) => (entry.FlaggedAsStoreGenerated(0) ? entry.ReadStoreGeneratedValue<Guid>(0) : (entry.FlaggedAsTemporary(0) && TestModelUnsafeAccessors.Id(((TestModel)(entry.Entity))) == new Guid("00000000-0000-0000-0000-000000000000") ? entry.ReadTemporaryValue<Guid>(0) : TestModelUnsafeAccessors.Id(((TestModel)(entry.Entity))))),
                Guid (InternalEntityEntry entry) => TestModelUnsafeAccessors.Id(((TestModel)(entry.Entity))),
                Guid (InternalEntityEntry entry) => entry.ReadOriginalValue<Guid>(id, 0),
                Guid (InternalEntityEntry entry) => entry.ReadRelationshipSnapshotValue<Guid>(id, 0),
                object (ValueBuffer valueBuffer) => valueBuffer[0]);
            id.SetPropertyIndexes(
                index: 0,
                originalValueIndex: 0,
                shadowIndex: -1,
                relationshipIndex: 0,
                storeGenerationIndex: 0);
            id.TypeMapping = GuidTypeMapping.Default.Clone(
                comparer: new ValueComparer<Guid>(
                    bool (Guid v1, Guid v2) => v1 == v2,
                    int (Guid v) => ((object)v).GetHashCode(),
                    Guid (Guid v) => v),
                keyComparer: new ValueComparer<Guid>(
                    bool (Guid v1, Guid v2) => v1 == v2,
                    int (Guid v) => ((object)v).GetHashCode(),
                    Guid (Guid v) => v),
                providerValueComparer: new ValueComparer<Guid>(
                    bool (Guid v1, Guid v2) => v1 == v2,
                    int (Guid v) => ((object)v).GetHashCode(),
                    Guid (Guid v) => v),
                mappingInfo: new RelationalTypeMappingInfo(
                    storeTypeName: "uuid"));
            id.SetCurrentValueComparer(new EntryCurrentValueComparer<Guid>(id));
            id.AddAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.None);

            var testEnum = runtimeEntityType.AddProperty(
                "TestEnum",
                typeof(TestEnum[]),
                propertyInfo: typeof(TestModel).GetProperty("TestEnum", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly),
                fieldInfo: typeof(TestModel).GetField("<TestEnum>k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly));
            testEnum.SetGetter(
                TestEnum[] (TestModel entity) => TestModelUnsafeAccessors.TestEnum(entity),
                bool (TestModel entity) => TestModelUnsafeAccessors.TestEnum(entity) == null,
                TestEnum[] (TestModel instance) => TestModelUnsafeAccessors.TestEnum(instance),
                bool (TestModel instance) => TestModelUnsafeAccessors.TestEnum(instance) == null);
            testEnum.SetSetter(
                (TestModel entity, TestEnum[] value) => TestModelUnsafeAccessors.TestEnum(entity) = value);
            testEnum.SetMaterializationSetter(
                (TestModel entity, TestEnum[] value) => TestModelUnsafeAccessors.TestEnum(entity) = value);
            testEnum.SetAccessors(
                TestEnum[] (InternalEntityEntry entry) => TestModelUnsafeAccessors.TestEnum(((TestModel)(entry.Entity))),
                TestEnum[] (InternalEntityEntry entry) => TestModelUnsafeAccessors.TestEnum(((TestModel)(entry.Entity))),
                TestEnum[] (InternalEntityEntry entry) => entry.ReadOriginalValue<TestEnum[]>(testEnum, 1),
                TestEnum[] (InternalEntityEntry entry) => entry.GetCurrentValue<TestEnum[]>(testEnum),
                object (ValueBuffer valueBuffer) => valueBuffer[1]);
            testEnum.SetPropertyIndexes(
                index: 1,
                originalValueIndex: 1,
                shadowIndex: -1,
                relationshipIndex: -1,
                storeGenerationIndex: -1);
            testEnum.TypeMapping = NpgsqlArrayTypeMapping<TestEnum[], TestEnum[], TestEnum>.Default.Clone(
                comparer: new ListOfValueTypesComparer<TestEnum[], TestEnum>(new ValueComparer<TestEnum>(
                    bool (TestEnum v1, TestEnum v2) => object.Equals(((object)(v1)), ((object)(v2))),
                    int (TestEnum v) => ((object)v).GetHashCode(),
                    TestEnum (TestEnum v) => v)),
                keyComparer: new ValueComparer<TestEnum[]>(
                    bool (TestEnum[] v1, TestEnum[] v2) => StructuralComparisons.StructuralEqualityComparer.Equals(((object)(v1)), ((object)(v2))),
                    int (TestEnum[] v) => StructuralComparisons.StructuralEqualityComparer.GetHashCode(((object)(v))),
                    TestEnum[] (TestEnum[] source) => source.ToArray()),
                providerValueComparer: new ValueComparer<int[]>(
                    bool (int[] v1, int[] v2) => StructuralComparisons.StructuralEqualityComparer.Equals(((object)(v1)), ((object)(v2))),
                    int (int[] v) => StructuralComparisons.StructuralEqualityComparer.GetHashCode(((object)(v))),
                    int[] (int[] source) => source.ToArray()),
                mappingInfo: new RelationalTypeMappingInfo(
                    storeTypeName: "integer[]"),
                converter: new ValueConverter<TestEnum[], int[]>(
                    int[] (TestEnum[] input) =>
                    {
                        if (input == null)
                        {
                            return null;
                        }
                        else
                        {
                            var length = input.Length;
                            var result = new int[length];
                            {
                                var i = 0;
                                {
                                    while (true)
                                    {
                                        if (i < length)
                                        {
                                            var value = input[i];
                                            result[i] = ((int)(value));
                                            i += 1;
                                        }
                                        else
                                        {
                                            goto LoopBreak;
                                        }
                                    }

                                    LoopBreak:
                                        ;
                                }
                            }

                            return result;
                        }
                    },
                    TestEnum[] (int[] input) =>
                    {
                        if (input == null)
                        {
                            return null;
                        }
                        else
                        {
                            var length = input.Length;
                            var result = new TestEnum[length];
                            {
                                var i = 0;
                                {
                                    while (true)
                                    {
                                        if (i < length)
                                        {
                                            var value = input[i];
                                            result[i] = ((TestEnum)(value));
                                            i += 1;
                                        }
                                        else
                                        {
                                            goto LoopBreak;
                                        }
                                    }

                                    LoopBreak:
                                        ;
                                }
                            }

                            return result;
                        }
                    }),
                jsonValueReaderWriter: new JsonCollectionOfStructsReaderWriter<TestEnum[], TestEnum>(
                    new JsonConvertedValueReaderWriter<TestEnum, int>(
                        JsonInt32ReaderWriter.Instance,
                        new ValueConverter<TestEnum, int>(
                            int (TestEnum value) => ((int)(value)),
                            TestEnum (int value) => ((TestEnum)(value))))),
                elementMapping: IntTypeMapping.Default.Clone(
                    comparer: new ValueComparer<TestEnum>(
                        bool (TestEnum v1, TestEnum v2) => object.Equals(((object)(v1)), ((object)(v2))),
                        int (TestEnum v) => ((object)v).GetHashCode(),
                        TestEnum (TestEnum v) => v),
                    keyComparer: new ValueComparer<TestEnum>(
                        bool (TestEnum v1, TestEnum v2) => object.Equals(((object)(v1)), ((object)(v2))),
                        int (TestEnum v) => ((object)v).GetHashCode(),
                        TestEnum (TestEnum v) => v),
                    providerValueComparer: new ValueComparer<int>(
                        bool (int v1, int v2) => v1 == v2,
                        int (int v) => v,
                        int (int v) => v),
                    mappingInfo: new RelationalTypeMappingInfo(
                        storeTypeName: "integer"),
                    converter: new ValueConverter<TestEnum, int>(
                        int (TestEnum value) => ((int)(value)),
                        TestEnum (int value) => ((TestEnum)(value))),
                    jsonValueReaderWriter: new JsonConvertedValueReaderWriter<TestEnum, int>(
                        JsonInt32ReaderWriter.Instance,
                        new ValueConverter<TestEnum, int>(
                            int (TestEnum value) => ((int)(value)),
                            TestEnum (int value) => ((TestEnum)(value))))));
            testEnum.AddAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.None);

            var key = runtimeEntityType.AddKey(
                new[] { id });
            runtimeEntityType.SetPrimaryKey(key);

            return runtimeEntityType;
        }

        public static void CreateAnnotations(RuntimeEntityType runtimeEntityType)
        {
            var id = runtimeEntityType.FindProperty("Id");
            var testEnum = runtimeEntityType.FindProperty("TestEnum");
            var key = runtimeEntityType.FindKey(new[] { id });
            key.SetPrincipalKeyValueFactory(KeyValueFactoryFactory.CreateSimpleNonNullableFactory<Guid>(key));
            key.SetIdentityMapFactory(IdentityMapFactoryFactory.CreateFactory<Guid>(key));
            runtimeEntityType.SetOriginalValuesFactory(
                ISnapshot (InternalEntityEntry source) =>
                {
                    var entity = ((TestModel)(source.Entity));
                    return ((ISnapshot)(new Snapshot<Guid, TestEnum[]>(((ValueComparer<Guid>)(((IProperty)id).GetValueComparer())).Snapshot(source.GetCurrentValue<Guid>(id)), (((IEnumerable<TestEnum>)(source.GetCurrentValue<TestEnum[]>(testEnum))) == null ? null : ((TestEnum[])(((ValueComparer<IEnumerable<TestEnum>>)(((IProperty)testEnum).GetValueComparer())).Snapshot(((IEnumerable<TestEnum>)(source.GetCurrentValue<TestEnum[]>(testEnum))))))))));
                });
            runtimeEntityType.SetStoreGeneratedValuesFactory(
                ISnapshot () => ((ISnapshot)(new Snapshot<Guid>(((ValueComparer<Guid>)(((IProperty)id).GetValueComparer())).Snapshot(default(Guid))))));
            runtimeEntityType.SetTemporaryValuesFactory(
                ISnapshot (InternalEntityEntry source) => ((ISnapshot)(new Snapshot<Guid>(default(Guid)))));
            runtimeEntityType.SetShadowValuesFactory(
                ISnapshot (IDictionary<string, object> source) => Snapshot.Empty);
            runtimeEntityType.SetEmptyShadowValuesFactory(
                ISnapshot () => Snapshot.Empty);
            runtimeEntityType.SetRelationshipSnapshotFactory(
                ISnapshot (InternalEntityEntry source) =>
                {
                    var entity = ((TestModel)(source.Entity));
                    return ((ISnapshot)(new Snapshot<Guid>(((ValueComparer<Guid>)(((IProperty)id).GetKeyValueComparer())).Snapshot(source.GetCurrentValue<Guid>(id)))));
                });
            runtimeEntityType.Counts = new PropertyCounts(
                propertyCount: 2,
                navigationCount: 0,
                complexPropertyCount: 0,
                originalValueCount: 2,
                shadowCount: 0,
                relationshipCount: 1,
                storeGeneratedCount: 1);
            runtimeEntityType.AddAnnotation("Relational:FunctionName", null);
            runtimeEntityType.AddAnnotation("Relational:Schema", null);
            runtimeEntityType.AddAnnotation("Relational:SqlQuery", null);
            runtimeEntityType.AddAnnotation("Relational:TableName", "TestModel");
            runtimeEntityType.AddAnnotation("Relational:ViewName", null);
            runtimeEntityType.AddAnnotation("Relational:ViewSchema", null);

            Customize(runtimeEntityType);
        }

        static partial void Customize(RuntimeEntityType runtimeEntityType);
    }
}

EF Core version

9.0.3

Database provider

Npgsql.EntityFrameworkCore.PostgreSQL

Target framework

.NET 9

Operating system

No response

IDE

No response

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant