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

Throw better exception when attempting to map a read-only collection as a primitive collection #31755

Merged
merged 1 commit into from
Sep 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading