Search code examples
entity-frameworknpgsqlef-core-2.2

EF Core In-Memmory Array mapping


I have a string array column in database

ALTER TABLE sample ADD languages VARCHAR[] NULL;

which is mapped to model:

public string[] languages { get; set; }

Using EF Core with PostgreSQL (Npgsql.EntityFrameworkCore.PostgreSQL 2.2.0) works out of the box.

However, when I want to test DbContext using In-Memmory (Microsoft.EntityFrameworkCore.InMemory 2.2.4) I got this error:

System.InvalidOperationException: The property 'sample.languages' could not be mapped, because it is of type 'string[]' which is not a supported primitive type or a valid entity type. Either explicitly map this property, or ignore it using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.

So, adding this to model builder configuration solves the In-Memmory error:

 builder.Property(p => p.languages)
                    .HasConversion(
                        v => string.Join("'", v),
                        v => v.Split(',', StringSplitOptions.RemoveEmptyEntries));

But raises new one (Npgsql):

Can't cast database type character varying[] to String at Npgsql.NpgsqlDefaultDataReader.GetFieldValue[T](Int32 column)

Do you have any hints please?


Solution

  • Not many databases support array type columns out of the box. Actually PostgreSQL might be the only one from the currently supported databases which does that. According to the original exception, apparently in-memory database also doesn't support it (currently).

    EF Core builds a separate model for each database type. Each database provider exposes an extension method of DatabaseFacade like IsSqlServer(), IsNpgsql(), IsInMemory() etc. which can be used inside OnModelCreating to conditionally configure different mappings for a specific database type(s) if needed.

    The intended usage is like this:

    modelBuilder.Entity<Sample>(builder =>
    {
        if (!Database.IsNpgsql()) // <--
        {
             builder.Property(p => p.languages)
                 .HasConversion(
                     v => string.Join("'", v),
                     v => v.Split(',', StringSplitOptions.RemoveEmptyEntries));    
        }
    });
    

    If you are using separate classes implementing IEntityTypeConfiguration<TEntity> for configuring the model, then you should pass the Database to their constructor and store in a class member, because the EntityTypeBuilder<TEntity> passed to the Configure method does not provide such information.