Skip to content

Commit

Permalink
Throw better exception when attempting to map a read-only collection …
Browse files Browse the repository at this point in the history
…as a primitive collection

Part of #31722
  • Loading branch information
ajcvickers committed Sep 15, 2023
1 parent d229a43 commit d44784d
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 7 deletions.
21 changes: 14 additions & 7 deletions src/EFCore/Infrastructure/ModelValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1023,14 +1023,21 @@ static void Validate(ITypeBase typeBase, IDiagnosticsLogger<DbLoggerCategory.Mod
foreach (var property in typeBase.GetDeclaredProperties())
{
var elementClrType = property.GetElementType()?.ClrType;
if (property is { IsPrimitiveCollection: true, ClrType.IsArray: false, ClrType.IsSealed: true }
&& elementClrType is { IsSealed: true }
&& elementClrType.TryGetElementType(typeof(IList<>)) == 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()));
}
}
}

Expand Down
8 changes: 8 additions & 0 deletions src/EFCore/Properties/CoreStrings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/EFCore/Properties/CoreStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -1381,6 +1381,9 @@
<data name="QueryUnhandledQueryRootExpression" xml:space="preserve">
<value>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.</value>
</data>
<data name="ReadOnlyListType" xml:space="preserve">
<value>The type '{givenType}' cannot be used as a primitive collection because it is read-only. Read-only collections of primitive types are not supported.</value>
</data>
<data name="RecursiveOnConfiguring" xml:space="preserve">
<value>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.</value>
</data>
Expand Down
46 changes: 46 additions & 0 deletions test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<WithReadOnlyCollection>(
eb =>
{
eb.Property(e => e.Id);
eb.PrimitiveCollection(e => e.Tags);
});

VerifyError(
CoreStrings.ReadOnlyListType("IReadOnlyCollection<int>"),
modelBuilder, sensitiveDataLoggingEnabled: false);
}

protected class WithReadOnlyCollection
{
public int Id { get; set; }
public IReadOnlyCollection<int> Tags { get; set; }
}

[ConditionalFact]
public virtual void Throws_when_mapping_an_IReadOnlyList()
{
var modelBuilder = CreateConventionModelBuilder();

modelBuilder.Entity<WithReadOnlyList>(
eb =>
{
eb.Property(e => e.Id);
eb.PrimitiveCollection(e => e.Tags);
});

VerifyError(
CoreStrings.ReadOnlyListType("IReadOnlyList<char>"),
modelBuilder, sensitiveDataLoggingEnabled: false);
}

protected class WithReadOnlyList
{
public int Id { get; set; }
public IReadOnlyList<char> Tags { get; set; }
}

[ConditionalFact]
public virtual void Ignores_binary_keys_and_strings_without_custom_comparer()
{
Expand Down

0 comments on commit d44784d

Please sign in to comment.