diff --git a/src/EFCore/Infrastructure/ModelValidator.cs b/src/EFCore/Infrastructure/ModelValidator.cs index 36a139e12d8..987015c8731 100644 --- a/src/EFCore/Infrastructure/ModelValidator.cs +++ b/src/EFCore/Infrastructure/ModelValidator.cs @@ -1023,14 +1023,21 @@ static void Validate(ITypeBase typeBase, IDiagnosticsLogger)) == null) + if (property is { IsPrimitiveCollection: true, ClrType.IsArray: false }) { - throw new InvalidOperationException( - CoreStrings.BadListType( - property.ClrType.ShortDisplayName(), - typeof(IList<>).MakeGenericType(elementClrType).ShortDisplayName())); + if (property.ClrType.IsSealed && property.ClrType.TryGetElementType(typeof(IList<>)) == null) + { + throw new InvalidOperationException( + CoreStrings.BadListType( + property.ClrType.ShortDisplayName(), + typeof(IList<>).MakeGenericType(elementClrType!).ShortDisplayName())); + } + + if (property.ClrType.GetGenericTypeDefinition() == typeof(IReadOnlyCollection<>) + || property.ClrType.GetGenericTypeDefinition() == typeof(IReadOnlyList<>)) + { + throw new InvalidOperationException(CoreStrings.ReadOnlyListType(property.ClrType.ShortDisplayName())); + } } } diff --git a/src/EFCore/Properties/CoreStrings.Designer.cs b/src/EFCore/Properties/CoreStrings.Designer.cs index be1e60cf391..ec6c16e94fa 100644 --- a/src/EFCore/Properties/CoreStrings.Designer.cs +++ b/src/EFCore/Properties/CoreStrings.Designer.cs @@ -2507,6 +2507,14 @@ public static string QueryUnhandledQueryRootExpression(object? type) GetString("QueryUnhandledQueryRootExpression", nameof(type)), type); + /// + /// The type '{givenType}' cannot be used as a primitive collection because it is read-only. Read-only collections of primitive types are not supported. + /// + public static string ReadOnlyListType(object? givenType) + => string.Format( + GetString("ReadOnlyListType", nameof(givenType)), + givenType); + /// /// An attempt was made to use the context instance while it is being configured. A DbContext instance cannot be used inside 'OnConfiguring' since it is still being configured at this point. This can happen if a second operation is started on this context instance before a previous operation completed. Any instance members are not guaranteed to be thread safe. /// diff --git a/src/EFCore/Properties/CoreStrings.resx b/src/EFCore/Properties/CoreStrings.resx index 4b08beee106..e109eb49aa9 100644 --- a/src/EFCore/Properties/CoreStrings.resx +++ b/src/EFCore/Properties/CoreStrings.resx @@ -1381,6 +1381,9 @@ Query root of type '{type}' wasn't handled by provider code. This issue happens when using a provider specific method on a different provider where it is not supported. + + The type '{givenType}' cannot be used as a primitive collection because it is read-only. Read-only collections of primitive types are not supported. + An attempt was made to use the context instance while it is being configured. A DbContext instance cannot be used inside 'OnConfiguring' since it is still being configured at this point. This can happen if a second operation is started on this context instance before a previous operation completed. Any instance members are not guaranteed to be thread safe. diff --git a/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs b/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs index 6d31ba320db..a25623959b3 100644 --- a/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs +++ b/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs @@ -204,6 +204,52 @@ protected class WithStringCollection public string SomeString { get; set; } } + [ConditionalFact] + public virtual void Throws_when_mapping_an_IReadOnlyCollection() + { + var modelBuilder = CreateConventionModelBuilder(); + + modelBuilder.Entity( + eb => + { + eb.Property(e => e.Id); + eb.PrimitiveCollection(e => e.Tags); + }); + + VerifyError( + CoreStrings.ReadOnlyListType("IReadOnlyCollection"), + modelBuilder, sensitiveDataLoggingEnabled: false); + } + + protected class WithReadOnlyCollection + { + public int Id { get; set; } + public IReadOnlyCollection Tags { get; set; } + } + + [ConditionalFact] + public virtual void Throws_when_mapping_an_IReadOnlyList() + { + var modelBuilder = CreateConventionModelBuilder(); + + modelBuilder.Entity( + eb => + { + eb.Property(e => e.Id); + eb.PrimitiveCollection(e => e.Tags); + }); + + VerifyError( + CoreStrings.ReadOnlyListType("IReadOnlyList"), + modelBuilder, sensitiveDataLoggingEnabled: false); + } + + protected class WithReadOnlyList + { + public int Id { get; set; } + public IReadOnlyList Tags { get; set; } + } + [ConditionalFact] public virtual void Ignores_binary_keys_and_strings_without_custom_comparer() {