diff --git a/src/EFCore/Metadata/Conventions/NonNullableConventionBase.cs b/src/EFCore/Metadata/Conventions/NonNullableConventionBase.cs
index 81bb83e8544..2d6baeb19b6 100644
--- a/src/EFCore/Metadata/Conventions/NonNullableConventionBase.cs
+++ b/src/EFCore/Metadata/Conventions/NonNullableConventionBase.cs
@@ -69,9 +69,9 @@ protected virtual bool IsNonNullableReferenceType(
return false;
}
- // For C# 8.0 nullable types, the C# currently synthesizes a NullableAttribute that expresses nullability into assemblies
- // it produces. If the model is spread across more than one assembly, there will be multiple versions of this attribute,
- // so look for it by name, caching to avoid reflection on every check.
+ // For C# 8.0 nullable types, the C# compiler currently synthesizes a NullableAttribute that expresses nullability into
+ // assemblies it produces. If the model is spread across more than one assembly, there will be multiple versions of this
+ // attribute, so look for it by name, caching to avoid reflection on every check.
// Note that this may change - if https://github.com/dotnet/corefx/issues/36222 is done we can remove all of this.
// First look for NullableAttribute on the member itself
@@ -114,6 +114,18 @@ protected virtual bool IsNonNullableReferenceType(
if (state.NullableContextFlagFieldInfo?.GetValue(contextAttr) is byte flag)
{
+ // We currently don't calculate support nullability for generic properties, since calculating that is complex
+ // (depends on the nullability of generic type argument).
+ // However, we special case Dictionary as it's used for property bags, and specifically don't identify its indexer
+ // as non-nullable.
+ if (memberInfo is PropertyInfo property
+ && property.IsIndexerProperty()
+ && type.IsGenericType
+ && type.GetGenericTypeDefinition() == typeof(Dictionary<,>))
+ {
+ return false;
+ }
+
return state.TypeCache[type] = flag == 1;
}
}
diff --git a/src/EFCore/Properties/CoreStrings.Designer.cs b/src/EFCore/Properties/CoreStrings.Designer.cs
index d9e38b1c421..841efa177d3 100644
--- a/src/EFCore/Properties/CoreStrings.Designer.cs
+++ b/src/EFCore/Properties/CoreStrings.Designer.cs
@@ -2367,7 +2367,7 @@ public static string BackingFieldOnIndexer([CanBeNull] object field, [CanBeNull]
field, entityType, property);
///
- /// The entity type '{entityType}' cannot be added to the model because a shared entity type with the same clr type already exists.
+ /// The entity type '{entityType}' cannot be added to the model because a shared entity type with the same CLR type already exists.
///
public static string ClashingSharedType([CanBeNull] object entityType)
=> string.Format(
diff --git a/src/EFCore/Properties/CoreStrings.resx b/src/EFCore/Properties/CoreStrings.resx
index b10f4a7fd3f..b07d092f536 100644
--- a/src/EFCore/Properties/CoreStrings.resx
+++ b/src/EFCore/Properties/CoreStrings.resx
@@ -1281,7 +1281,7 @@
Cannot set backing field '{field}' for the indexer property '{entityType}.{property}'. Indexer properties are not allowed to use a backing field.
- The entity type '{entityType}' cannot be added to the model because a shared entity type with the same clr type already exists.
+ The entity type '{entityType}' cannot be added to the model because a shared entity type with the same CLR type already exists.
The skip navigation '{skipNavigation}' cannot be removed because it is set as the inverse of the skip navigation '{inverseSkipNavigation}' on '{referencingEntityType}'. All referencing skip navigations must be removed before this skip navigation can be removed.
diff --git a/test/EFCore.Specification.Tests/Query/NorthwindKeylessEntitiesQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindKeylessEntitiesQueryTestBase.cs
index 982af1e7fb4..e94a0a5b9da 100644
--- a/test/EFCore.Specification.Tests/Query/NorthwindKeylessEntitiesQueryTestBase.cs
+++ b/test/EFCore.Specification.Tests/Query/NorthwindKeylessEntitiesQueryTestBase.cs
@@ -161,7 +161,7 @@ where ov.Customer.Orders.Any()
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
- public virtual Task KeylesEntity_groupby(bool async)
+ public virtual Task KeylessEntity_groupby(bool async)
{
return AssertQuery(
async,
diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindKeylessEntitiesQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindKeylessEntitiesQuerySqlServerTest.cs
index 89c2591b4dd..82a9cdf355e 100644
--- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindKeylessEntitiesQuerySqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindKeylessEntitiesQuerySqlServerTest.cs
@@ -147,9 +147,9 @@ public override void Auto_initialized_view_set()
@"SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Customers] AS [c]");
}
- public override async Task KeylesEntity_groupby(bool async)
+ public override async Task KeylessEntity_groupby(bool async)
{
- await base.KeylesEntity_groupby(async);
+ await base.KeylessEntity_groupby(async);
AssertSql(
@"SELECT [c].[City] AS [Key], COUNT(*) AS [Count], COALESCE(SUM(CAST(LEN([c].[Address]) AS int)), 0) AS [Sum]
diff --git a/test/EFCore.Tests/Metadata/Conventions/NonNullableReferencePropertyConventionTest.cs b/test/EFCore.Tests/Metadata/Conventions/NonNullableReferencePropertyConventionTest.cs
index 866aa97772a..072e74cd68f 100644
--- a/test/EFCore.Tests/Metadata/Conventions/NonNullableReferencePropertyConventionTest.cs
+++ b/test/EFCore.Tests/Metadata/Conventions/NonNullableReferencePropertyConventionTest.cs
@@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
+using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
@@ -82,6 +83,15 @@ public void Reference_nullability_sets_is_nullable_correctly(Type type, string p
Assert.Equal(expectedNullable, entityTypeBuilder.Property(propertyName).Metadata.IsNullable);
}
+ [ConditionalFact]
+ public void Non_nullability_ignores_context_on_generic_types()
+ {
+ var modelBuilder = CreateModelBuilder();
+ var entityTypeBuilder = modelBuilder.SharedTypeEntity>("SomeBag");
+ entityTypeBuilder.IndexerProperty(typeof(string), "b");
+ Assert.True(entityTypeBuilder.Property("b").Metadata.IsNullable);
+ }
+
private void RunConvention(InternalPropertyBuilder propertyBuilder)
{
var context = new ConventionContext(