You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
NpgsqlArrayConverter.ArrayConversionExpression throws IndexOutOfRangeException when using a custom derived type for the collection #3092
When using a custom derived class for a collection, the ArrayConversionExpression method throws an IndexOutOfRangeException when it attempts to get the (missing) generic type argument.
publicclassTestEntity(Guidid,CustomCollectionvalues){publicGuidId{get;}=id;publicCustomCollectionValues{get;}=values;}publicclassCustomCollection:ReadOnlyCollection<string>{publicCustomCollection(string[]strings):base(strings){if(strings.Length==0)thrownewArgumentException("At least one string must be provided",nameof(strings));}publicboolSomeBusinessLogic()=>this.Any(s =>s.Contains("foo"));}publicclassTestDbContext:DbContext{publicDbSet<TestEntity>TestEntities{get;set;}=default!;publicTestDbContext(DbContextOptions<TestDbContext>options):base(options){}protectedoverridevoidOnModelCreating(ModelBuildermodelBuilder){modelBuilder.Entity<TestEntity>().ToTable("test_entities");modelBuilder.Entity<TestEntity>().Property(x =>x.Id).HasColumnName("id");modelBuilder.Entity<TestEntity>().PrimitiveCollection(x =>x.Values).HasColumnName("values").ElementType().HasConversion(typeof(CustomCollectionConversion));}}
I think this could be resolved by getting the generic type arguments of the base class if TInput doesn't have any:
I can't reproduce the precise error, probably because the code above is partial (e.g. CustomCollectionConversion is missing); when reporting issues, please always include a fully runnable, minimal code sample so that nothing is missing.
What I do get is the following exception:
/Users/roji/projects/test/bin/Debug/net8.0/Test
Unhandled exception. System.InvalidOperationException: Converter for element type 'CustomCollection' cannot be used for 'TestEntity.Values' because its element type is 'string'.
at Microsoft.EntityFrameworkCore.Metadata.Internal.ElementType.SetValueConverter(ValueConverter converter, ConfigurationSource configurationSource)
at Microsoft.EntityFrameworkCore.Metadata.Internal.ElementType.SetValueConverter(Type converterType, ConfigurationSource configurationSource)
at Microsoft.EntityFrameworkCore.Metadata.Internal.InternalElementTypeBuilder.HasConverter(Type converterType, ConfigurationSource configurationSource)
at Microsoft.EntityFrameworkCore.Metadata.Builders.ElementTypeBuilder.HasConversion(Type conversionType)
at BlogContext.OnModelCreating(ModelBuilder modelBuilder) in /Users/roji/projects/test/Program.cs:line 42
This error makes sense: you're doing HasConversion on the element type, rather than on the collection as a whole. changing the configuration to the following makes the program run without errors:
awaitusingvarctx=newBlogContext();awaitctx.Database.EnsureDeletedAsync();awaitctx.Database.EnsureCreatedAsync();publicclassBlogContext:DbContext{publicDbSet<TestEntity>TestEntities{get;set;}=default!;protectedoverridevoidOnConfiguring(DbContextOptionsBuilderoptionsBuilder)=>optionsBuilder.UseNpgsql("Host=localhost;Username=test;Password=test").LogTo(Console.WriteLine,LogLevel.Information).EnableSensitiveDataLogging();protectedoverridevoidOnModelCreating(ModelBuildermodelBuilder){modelBuilder.Entity<TestEntity>().ToTable("test_entities");modelBuilder.Entity<TestEntity>().Property(x =>x.Id).HasColumnName("id");modelBuilder.Entity<TestEntity>().PrimitiveCollection(x =>x.Values).HasColumnName("values").Metadata.SetValueConverter(typeof(CustomCollectionConversion));// modelBuilder.Entity<TestEntity>()// .PrimitiveCollection(x => x.Values)// .HasColumnName("values")// .ElementType()// .HasConversion(typeof(CustomCollectionConversion));}publicclassCustomCollectionConversion():ValueConverter<CustomCollection,string[]>(
c =>c.ToArray(),
a =>newCustomCollection(a));}publicclassBlog{publicintId{get;set;}publicstringName{get;set;}}publicclassTestEntity(Guidid,CustomCollectionvalues){publicGuidId{get;}=id;publicCustomCollectionValues{get;}=values;}publicclassCustomCollection:ReadOnlyCollection<string>{publicCustomCollection(string[]strings):base(strings){if(strings.Length==0)thrownewArgumentException("At least one string must be provided",nameof(strings));}publicboolSomeBusinessLogic()=>this.Any(s =>s.Contains("foo"));}
If this doesn't fix your problem in some way, can you please share a full, runnable repro (like the code just above) that shows the problem?
Apologies, didn't realise I'd left it off. My CustomCollection converter simply converted from string to string.
publicclassCustomCollectionConversion():ValueConverter<string,string>(
v =>v,
v =>v);
Since using .HasConversion<string>() caused the following exception:
Unhandled exception. System.InvalidCastException: Unable to cast object of type 'System.String[]' to type 'NpgsqlArrayConversion.CustomCollection'.
at lambda_method13(Closure, InternalEntityEntry)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.OriginalValues..ctor(InternalEntityEntry entry)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.EnsureOriginalValues()
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntrySubscriber.SnapshotAndSubscribe(InternalEntityEntry entry)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.StartTracking(InternalEntityEntry entry)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetEntityState(EntityState oldState, EntityState newState, Boolean acceptChanges, Boolean modifyProper
ties)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetEntityState(EntityState entityState, Boolean acceptChanges, Boolean modifyProperties, Nullable`1 fo
rceStateWhenUnknownKey, Nullable`1 fallbackState)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityGraphAttacher.PaintAction(EntityEntryGraphNode`1 node)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityEntryGraphIterator.TraverseGraph[TState](EntityEntryGraphNode`1 node, Func`2 handleNode)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityGraphAttacher.AttachGraph(InternalEntityEntry rootEntry, EntityState targetState, EntityState storeGeneratedWithKeyS
etTargetState, Boolean forceStateWhenUnknownKey)
at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.SetEntityState(InternalEntityEntry entry, EntityState entityState)
at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.Add(TEntity entity)
Here's my full code:
Full repro of above
Model.cs
usingSystem.Collections.ObjectModel;usingMicrosoft.EntityFrameworkCore;usingMicrosoft.EntityFrameworkCore.Design;usingMicrosoft.EntityFrameworkCore.Storage.ValueConversion;namespaceNpgsqlArrayConversion;publicclassTestEntity(Guidid,CustomCollectionvalues){publicGuidId{get;}=id;publicCustomCollectionValues{get;}=values;privateTestEntity():this(default,default!){}}publicclassCustomCollection:ReadOnlyCollection<string>{publicCustomCollection(string[]strings):base(strings){if(strings.Length==0)thrownewArgumentException("At least one string must be provided",nameof(strings));}publicboolSomeBusinessLogic()=>this.Any(s =>s.Contains("foo"));}publicclassTestDbContext:DbContext{publicDbSet<TestEntity>TestEntities{get;set;}=default!;publicTestDbContext(DbContextOptions<TestDbContext>options):base(options){}protectedoverridevoidOnModelCreating(ModelBuildermodelBuilder){modelBuilder.Entity<TestEntity>().ToTable("test_entities");modelBuilder.Entity<TestEntity>().Property(x =>x.Id).HasColumnName("id");modelBuilder.Entity<TestEntity>().PrimitiveCollection(x =>x.Values).HasColumnName("values").ElementType().HasConversion(typeof(CustomCollectionConversion));}}publicclassCustomCollectionConversion():ValueConverter<string,string>(
v =>v,
v =>v);publicclassTestDbContextFactory:IDesignTimeDbContextFactory<TestDbContext>{publicTestDbContextCreateDbContext(string[]args){varoptionsBuilder=newDbContextOptionsBuilder<TestDbContext>();optionsBuilder.UseNpgsql("Host=localhost;Database=postgres;Username=postgres;Password=password");returnnewTestDbContext(optionsBuilder.Options);}}
Program.cs
usingNpgsqlArrayConversion;usingvardb=newTestDbContextFactory().CreateDbContext(Array.Empty<string>());Console.WriteLine("Inserting a new item");varstrings=new[]{"One","Two"};db.TestEntities.Add(newTestEntity(Guid.NewGuid(),newCustomCollection(strings)));db.SaveChanges();Console.WriteLine("Saved");
Also worth noting that in my real-world use case, I've got a wrapper class for my strings:
Repro of real-world case
Model.cs
usingSystem.Collections.ObjectModel;usingMicrosoft.EntityFrameworkCore;usingMicrosoft.EntityFrameworkCore.Design;usingMicrosoft.EntityFrameworkCore.Storage.ValueConversion;namespaceNpgsqlArrayConversion;publicclassTestEntity(Guidid,CustomCollectionvalues){publicGuidId{get;}=id;publicCustomCollectionValues{get;}=values;privateTestEntity():this(default,default!){}}publicclassCustomCollection:ReadOnlyCollection<StringWrapper>{publicCustomCollection(string[]strings):base(strings.Select(StringWrapper.FromString).ToList()){if(strings.Length==0)thrownewArgumentException("At least one string must be provided",nameof(strings));}publicboolSomeBusinessLogic()=>this.Any(s =>s.Value.Contains("foo"));}publicclassStringWrapper{publicstringValue{get;}privateStringWrapper(stringvalue)=>Value=value;publicstaticStringWrapperFromString(stringvalue)=>new(value);}publicclassTestDbContext:DbContext{publicDbSet<TestEntity>TestEntities{get;set;}=default!;publicTestDbContext(DbContextOptions<TestDbContext>options):base(options){}protectedoverridevoidOnModelCreating(ModelBuildermodelBuilder){modelBuilder.Entity<TestEntity>().ToTable("test_entities");modelBuilder.Entity<TestEntity>().Property(x =>x.Id).HasColumnName("id");modelBuilder.Entity<TestEntity>().PrimitiveCollection(x =>x.Values).HasColumnName("values").ElementType().HasConversion(typeof(StringWrapperConverter));}}publicclassStringWrapperConverter():ValueConverter<StringWrapper,string>(
v =>v.Value,
v =>StringWrapper.FromString(v));publicclassTestDbContextFactory:IDesignTimeDbContextFactory<TestDbContext>{publicTestDbContextCreateDbContext(string[]args){varoptionsBuilder=newDbContextOptionsBuilder<TestDbContext>();optionsBuilder.UseNpgsql("Host=localhost;Database=postgres;Username=postgres;Password=password");returnnewTestDbContext(optionsBuilder.Options);}}
Program.cs
usingNpgsqlArrayConversion;usingvardb=newTestDbContextFactory().CreateDbContext(Array.Empty<string>());Console.WriteLine("Inserting a new item");varstrings=new[]{"One","Two"};db.TestEntities.Add(newTestEntity(Guid.NewGuid(),newCustomCollection(strings)));db.SaveChanges();Console.WriteLine("Saved");
Using Microsoft.EntityFrameworkCore 8.0.2, and Ngpsql.EntityFrameworkCore.PostgreSQL 8.0.2
When I try the SetValueConverter implementation:
SetValueConverter implementation
Model.cs
usingSystem.Collections.ObjectModel;usingMicrosoft.EntityFrameworkCore;usingMicrosoft.EntityFrameworkCore.Design;usingMicrosoft.EntityFrameworkCore.Storage.ValueConversion;namespaceNpgsqlArrayConversion;publicclassTestEntity(Guidid,CustomCollectionvalues){publicGuidId{get;}=id;publicCustomCollectionValues{get;}=values;privateTestEntity():this(default,default!){}}publicclassCustomCollection:ReadOnlyCollection<StringWrapper>{publicCustomCollection(string[]strings):base(strings.Select(StringWrapper.FromString).ToList()){if(strings.Length==0)thrownewArgumentException("At least one string must be provided",nameof(strings));}publicboolSomeBusinessLogic()=>this.Any(s =>s.Value.Contains("foo"));}publicclassStringWrapper{publicstringValue{get;}privateStringWrapper(stringvalue)=>Value=value;publicstaticStringWrapperFromString(stringvalue)=>new(value);}publicclassTestDbContext:DbContext{publicDbSet<TestEntity>TestEntities{get;set;}=default!;publicTestDbContext(DbContextOptions<TestDbContext>options):base(options){}protectedoverridevoidOnModelCreating(ModelBuildermodelBuilder){modelBuilder.Entity<TestEntity>().ToTable("test_entities");modelBuilder.Entity<TestEntity>().Property(x =>x.Id).HasColumnName("id");modelBuilder.Entity<TestEntity>().PrimitiveCollection(x =>x.Values).HasColumnName("values").Metadata.SetValueConverter(typeof(CustomCollectionConverter));}}publicclassCustomCollectionConverter():ValueConverter<CustomCollection,string[]>(
c =>c.Select(x =>x.Value).ToArray(),
a =>newCustomCollection(a));publicclassTestDbContextFactory:IDesignTimeDbContextFactory<TestDbContext>{publicTestDbContextCreateDbContext(string[]args){varoptionsBuilder=newDbContextOptionsBuilder<TestDbContext>();optionsBuilder.UseNpgsql("Host=localhost;Database=postgres;Username=postgres;Password=password");returnnewTestDbContext(optionsBuilder.Options);}}
Program.cs is the same as above
I get the following exception:
Unhandled exception. System.InvalidOperationException: The 'CustomCollection' property 'TestEntity.Values' could not be mapped because the database provider does not support this typ
e. Consider converting the property value to a type supported by the database using a value converter. See https://aka.ms/efcore-docs-value-converters for more information. Alternate
ly, exclude the property from the model using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.
at Microsoft.EntityFrameworkCore.Infrastructure.ModelValidator.ThrowPropertyNotMappedException(String propertyType, IConventionTypeBase typeBase, IConventionProperty unmappedPrope
rty)
at Microsoft.EntityFrameworkCore.Infrastructure.RelationalModelValidator.ThrowPropertyNotMappedException(String propertyType, IConventionTypeBase typeBase, IConventionProperty unm
appedProperty)
at Microsoft.EntityFrameworkCore.Infrastructure.ModelValidator.<ValidatePropertyMapping>g__Validate|7_0(IConventionTypeBase typeBase, <>c__DisplayClass7_0&)
at Microsoft.EntityFrameworkCore.Infrastructure.ModelValidator.ValidatePropertyMapping(IModel model, IDiagnosticsLogger`1 logger)
at Microsoft.EntityFrameworkCore.Infrastructure.ModelValidator.Validate(IModel model, IDiagnosticsLogger`1 logger)
at Microsoft.EntityFrameworkCore.Infrastructure.RelationalModelValidator.Validate(IModel model, IDiagnosticsLogger`1 logger)
at Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.Internal.NpgsqlModelValidator.Validate(IModel model, IDiagnosticsLogger`1 logger)
at Microsoft.EntityFrameworkCore.Infrastructure.ModelRuntimeInitializer.Initialize(IModel model, Boolean designTime, IDiagnosticsLogger`1 validationLogger)
at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.GetModel(DbContext context, ModelCreationDependencies modelCreationDependencies, Boolean designTime)
at Microsoft.EntityFrameworkCore.Internal.DbContextServices.CreateModel(Boolean designTime)
at Microsoft.EntityFrameworkCore.Internal.DbContextServices.get_Model()
at Microsoft.EntityFrameworkCore.Infrastructure.EntityFrameworkServicesBuilder.<>c.<TryAddCoreServices>b__8_4(IServiceProvider p)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitFactory(FactoryCallSite factoryCallSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope s
erviceProviderEngine, RuntimeResolverLock lockType)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope s
erviceProviderEngine, RuntimeResolverLock lockType)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope s
erviceProviderEngine, RuntimeResolverLock lockType)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope s
erviceProviderEngine, RuntimeResolverLock lockType)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope s
erviceProviderEngine, RuntimeResolverLock lockType)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope s
erviceProviderEngine, RuntimeResolverLock lockType)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine.<>c__DisplayClass2_0.<RealizeService>b__0(ServiceProviderEngineScope scope)
at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(ServiceIdentifier serviceIdentifier, ServiceProviderEngineScope serviceProviderEngineScope)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
at Microsoft.EntityFrameworkCore.DbContext.get_DbContextDependencies()
at Microsoft.EntityFrameworkCore.DbContext.get_ContextServices()
at Microsoft.EntityFrameworkCore.DbContext.get_InternalServiceProvider()
at Microsoft.EntityFrameworkCore.DbContext.get_DbContextDependencies()
at Microsoft.EntityFrameworkCore.DbContext.Microsoft.EntityFrameworkCore.Internal.IDbContextDependencies.get_StateManager()
at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.EntryWithoutDetectChanges(TEntity entity)
at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.Add(TEntity entity)
at Program.<Main>$(String[] args) in \NpgsqlArrayConversion\Program.cs:line 10
The below is an AI-generated analysis and may contain inaccuracies.
Confirmed; classified as a bug. The IndexOutOfRangeException reproduces with the reporter's identity element converter. It occurs during model initialization, before connecting to PostgreSQL, so neither seed data nor a database is needed. The StringWrapper variant also reproduces.
Versions and regression
Npgsql EF provider
EF Core
Model initialization
8.0.0
8.0.0
IndexOutOfRangeException
8.0.2
8.0.2
IndexOutOfRangeException
8.0.11
8.0.32
IndexOutOfRangeException
9.0.4
9.0.21
NotSupportedException: “Array value converter input type must be an IEnumerable, but is CustomCollection”
10.0.3
10.0.13
Same NotSupportedException
The minimized 8.0.2 repro was also verified on .NET 8. The cross-version comparisons used .NET 10.
This was not introduced in 8.0.2, since 8.0.0 already fails. No previously working version was established, so a regression from an earlier major version is not confirmed. The same PrimitiveCollection configuration cannot be tested unchanged on EF 7 because that API was introduced in EF 8.
Cause and provider comparison
CustomCollection implements IEnumerable<string> through its base class, but is not itself a generic type. In 8.0.2's converter, it falls through to typeof(TInput).GetGenericArguments()[0], indexing an empty array. In 10.0.3, the collection-handling branches still require the input type itself to be generic, so they reject it instead.
SQLite 8.0.2 initializes this same model successfully. Therefore, the reported converter/model-initialization failure is PostgreSQL-provider-specific; this issue should not simply be moved upstream.
There is also a separate, shared EF limitation in the follow-up: adding an entity with this read-only collection on SQLite 8.0.2 reproduces the InvalidCastException from string[] to CustomCollection during original-value snapshotting. SQLite 10.0.13 instead throws MissingMethodException because the collection has no parameterless constructor. Merely correcting element-type discovery will not make this read-only collection constructible and usable throughout tracking/materialization.
As a control, replacing the collection with List<string> successfully inserts and reloads the values on PostgreSQL with both 8.0.2 and 10.0.3. I could not validate the earlier whole-property CustomCollection → string[] conversion suggestion as a workaround for the StringWrapper variant: saving/tracking still fails, so successful model initialization alone is insufficient.
Related issues
Closest overlap: npgsql/efcore.pg#3082, supporting IList<T> implementations beyond array/List<T>. npgsql/efcore.pg#3413, already closed as its duplicate, reports the same 9.x converter rejection for a non-generic List<T> subclass. The read-only construction/snapshotting aspect here needs to be distinguished from supporting mutable custom lists.
Upstream: dotnet/efcore#32055 is a closed, related non-generic primitive-collection validation bug, but has a different failing component and exception. dotnet/efcore#31374 tracks comparer behavior for immutable/read-only primitive collections. Neither is an exact duplicate of the Npgsql converter failure.
npgsql/efcore.pg#3074 reaches the same old converter exception through migration defaults, rather than a custom collection property.
usingSystem.Collections.ObjectModel;usingMicrosoft.EntityFrameworkCore;usingMicrosoft.EntityFrameworkCore.Storage.ValueConversion;usingMicrosoft.Extensions.Logging;awaitusingvarcontext=newTestDbContext();_=context.Model;publicclassTestDbContext:DbContext{publicDbSet<TestEntity>TestEntities{get;set;}=null!;protectedoverridevoidOnConfiguring(DbContextOptionsBuilderoptionsBuilder)=>optionsBuilder.UseNpgsql("Host=localhost;Database=triage_3092").LogTo(Console.WriteLine,LogLevel.Information).EnableSensitiveDataLogging();protectedoverridevoidOnModelCreating(ModelBuildermodelBuilder){modelBuilder.Entity<TestEntity>().Property(x =>x.Id);modelBuilder.Entity<TestEntity>().PrimitiveCollection(x =>x.Values).ElementType().HasConversion<CustomCollectionConversion>();}}publicclassTestEntity(Guidid,CustomCollectionvalues){publicGuidId{get;}=id;publicCustomCollectionValues{get;}=values;privateTestEntity():this(default,null!){}}publicclassCustomCollection(string[]strings):ReadOnlyCollection<string>(strings);publicclassCustomCollectionConversion():ValueConverter<string,string>(v =>v, v =>v);
Run dotnet run. No database is contacted. The business-logic method and non-empty validation from the original collection are unnecessary to trigger the failure.
Activity
roji commentedon Feb 14, 2024
Note to self: see also #3074 which may be related.
roji commentedon Feb 17, 2024
I can't reproduce the precise error, probably because the code above is partial (e.g. CustomCollectionConversion is missing); when reporting issues, please always include a fully runnable, minimal code sample so that nothing is missing.
What I do get is the following exception:
This error makes sense: you're doing HasConversion on the element type, rather than on the collection as a whole. changing the configuration to the following makes the program run without errors:
Full repro
If this doesn't fix your problem in some way, can you please share a full, runnable repro (like the code just above) that shows the problem?
PressXtoChris commentedon Feb 18, 2024
Apologies, didn't realise I'd left it off. My CustomCollection converter simply converted from string to string.
Since using
.HasConversion<string>()caused the following exception:Here's my full code:
Full repro of above
Model.csProgram.cs
Also worth noting that in my real-world use case, I've got a wrapper class for my strings:
Repro of real-world case
Model.csProgram.cs
Using Microsoft.EntityFrameworkCore 8.0.2, and Ngpsql.EntityFrameworkCore.PostgreSQL 8.0.2
When I try the
SetValueConverterimplementation:SetValueConverter implementation
Model.cs
Program.cs is the same as above
I get the following exception:
roji commentedon Oct 18, 2026
AI Triage
The below is an AI-generated analysis and may contain inaccuracies.
Confirmed; classified as a bug. The
IndexOutOfRangeExceptionreproduces with the reporter's identity element converter. It occurs during model initialization, before connecting to PostgreSQL, so neither seed data nor a database is needed. TheStringWrappervariant also reproduces.Versions and regression
IndexOutOfRangeExceptionIndexOutOfRangeExceptionIndexOutOfRangeExceptionNotSupportedException: “Array value converter input type must be an IEnumerable, but is CustomCollection”NotSupportedExceptionThe minimized 8.0.2 repro was also verified on .NET 8. The cross-version comparisons used .NET 10.
This was not introduced in 8.0.2, since 8.0.0 already fails. No previously working version was established, so a regression from an earlier major version is not confirmed. The same
PrimitiveCollectionconfiguration cannot be tested unchanged on EF 7 because that API was introduced in EF 8.Cause and provider comparison
CustomCollectionimplementsIEnumerable<string>through its base class, but is not itself a generic type. In 8.0.2's converter, it falls through totypeof(TInput).GetGenericArguments()[0], indexing an empty array. In 10.0.3, the collection-handling branches still require the input type itself to be generic, so they reject it instead.SQLite 8.0.2 initializes this same model successfully. Therefore, the reported converter/model-initialization failure is PostgreSQL-provider-specific; this issue should not simply be moved upstream.
There is also a separate, shared EF limitation in the follow-up: adding an entity with this read-only collection on SQLite 8.0.2 reproduces the
InvalidCastExceptionfromstring[]toCustomCollectionduring original-value snapshotting. SQLite 10.0.13 instead throwsMissingMethodExceptionbecause the collection has no parameterless constructor. Merely correcting element-type discovery will not make this read-only collection constructible and usable throughout tracking/materialization.As a control, replacing the collection with
List<string>successfully inserts and reloads the values on PostgreSQL with both 8.0.2 and 10.0.3. I could not validate the earlier whole-propertyCustomCollection→string[]conversion suggestion as a workaround for theStringWrappervariant: saving/tracking still fails, so successful model initialization alone is insufficient.Related issues
IList<T>implementations beyond array/List<T>. npgsql/efcore.pg#3413, already closed as its duplicate, reports the same 9.x converter rejection for a non-genericList<T>subclass. The read-only construction/snapshotting aspect here needs to be distinguished from supporting mutable custom lists.minimal repro
Repro.csproj:Program.cs:Run
dotnet run. No database is contacted. The business-logic method and non-empty validation from the original collection are unnecessary to trigger the failure.