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

Support HasConversion to primitive collection types #33688

Open
PascalArdex opened this issue May 8, 2024 · 2 comments
Open

Support HasConversion to primitive collection types #33688

PascalArdex opened this issue May 8, 2024 · 2 comments

Comments

@PascalArdex
Copy link

Description

In the Entities we use Enum types a lot and we make sure to store them as strings instead of numbers
For this we use .HasConversion<string>(); and it works great.

Now we would like to also support collections of Enums and have them serialized as text[] for consistency
And you would think this is a job for EF Core 8 Primitive Collections and we are going to be able to simply use .HasConversion<string[]>(); but no luck.

Example

Entity

public class Rider
{
    public Guid Id { get; set; }
    public List<EquineBeast> Mounts { get; set; }
}

public enum EquineBeast
{
    Donkey,
    Mule,
    Horse,
    Unicorn
}

I will include the full code because we configure all Enum based entities at once

OnModelCreating

    private void SaveEnumAsString(ModelBuilder modelBuilder)
    {
        var entityTypes = modelBuilder.Model.GetEntityTypes();

        foreach (var entityType in entityTypes)
        {
            var enumProperties = entityType.ClrType.GetProperties()
                .Where(p => p.PropertyType.IsEnum
                    || (p.PropertyType.IsGenericType
                        && p.PropertyType.GetGenericTypeDefinition() == typeof(List<>)
                        && p.PropertyType.GetGenericArguments()[0].IsEnum));

            foreach (var enumProperty in enumProperties)
            {
                if(enumProperty.PropertyType.IsEnum)
                {
                    modelBuilder
                        .Entity(entityType.ClrType)
                        .Property(enumProperty.Name)
                        .HasConversion<string>();
                }
                else
                {
                    modelBuilder
                        .Entity(entityType.ClrType)
                        .Property(enumProperty.Name)
                        .HasConversion<string[]>();
                }
            }
        }
    }

Problem

Now here is the odd part.
When we create the migration EF Core is stuck with int[]

            migrationBuilder.CreateTable(
                name: "riders",
                columns: table => new
                {
                    id = table.Column<Guid>(type: "uuid", nullable: false),
                    mount = table.Column<int[]>(type: "integer[]", nullable: false)
                },
                constraints: table =>
                {
                    table.PrimaryKey("pk_riders", x => x.id);
                });

We can force the column type with .HasColumnType("text[]") but it is creating more problems down the path.
Namely the migration will apply but at runtime fails to load/convert the field

Exception has occurred: CLR/System.InvalidCastException
Exception thrown: 'System.InvalidCastException' in Microsoft.EntityFrameworkCore.Relational.dll: 'Reading as 'System.Int32[]' is not supported for fields having DataTypeName 'text[]''

Version information

EF Core version: Microsoft.EntityFrameworkCore.Design 8.0.0
Database provider: Npgsql.EntityFrameworkCore.PostgreSQL 8.0.0
Target framework: net8.0

@ajcvickers
Copy link
Member

@PascalArdex You should not need all the code you have when dealing with non-collections of enums. Instead, this should work:

protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
    configurationBuilder.Properties<EquineBeast>().HaveConversion<string>();
}

However, this is not yet supported for primitive collections--this is tracked by #31413. Instead you should be able to adapt the code you already have to configure the element type using this pattern:

modelBuilder.Entity<Rider>().PrimitiveCollection(e => e.Mounts).ElementType().HasConversion<string>();

@ajcvickers ajcvickers changed the title Save collections of Enums as array of strings Support HasConversion to primitive collection types May 8, 2024
@ajcvickers ajcvickers self-assigned this May 8, 2024
@ajcvickers ajcvickers added this to the Backlog milestone May 8, 2024
@PascalArdex
Copy link
Author

It works, thanks for your help.
Thanks for pointing out that we can configure entities individually (and the syntax is much simpler).
Unfortunately we take the save enums as string for granted and too often we just forget to add the line ;-)

Here is the final code if anyone stumble into this

    private void SaveEnumAsString(ModelBuilder modelBuilder)
    {
        var entityTypes = modelBuilder.Model.GetEntityTypes();

        foreach (var entityType in entityTypes)
        {
            var enumProperties = entityType.ClrType.GetProperties()
                .Where(p => p.PropertyType.IsEnum
                    || (p.PropertyType.IsGenericType
                        && p.PropertyType.GetGenericTypeDefinition() == typeof(List<>)
                        && p.PropertyType.GetGenericArguments()[0].IsEnum));

            foreach (var enumProperty in enumProperties)
            {
                if(enumProperty.PropertyType.IsEnum)
                {
                    modelBuilder
                        .Entity(entityType.ClrType)
                        .Property(enumProperty.Name)
                        .HasConversion<string>();
                }
                else
                {
                    modelBuilder
                        .Entity(entityType.ClrType)
                        .PrimitiveCollection(enumProperty.Name)
                        .ElementType()
                        .HasConversion<string>();
                }
            }
        }
    }

@ajcvickers ajcvickers reopened this May 9, 2024
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

2 participants