From 36edc10b1f1e9c7dadc4efe76ace9830da15cc71 Mon Sep 17 00:00:00 2001 From: Arthur Vickers Date: Sat, 11 Feb 2023 00:56:03 +0000 Subject: [PATCH] Support JSON columns in compiled models Fixes #29602 The main change here is to store the JSON type mapping in the relational model, and obsolete the previous storage in the underlying EF model. --- .../Design/AnnotationCodeGenerator.cs | 4 + .../RelationalEntityTypeExtensions.cs | 4 + .../RelationalMapToJsonConvention.cs | 18 +- .../Metadata/Internal/JsonColumn.cs | 18 +- .../Metadata/Internal/JsonViewColumn.cs | 13 +- .../Metadata/Internal/RelationalModel.cs | 5 +- .../Metadata/RelationalAnnotationNames.cs | 1 + ...sitor.ShaperProcessingExpressionVisitor.cs | 8 +- .../Query/SqlExpressions/SelectExpression.cs | 4 +- .../Update/ModificationCommand.cs | 11 +- .../Design/CSharpMigrationsGeneratorTest.cs | 4 + .../CSharpRuntimeModelCodeGeneratorTest.cs | 1321 ++++++++++++++++- .../SqlServerModelBuilderTestBase.cs | 4 + 13 files changed, 1358 insertions(+), 57 deletions(-) diff --git a/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs b/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs index 62a1d346daa..c15cda1c5fd 100644 --- a/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs +++ b/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs @@ -35,7 +35,9 @@ public class AnnotationCodeGenerator : IAnnotationCodeGenerator RelationalAnnotationNames.UpdateStoredProcedure, RelationalAnnotationNames.MappingFragments, RelationalAnnotationNames.RelationalOverrides, +#pragma warning disable CS0618 RelationalAnnotationNames.ContainerColumnTypeMapping +#pragma warning restore CS0618 }; /// @@ -225,7 +227,9 @@ public virtual IReadOnlyList GenerateFluentApiCalls( containerColumnName)); annotations.Remove(RelationalAnnotationNames.ContainerColumnName); +#pragma warning disable CS0618 annotations.Remove(RelationalAnnotationNames.ContainerColumnTypeMapping); +#pragma warning restore CS0618 } methodCallCodeFragments.AddRange(GenerateFluentApiCallsHelper(entityType, annotations, GenerateFluentApi)); diff --git a/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs b/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs index b0ea10a4f36..98d80bc64dc 100644 --- a/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs @@ -1682,6 +1682,7 @@ public static void SetContainerColumnName(this IMutableEntityType entityType, st /// /// The entity type to set the container column type mapping for. /// The type mapping to set. + [Obsolete("Container column mappings are now obtained from IColumnBase.StoreTypeMapping")] public static void SetContainerColumnTypeMapping(this IMutableEntityType entityType, RelationalTypeMapping typeMapping) => entityType.SetOrRemoveAnnotation(RelationalAnnotationNames.ContainerColumnTypeMapping, typeMapping); @@ -1692,6 +1693,7 @@ public static void SetContainerColumnTypeMapping(this IMutableEntityType entityT /// The type mapping to set. /// Indicates whether the configuration was specified using a data annotation. /// The configured value. + [Obsolete("Container column mappings are now obtained from IColumnBase.StoreTypeMapping")] public static RelationalTypeMapping? SetContainerColumnTypeMapping( this IConventionEntityType entityType, RelationalTypeMapping? typeMapping, @@ -1704,6 +1706,7 @@ public static void SetContainerColumnTypeMapping(this IMutableEntityType entityT /// /// The entity type to set the container column type mapping for. /// The for the container column type mapping. + [Obsolete("Container column mappings are now obtained from IColumnBase.StoreTypeMapping")] public static ConfigurationSource? GetContainerColumnTypeMappingConfigurationSource(this IConventionEntityType entityType) => entityType.FindAnnotation(RelationalAnnotationNames.ContainerColumnTypeMapping) ?.GetConfigurationSource(); @@ -1713,6 +1716,7 @@ public static void SetContainerColumnTypeMapping(this IMutableEntityType entityT /// /// The entity type to get the container column type mapping for. /// The container column type mapping to which the entity type is mapped. + [Obsolete("Container column mappings are now obtained from IColumnBase.StoreTypeMapping")] public static RelationalTypeMapping? GetContainerColumnTypeMapping(this IReadOnlyEntityType entityType) => entityType.FindAnnotation(RelationalAnnotationNames.ContainerColumnTypeMapping)?.Value is RelationalTypeMapping typeMapping ? typeMapping diff --git a/src/EFCore.Relational/Metadata/Conventions/RelationalMapToJsonConvention.cs b/src/EFCore.Relational/Metadata/Conventions/RelationalMapToJsonConvention.cs index 7ba1c5ed663..a418180661b 100644 --- a/src/EFCore.Relational/Metadata/Conventions/RelationalMapToJsonConvention.cs +++ b/src/EFCore.Relational/Metadata/Conventions/RelationalMapToJsonConvention.cs @@ -37,6 +37,7 @@ public RelationalMapToJsonConvention( protected virtual RelationalConventionSetBuilderDependencies RelationalDependencies { get; } /// + [Obsolete("Container column mappings are now obtained from IColumnBase.StoreTypeMapping")] public virtual void ProcessEntityTypeAnnotationChanged( IConventionEntityTypeBuilder entityTypeBuilder, string name, @@ -44,23 +45,6 @@ public virtual void ProcessEntityTypeAnnotationChanged( IConventionAnnotation? oldAnnotation, IConventionContext context) { - if (name != RelationalAnnotationNames.ContainerColumnName) - { - return; - } - - var jsonColumnName = annotation?.Value as string; - if (!string.IsNullOrEmpty(jsonColumnName)) - { - var jsonColumnTypeMapping = ((IRelationalTypeMappingSource)Dependencies.TypeMappingSource).FindMapping( - typeof(JsonElement))!; - - entityTypeBuilder.Metadata.SetContainerColumnTypeMapping(jsonColumnTypeMapping); - } - else - { - entityTypeBuilder.Metadata.SetContainerColumnTypeMapping(null); - } } /// diff --git a/src/EFCore.Relational/Metadata/Internal/JsonColumn.cs b/src/EFCore.Relational/Metadata/Internal/JsonColumn.cs index edc9d22c981..74e86bf3ea6 100644 --- a/src/EFCore.Relational/Metadata/Internal/JsonColumn.cs +++ b/src/EFCore.Relational/Metadata/Internal/JsonColumn.cs @@ -11,20 +11,26 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal; /// public class JsonColumn : Column, IColumn { - private readonly ValueComparer _providerValueComparer; - /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public JsonColumn(string name, string type, Table table, ValueComparer provierValueComparer) - : base(name, type, table) + public JsonColumn(string name, Table table, RelationalTypeMapping typeMapping) + : base(name, typeMapping.StoreType, table) { - _providerValueComparer = provierValueComparer; + StoreTypeMapping = typeMapping; } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public override RelationalTypeMapping StoreTypeMapping { get; } + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -149,7 +155,7 @@ bool IColumn.IsRowVersion /// doing so can result in application failures when updating to a new Entity Framework Core release. /// ValueComparer IColumnBase.ProviderValueComparer - => _providerValueComparer; + => StoreTypeMapping.ProviderValueComparer; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore.Relational/Metadata/Internal/JsonViewColumn.cs b/src/EFCore.Relational/Metadata/Internal/JsonViewColumn.cs index c6e3632964a..8f0475d3aad 100644 --- a/src/EFCore.Relational/Metadata/Internal/JsonViewColumn.cs +++ b/src/EFCore.Relational/Metadata/Internal/JsonViewColumn.cs @@ -17,8 +17,17 @@ public class JsonViewColumn : ViewColumn, IViewColumn /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public JsonViewColumn(string name, string type, View view) - : base(name, type, view) + public JsonViewColumn(string name, View view, RelationalTypeMapping typeMapping) + : base(name, typeMapping.StoreType, view) { + StoreTypeMapping = typeMapping; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public override RelationalTypeMapping StoreTypeMapping { get; } } diff --git a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs index cd06a4b187f..c2596f665c8 100644 --- a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs +++ b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs @@ -466,8 +466,7 @@ private static void CreateTableMapping( Debug.Assert(table.FindColumn(containerColumnName) == null); var jsonColumnTypeMapping = relationalTypeMappingSource.FindMapping(typeof(JsonElement))!; - var jsonColumn = new JsonColumn( - containerColumnName, jsonColumnTypeMapping.StoreType, table, jsonColumnTypeMapping.ProviderValueComparer); + var jsonColumn = new JsonColumn(containerColumnName, table, jsonColumnTypeMapping); table.Columns.Add(containerColumnName, jsonColumn); jsonColumn.IsNullable = !ownership.IsRequiredDependent || !ownership.IsUnique; @@ -624,7 +623,7 @@ private static void CreateViewMapping( Debug.Assert(view.FindColumn(containerColumnName) == null); var jsonColumnTypeMapping = relationalTypeMappingSource.FindMapping(typeof(JsonElement))!; - var jsonColumn = new JsonViewColumn(containerColumnName, jsonColumnTypeMapping.StoreType, view); + var jsonColumn = new JsonViewColumn(containerColumnName, view, jsonColumnTypeMapping); view.Columns.Add(containerColumnName, jsonColumn); jsonColumn.IsNullable = !ownership.IsRequired || !ownership.IsUnique; diff --git a/src/EFCore.Relational/Metadata/RelationalAnnotationNames.cs b/src/EFCore.Relational/Metadata/RelationalAnnotationNames.cs index 8a13a6d23ac..94972e366ef 100644 --- a/src/EFCore.Relational/Metadata/RelationalAnnotationNames.cs +++ b/src/EFCore.Relational/Metadata/RelationalAnnotationNames.cs @@ -322,6 +322,7 @@ public static class RelationalAnnotationNames /// /// The name for the annotation specifying container column type mapping. /// + [Obsolete("Container column mappings are now obtained from IColumnBase.StoreTypeMapping")] public const string ContainerColumnTypeMapping = Prefix + "ContainerColumnTypeMapping"; /// diff --git a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs index 63c775e2a95..e387de803e6 100644 --- a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs @@ -6,6 +6,7 @@ using System.Text.Json; using System.Text.Json.Nodes; using Microsoft.EntityFrameworkCore.Internal; +using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Query.Internal; using Microsoft.EntityFrameworkCore.Query.SqlExpressions; @@ -1321,12 +1322,17 @@ private Expression CreateJsonShapers( if (index == 0) { + var jsonColumnName = entityType.GetContainerColumnName()!; + var jsonColumnTypeMapping = (entityType.GetViewOrTableMappings().SingleOrDefault()?.Table + ?? entityType.GetDefaultMappings().Single().Table) + .FindColumn(jsonColumnName)!.StoreTypeMapping; + // create the JsonElement for the initial entity var jsonElementValueExpression = CreateGetValueExpression( _dataReaderParameter, jsonProjectionInfo.JsonColumnIndex, nullable: true, - entityType.GetContainerColumnTypeMapping()!, + jsonColumnTypeMapping, typeof(JsonElement?), property: null); diff --git a/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs index 8526a7551ac..e432ebc46d8 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs @@ -504,7 +504,9 @@ private void AddJsonNavigationBindings( { var targetEntityType = ownedJsonNavigation.TargetEntityType; var jsonColumnName = targetEntityType.GetContainerColumnName()!; - var jsonColumnTypeMapping = targetEntityType.GetContainerColumnTypeMapping()!; + var jsonColumnTypeMapping = (entityType.GetViewOrTableMappings().SingleOrDefault()?.Table + ?? entityType.GetDefaultMappings().Single().Table) + .FindColumn(jsonColumnName)!.StoreTypeMapping; var jsonColumn = new ConcreteColumnExpression( jsonColumnName, diff --git a/src/EFCore.Relational/Update/ModificationCommand.cs b/src/EFCore.Relational/Update/ModificationCommand.cs index 5fd9ecccdd9..b5dd6cc576a 100644 --- a/src/EFCore.Relational/Update/ModificationCommand.cs +++ b/src/EFCore.Relational/Update/ModificationCommand.cs @@ -294,11 +294,11 @@ private List GenerateColumnModifications() if (jsonEntry) { - var jsonColumnsUpdateMap = new Dictionary(); + var jsonColumnsUpdateMap = new Dictionary(); var processedEntries = new List(); foreach (var entry in _entries.Where(e => e.EntityType.IsMappedToJson())) { - var jsonColumn = entry.EntityType.GetContainerColumnName()!; + var jsonColumn = GetTableMapping(entry.EntityType)!.Table.FindColumn(entry.EntityType.GetContainerColumnName()!)!; var jsonPartialUpdateInfo = FindJsonPartialUpdateInfo(entry, processedEntries); if (jsonPartialUpdateInfo == null) @@ -316,12 +316,11 @@ private List GenerateColumnModifications() jsonColumnsUpdateMap[jsonColumn] = jsonPartialUpdateInfo; } - foreach (var (jsonColumnName, updateInfo) in jsonColumnsUpdateMap) + foreach (var (jsonColumn, updateInfo) in jsonColumnsUpdateMap) { var finalUpdatePathElement = updateInfo.Path.Last(); var navigation = finalUpdatePathElement.Navigation; - - var jsonColumnTypeMapping = navigation.TargetEntityType.GetContainerColumnTypeMapping()!; + var jsonColumnTypeMapping = jsonColumn.StoreTypeMapping; var navigationValue = finalUpdatePathElement.ParentEntry.GetCurrentValue(navigation); var json = default(JsonNode?); @@ -367,7 +366,7 @@ private List GenerateColumnModifications() } var columnModificationParameters = new ColumnModificationParameters( - jsonColumnName, + jsonColumn.Name, value: json?.ToJsonString(), property: updateInfo.Property, columnType: jsonColumnTypeMapping.StoreType, diff --git a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs index c149a056798..b15e1b8dcc2 100644 --- a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs @@ -94,7 +94,9 @@ public void Test_new_annotations_handled_for_entity_types() RelationalAnnotationNames.JsonPropertyName, // Appears on entity type but requires specific model (i.e. owned types that can map to json, otherwise validation throws) RelationalAnnotationNames.ContainerColumnName, +#pragma warning disable CS0618 RelationalAnnotationNames.ContainerColumnTypeMapping, +#pragma warning restore CS0618 }; // Add a line here if the code generator is supposed to handle this annotation @@ -262,7 +264,9 @@ public void Test_new_annotations_handled_for_properties() RelationalAnnotationNames.ModelDependencies, RelationalAnnotationNames.FieldValueGetter, RelationalAnnotationNames.ContainerColumnName, +#pragma warning disable CS0618 RelationalAnnotationNames.ContainerColumnTypeMapping, +#pragma warning restore CS0618 RelationalAnnotationNames.JsonPropertyName, }; diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs index 4f51092fe7b..557151151d6 100644 --- a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs @@ -2236,8 +2236,1264 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) c.SaveChanges(); }); + [ConditionalFact] + [SqlServerConfiguredCondition] + public void BigModel_with_JSON_columns() + => Test( + new BigContextWithJson(), + new CompiledModelCodeGenerationOptions { UseNullableReferenceTypes = true }, + code => + Assert.Collection( + code, + c => AssertFileContents( + "BigContextWithJsonModel.cs", +""" +// +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Scaffolding.Internal; + +#pragma warning disable 219, 612, 618 +#nullable enable + +namespace TestNamespace +{ + [DbContext(typeof(CSharpRuntimeModelCodeGeneratorTest.BigContextWithJson))] + public partial class BigContextWithJsonModel : RuntimeModel + { + static BigContextWithJsonModel() + { + var model = new BigContextWithJsonModel(); + model.Initialize(); + model.Customize(); + _instance = model; + } + + private static BigContextWithJsonModel _instance; + public static IModel Instance => _instance; + + partial void Initialize(); + + partial void Customize(); + } +} +""", c), + c => AssertFileContents( + "BigContextWithJsonModelBuilder.cs", +""" +// +using System; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; + +#pragma warning disable 219, 612, 618 +#nullable enable + +namespace TestNamespace +{ + public partial class BigContextWithJsonModel + { + partial void Initialize() + { + var dependentBase = DependentBaseEntityType.Create(this); + var principalBase = PrincipalBaseEntityType.Create(this); + var ownedType = OwnedTypeEntityType.Create(this); + var ownedType0 = OwnedType0EntityType.Create(this); + var principalBasePrincipalDerivedDependentBasebyte = PrincipalBasePrincipalDerivedDependentBasebyteEntityType.Create(this); + var dependentDerived = DependentDerivedEntityType.Create(this, dependentBase); + var principalDerived = PrincipalDerivedEntityType.Create(this, principalBase); + + DependentBaseEntityType.CreateForeignKey1(dependentBase, principalBase); + DependentBaseEntityType.CreateForeignKey2(dependentBase, principalDerived); + OwnedTypeEntityType.CreateForeignKey1(ownedType, principalBase); + OwnedType0EntityType.CreateForeignKey1(ownedType0, principalDerived); + PrincipalBasePrincipalDerivedDependentBasebyteEntityType.CreateForeignKey1(principalBasePrincipalDerivedDependentBasebyte, principalDerived); + PrincipalBasePrincipalDerivedDependentBasebyteEntityType.CreateForeignKey2(principalBasePrincipalDerivedDependentBasebyte, principalBase); + + PrincipalBaseEntityType.CreateSkipNavigation1(principalBase, principalDerived, principalBasePrincipalDerivedDependentBasebyte); + PrincipalDerivedEntityType.CreateSkipNavigation1(principalDerived, principalBase, principalBasePrincipalDerivedDependentBasebyte); + + DependentBaseEntityType.CreateAnnotations(dependentBase); + PrincipalBaseEntityType.CreateAnnotations(principalBase); + OwnedTypeEntityType.CreateAnnotations(ownedType); + OwnedType0EntityType.CreateAnnotations(ownedType0); + PrincipalBasePrincipalDerivedDependentBasebyteEntityType.CreateAnnotations(principalBasePrincipalDerivedDependentBasebyte); + DependentDerivedEntityType.CreateAnnotations(dependentDerived); + PrincipalDerivedEntityType.CreateAnnotations(principalDerived); + + AddAnnotation("Relational:MaxIdentifierLength", 128); + AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + } + } +} +""", c), + c => AssertFileContents( + "DependentBaseEntityType.cs", +""" +// +using System; +using System.Reflection; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations.Design; +using Microsoft.EntityFrameworkCore.Scaffolding.Internal; +using Microsoft.EntityFrameworkCore.ValueGeneration; + +#pragma warning disable 219, 612, 618 +#nullable enable + +namespace TestNamespace +{ + internal partial class DependentBaseEntityType + { + public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType? baseEntityType = null) + { + var runtimeEntityType = model.AddEntityType( + "Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+DependentBase", + typeof(CSharpRuntimeModelCodeGeneratorTest.DependentBase), + baseEntityType, + discriminatorProperty: "EnumDiscriminator", + discriminatorValue: CSharpMigrationsGeneratorTest.Enum1.One); + + var principalId = runtimeEntityType.AddProperty( + "PrincipalId", + typeof(long), + afterSaveBehavior: PropertySaveBehavior.Throw); + principalId.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.None); + + var principalAlternateId = runtimeEntityType.AddProperty( + "PrincipalAlternateId", + typeof(Guid), + afterSaveBehavior: PropertySaveBehavior.Throw); + principalAlternateId.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.None); + + var enumDiscriminator = runtimeEntityType.AddProperty( + "EnumDiscriminator", + typeof(CSharpMigrationsGeneratorTest.Enum1), + afterSaveBehavior: PropertySaveBehavior.Throw, + valueGeneratorFactory: new DiscriminatorValueGeneratorFactory().Create); + enumDiscriminator.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.None); + + var id = runtimeEntityType.AddProperty( + "Id", + typeof(byte?), + propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.DependentBase).GetProperty("Id", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.DependentBase).GetField("k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + nullable: true); + id.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.None); + + var key = runtimeEntityType.AddKey( + new[] { principalId, principalAlternateId }); + runtimeEntityType.SetPrimaryKey(key); + + var index = runtimeEntityType.AddIndex( + new[] { principalId }, + unique: true); + + return runtimeEntityType; + } + + public static RuntimeForeignKey CreateForeignKey1(RuntimeEntityType declaringEntityType, RuntimeEntityType principalEntityType) + { + var runtimeForeignKey = declaringEntityType.AddForeignKey(new[] { declaringEntityType.FindProperty("PrincipalId")! }, + principalEntityType.FindKey(new[] { principalEntityType.FindProperty("Id")! })!, + principalEntityType, + deleteBehavior: DeleteBehavior.Cascade, + unique: true, + required: true); + + return runtimeForeignKey; + } + + public static RuntimeForeignKey CreateForeignKey2(RuntimeEntityType declaringEntityType, RuntimeEntityType principalEntityType) + { + var runtimeForeignKey = declaringEntityType.AddForeignKey(new[] { declaringEntityType.FindProperty("PrincipalId")!, declaringEntityType.FindProperty("PrincipalAlternateId")! }, + principalEntityType.FindKey(new[] { principalEntityType.FindProperty("Id")!, principalEntityType.FindProperty("AlternateId")! })!, + principalEntityType, + deleteBehavior: DeleteBehavior.ClientNoAction, + unique: true, + required: true); + + var principal = declaringEntityType.AddNavigation("Principal", + runtimeForeignKey, + onDependent: true, + typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalDerived>), + propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.DependentBase).GetProperty("Principal", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.DependentBase).GetField("k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly)); + + var dependent = principalEntityType.AddNavigation("Dependent", + runtimeForeignKey, + onDependent: false, + typeof(CSharpRuntimeModelCodeGeneratorTest.DependentBase), + propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalDerived>).GetProperty("Dependent", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalDerived>).GetField("k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + eagerLoaded: true, + lazyLoadingEnabled: false); + + return runtimeForeignKey; + } + + public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) + { + runtimeEntityType.AddAnnotation("DiscriminatorMappingComplete", false); + runtimeEntityType.AddAnnotation("Relational:FunctionName", null); + runtimeEntityType.AddAnnotation("Relational:MappingStrategy", "TPH"); + runtimeEntityType.AddAnnotation("Relational:Schema", null); + runtimeEntityType.AddAnnotation("Relational:SqlQuery", null); + runtimeEntityType.AddAnnotation("Relational:TableName", "DependentBase"); + runtimeEntityType.AddAnnotation("Relational:ViewName", null); + runtimeEntityType.AddAnnotation("Relational:ViewSchema", null); + + Customize(runtimeEntityType); + } + + static partial void Customize(RuntimeEntityType runtimeEntityType); + } +} +""", c), + c => AssertFileContents( + "PrincipalBaseEntityType.cs", +""" +// +using System; +using System.Collections.Generic; +using System.Reflection; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Scaffolding.Internal; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.EntityFrameworkCore.ValueGeneration; +using NetTopologySuite.Geometries; + +#pragma warning disable 219, 612, 618 +#nullable enable + +namespace TestNamespace +{ + internal partial class PrincipalBaseEntityType + { + public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType? baseEntityType = null) + { + var runtimeEntityType = model.AddEntityType( + "Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+PrincipalBase", + typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase), + baseEntityType, + discriminatorProperty: "Discriminator", + discriminatorValue: "PrincipalBase"); + + var id = runtimeEntityType.AddProperty( + "Id", + typeof(long?), + propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetProperty("Id", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetField("k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + afterSaveBehavior: PropertySaveBehavior.Throw); + id.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.None); + + var alternateId = runtimeEntityType.AddProperty( + "AlternateId", + typeof(Guid), + fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetField("AlternateId", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + propertyAccessMode: PropertyAccessMode.FieldDuringConstruction, + afterSaveBehavior: PropertySaveBehavior.Throw); + alternateId.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.None); + + var discriminator = runtimeEntityType.AddProperty( + "Discriminator", + typeof(string), + afterSaveBehavior: PropertySaveBehavior.Throw, + maxLength: 55, + valueGeneratorFactory: new DiscriminatorValueGeneratorFactory().Create); + discriminator.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.None); + + var point = runtimeEntityType.AddProperty( + "Point", + typeof(Point), + nullable: true, + valueGenerated: ValueGenerated.OnAdd, + valueConverter: new CastingConverter(), + valueComparer: new CSharpRuntimeModelCodeGeneratorTest.CustomValueComparer(), + providerValueComparer: new CSharpRuntimeModelCodeGeneratorTest.CustomValueComparer()); + point.AddAnnotation("Relational:ColumnType", "geometry"); + point.AddAnnotation("Relational:DefaultValue", (NetTopologySuite.Geometries.Point)new NetTopologySuite.IO.WKTReader().Read("SRID=0;POINT Z(0 0 0)")); + point.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.None); + + var key = runtimeEntityType.AddKey( + new[] { id }); + + var key0 = runtimeEntityType.AddKey( + new[] { id, alternateId }); + runtimeEntityType.SetPrimaryKey(key0); + key0.AddAnnotation("Relational:Name", "PK"); + + var index = runtimeEntityType.AddIndex( + new[] { alternateId, id }); + + return runtimeEntityType; + } + + public static RuntimeSkipNavigation CreateSkipNavigation1(RuntimeEntityType declaringEntityType, RuntimeEntityType targetEntityType, RuntimeEntityType joinEntityType) + { + var skipNavigation = declaringEntityType.AddSkipNavigation( + "Deriveds", + targetEntityType, + joinEntityType.FindForeignKey( + new[] { joinEntityType.FindProperty("PrincipalsId")!, joinEntityType.FindProperty("PrincipalsAlternateId")! }, + declaringEntityType.FindKey(new[] { declaringEntityType.FindProperty("Id")!, declaringEntityType.FindProperty("AlternateId")! })!, + declaringEntityType)!, + true, + false, + typeof(ICollection), + propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetProperty("Deriveds", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetField("k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly)); + + var inverse = targetEntityType.FindSkipNavigation("Principals"); + if (inverse != null) + { + skipNavigation.Inverse = inverse; + inverse.Inverse = skipNavigation; + } + + return skipNavigation; + } + + public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) + { + runtimeEntityType.AddAnnotation("Relational:FunctionName", null); + runtimeEntityType.AddAnnotation("Relational:MappingStrategy", "TPH"); + runtimeEntityType.AddAnnotation("Relational:Schema", null); + runtimeEntityType.AddAnnotation("Relational:SqlQuery", null); + runtimeEntityType.AddAnnotation("Relational:TableName", "PrincipalBase"); + runtimeEntityType.AddAnnotation("Relational:ViewName", null); + runtimeEntityType.AddAnnotation("Relational:ViewSchema", null); + + Customize(runtimeEntityType); + } + + static partial void Customize(RuntimeEntityType runtimeEntityType); + } +} +""", c), + c => AssertFileContents( + "OwnedTypeEntityType.cs", +""" +// +using System; +using System.Reflection; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Scaffolding.Internal; + +#pragma warning disable 219, 612, 618 +#nullable enable + +namespace TestNamespace +{ + internal partial class OwnedTypeEntityType + { + public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType? baseEntityType = null) + { + var runtimeEntityType = model.AddEntityType( + "Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+PrincipalBase.Owned#OwnedType", + typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType), + baseEntityType, + sharedClrType: true, + changeTrackingStrategy: ChangeTrackingStrategy.ChangingAndChangedNotificationsWithOriginalValues); + + var principalBaseId = runtimeEntityType.AddProperty( + "PrincipalBaseId", + typeof(long), + propertyAccessMode: PropertyAccessMode.Field, + afterSaveBehavior: PropertySaveBehavior.Throw); + principalBaseId.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.None); + + var principalBaseAlternateId = runtimeEntityType.AddProperty( + "PrincipalBaseAlternateId", + typeof(Guid), + propertyAccessMode: PropertyAccessMode.Field, + afterSaveBehavior: PropertySaveBehavior.Throw); + principalBaseAlternateId.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.None); + + var details = runtimeEntityType.AddProperty( + "Details", + typeof(string), + propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType).GetProperty("Details", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType).GetField("
k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + propertyAccessMode: PropertyAccessMode.Field, + nullable: true); + details.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.None); + + var number = runtimeEntityType.AddProperty( + "Number", + typeof(int), + propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType).GetProperty("Number", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType).GetField("k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + propertyAccessMode: PropertyAccessMode.Field); + number.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.None); + + var context = runtimeEntityType.AddServiceProperty( + "Context", + typeof(Microsoft.EntityFrameworkCore.DbContext), + propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType).GetProperty("Context", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)); + + var key = runtimeEntityType.AddKey( + new[] { principalBaseId, principalBaseAlternateId }); + runtimeEntityType.SetPrimaryKey(key); + + return runtimeEntityType; + } + + public static RuntimeForeignKey CreateForeignKey1(RuntimeEntityType declaringEntityType, RuntimeEntityType principalEntityType) + { + var runtimeForeignKey = declaringEntityType.AddForeignKey(new[] { declaringEntityType.FindProperty("PrincipalBaseId")!, declaringEntityType.FindProperty("PrincipalBaseAlternateId")! }, + principalEntityType.FindKey(new[] { principalEntityType.FindProperty("Id")!, principalEntityType.FindProperty("AlternateId")! })!, + principalEntityType, + deleteBehavior: DeleteBehavior.Cascade, + unique: true, + required: true, + requiredDependent: true, + ownership: true); + + var owned = principalEntityType.AddNavigation("Owned", + runtimeForeignKey, + onDependent: false, + typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType), + propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetProperty("Owned", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetField("_ownedField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + propertyAccessMode: PropertyAccessMode.Field, + eagerLoaded: true); + + return runtimeForeignKey; + } + + public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) + { + runtimeEntityType.AddAnnotation("Relational:ContainerColumnName", "Owned"); + runtimeEntityType.AddAnnotation("Relational:FunctionName", null); + runtimeEntityType.AddAnnotation("Relational:Schema", null); + runtimeEntityType.AddAnnotation("Relational:SqlQuery", null); + runtimeEntityType.AddAnnotation("Relational:TableName", "PrincipalBase"); + runtimeEntityType.AddAnnotation("Relational:ViewName", null); + runtimeEntityType.AddAnnotation("Relational:ViewSchema", null); + + Customize(runtimeEntityType); + } + + static partial void Customize(RuntimeEntityType runtimeEntityType); + } +} +""", c), + c => AssertFileContents( + "OwnedType0EntityType.cs", +""" +// +using System; +using System.Collections.Generic; +using System.Reflection; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Scaffolding.Internal; + +#pragma warning disable 219, 612, 618 +#nullable enable + +namespace TestNamespace +{ + internal partial class OwnedType0EntityType + { + public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType? baseEntityType = null) + { + var runtimeEntityType = model.AddEntityType( + "Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+PrincipalDerived>.ManyOwned#OwnedType", + typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType), + baseEntityType, + sharedClrType: true); + + var principalDerivedId = runtimeEntityType.AddProperty( + "PrincipalDerivedId", + typeof(long), + afterSaveBehavior: PropertySaveBehavior.Throw); + principalDerivedId.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.None); + + var principalDerivedAlternateId = runtimeEntityType.AddProperty( + "PrincipalDerivedAlternateId", + typeof(Guid), + afterSaveBehavior: PropertySaveBehavior.Throw); + principalDerivedAlternateId.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.None); + + var id = runtimeEntityType.AddProperty( + "Id", + typeof(int), + valueGenerated: ValueGenerated.OnAdd, + afterSaveBehavior: PropertySaveBehavior.Throw); + id.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + var details = runtimeEntityType.AddProperty( + "Details", + typeof(string), + propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType).GetProperty("Details", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType).GetField("
k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + nullable: true); + details.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.None); + + var number = runtimeEntityType.AddProperty( + "Number", + typeof(int), + propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType).GetProperty("Number", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType).GetField("k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly)); + number.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.None); + + var context = runtimeEntityType.AddServiceProperty( + "Context", + typeof(Microsoft.EntityFrameworkCore.DbContext), + propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType).GetProperty("Context", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)); + + var key = runtimeEntityType.AddKey( + new[] { principalDerivedId, principalDerivedAlternateId, id }); + runtimeEntityType.SetPrimaryKey(key); + + return runtimeEntityType; + } + + public static RuntimeForeignKey CreateForeignKey1(RuntimeEntityType declaringEntityType, RuntimeEntityType principalEntityType) + { + var runtimeForeignKey = declaringEntityType.AddForeignKey(new[] { declaringEntityType.FindProperty("PrincipalDerivedId")!, declaringEntityType.FindProperty("PrincipalDerivedAlternateId")! }, + principalEntityType.FindKey(new[] { principalEntityType.FindProperty("Id")!, principalEntityType.FindProperty("AlternateId")! })!, + principalEntityType, + deleteBehavior: DeleteBehavior.Cascade, + required: true, + ownership: true); + + var manyOwned = principalEntityType.AddNavigation("ManyOwned", + runtimeForeignKey, + onDependent: false, + typeof(ICollection), + fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalDerived>).GetField("ManyOwned", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + eagerLoaded: true); + + return runtimeForeignKey; + } + + public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) + { + runtimeEntityType.AddAnnotation("Relational:ContainerColumnName", "ManyOwned"); + runtimeEntityType.AddAnnotation("Relational:FunctionName", null); + runtimeEntityType.AddAnnotation("Relational:Schema", null); + runtimeEntityType.AddAnnotation("Relational:SqlQuery", null); + runtimeEntityType.AddAnnotation("Relational:TableName", "PrincipalBase"); + runtimeEntityType.AddAnnotation("Relational:ViewName", null); + runtimeEntityType.AddAnnotation("Relational:ViewSchema", null); + + Customize(runtimeEntityType); + } + + static partial void Customize(RuntimeEntityType runtimeEntityType); + } +} +""", c), + c => AssertFileContents( + "PrincipalBasePrincipalDerivedDependentBasebyteEntityType.cs", +""" +// +using System; +using System.Collections.Generic; +using System.Reflection; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata; + +#pragma warning disable 219, 612, 618 +#nullable enable + +namespace TestNamespace +{ + internal partial class PrincipalBasePrincipalDerivedDependentBasebyteEntityType + { + public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType? baseEntityType = null) + { + var runtimeEntityType = model.AddEntityType( + "PrincipalBasePrincipalDerived>", + typeof(Dictionary), + baseEntityType, + sharedClrType: true, + indexerPropertyInfo: RuntimeEntityType.FindIndexerProperty(typeof(Dictionary)), + propertyBag: true); + + var derivedsId = runtimeEntityType.AddProperty( + "DerivedsId", + typeof(long), + propertyInfo: runtimeEntityType.FindIndexerPropertyInfo(), + afterSaveBehavior: PropertySaveBehavior.Throw); + derivedsId.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.None); + + var derivedsAlternateId = runtimeEntityType.AddProperty( + "DerivedsAlternateId", + typeof(Guid), + propertyInfo: runtimeEntityType.FindIndexerPropertyInfo(), + afterSaveBehavior: PropertySaveBehavior.Throw); + derivedsAlternateId.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.None); + + var principalsId = runtimeEntityType.AddProperty( + "PrincipalsId", + typeof(long), + propertyInfo: runtimeEntityType.FindIndexerPropertyInfo(), + afterSaveBehavior: PropertySaveBehavior.Throw); + principalsId.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.None); + + var principalsAlternateId = runtimeEntityType.AddProperty( + "PrincipalsAlternateId", + typeof(Guid), + propertyInfo: runtimeEntityType.FindIndexerPropertyInfo(), + afterSaveBehavior: PropertySaveBehavior.Throw); + principalsAlternateId.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.None); + + var rowid = runtimeEntityType.AddProperty( + "rowid", + typeof(byte[]), + propertyInfo: runtimeEntityType.FindIndexerPropertyInfo(), + nullable: true, + concurrencyToken: true, + valueGenerated: ValueGenerated.OnAddOrUpdate, + beforeSaveBehavior: PropertySaveBehavior.Ignore, + afterSaveBehavior: PropertySaveBehavior.Ignore); + rowid.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.None); + + var key = runtimeEntityType.AddKey( + new[] { derivedsId, derivedsAlternateId, principalsId, principalsAlternateId }); + runtimeEntityType.SetPrimaryKey(key); + + var index = runtimeEntityType.AddIndex( + new[] { principalsId, principalsAlternateId }); + + return runtimeEntityType; + } + + public static RuntimeForeignKey CreateForeignKey1(RuntimeEntityType declaringEntityType, RuntimeEntityType principalEntityType) + { + var runtimeForeignKey = declaringEntityType.AddForeignKey(new[] { declaringEntityType.FindProperty("DerivedsId")!, declaringEntityType.FindProperty("DerivedsAlternateId")! }, + principalEntityType.FindKey(new[] { principalEntityType.FindProperty("Id")!, principalEntityType.FindProperty("AlternateId")! })!, + principalEntityType, + deleteBehavior: DeleteBehavior.Cascade, + required: true); + + return runtimeForeignKey; + } + + public static RuntimeForeignKey CreateForeignKey2(RuntimeEntityType declaringEntityType, RuntimeEntityType principalEntityType) + { + var runtimeForeignKey = declaringEntityType.AddForeignKey(new[] { declaringEntityType.FindProperty("PrincipalsId")!, declaringEntityType.FindProperty("PrincipalsAlternateId")! }, + principalEntityType.FindKey(new[] { principalEntityType.FindProperty("Id")!, principalEntityType.FindProperty("AlternateId")! })!, + principalEntityType, + deleteBehavior: DeleteBehavior.ClientCascade, + required: true); + + return runtimeForeignKey; + } + + public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) + { + runtimeEntityType.AddAnnotation("Relational:FunctionName", null); + runtimeEntityType.AddAnnotation("Relational:Schema", null); + runtimeEntityType.AddAnnotation("Relational:SqlQuery", null); + runtimeEntityType.AddAnnotation("Relational:TableName", "PrincipalBasePrincipalDerived>"); + runtimeEntityType.AddAnnotation("Relational:ViewName", null); + runtimeEntityType.AddAnnotation("Relational:ViewSchema", null); + + Customize(runtimeEntityType); + } + + static partial void Customize(RuntimeEntityType runtimeEntityType); + } +} +""", c), + c => AssertFileContents( + "DependentDerivedEntityType.cs", +""" +// +using System; +using System.Reflection; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations.Design; +using Microsoft.EntityFrameworkCore.Scaffolding.Internal; + +#pragma warning disable 219, 612, 618 +#nullable enable + +namespace TestNamespace +{ + internal partial class DependentDerivedEntityType + { + public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType? baseEntityType = null) + { + var runtimeEntityType = model.AddEntityType( + "Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+DependentDerived", + typeof(CSharpRuntimeModelCodeGeneratorTest.DependentDerived), + baseEntityType, + discriminatorProperty: "EnumDiscriminator", + discriminatorValue: CSharpMigrationsGeneratorTest.Enum1.Two); + + var data = runtimeEntityType.AddProperty( + "Data", + typeof(string), + propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.DependentDerived).GetProperty("Data", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.DependentDerived).GetField("k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + nullable: true, + maxLength: 20, + unicode: false); + data.AddAnnotation("Relational:IsFixedLength", true); + data.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.None); + + var money = runtimeEntityType.AddProperty( + "Money", + typeof(decimal), + precision: 9, + scale: 3); + money.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.None); + + return runtimeEntityType; + } + + public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) + { + runtimeEntityType.AddAnnotation("Relational:FunctionName", null); + runtimeEntityType.AddAnnotation("Relational:Schema", null); + runtimeEntityType.AddAnnotation("Relational:SqlQuery", null); + runtimeEntityType.AddAnnotation("Relational:TableName", "DependentBase"); + runtimeEntityType.AddAnnotation("Relational:ViewName", null); + runtimeEntityType.AddAnnotation("Relational:ViewSchema", null); + + Customize(runtimeEntityType); + } + + static partial void Customize(RuntimeEntityType runtimeEntityType); + } +} +""", c), + c => AssertFileContents( + "PrincipalDerivedEntityType.cs", +""" +// +using System; +using System.Collections.Generic; +using System.Reflection; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Scaffolding.Internal; + +#pragma warning disable 219, 612, 618 +#nullable enable + +namespace TestNamespace +{ + internal partial class PrincipalDerivedEntityType + { + public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType? baseEntityType = null) + { + var runtimeEntityType = model.AddEntityType( + "Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+PrincipalDerived>", + typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalDerived>), + baseEntityType, + discriminatorProperty: "Discriminator", + discriminatorValue: "PrincipalDerived>"); + + return runtimeEntityType; + } + + public static RuntimeSkipNavigation CreateSkipNavigation1(RuntimeEntityType declaringEntityType, RuntimeEntityType targetEntityType, RuntimeEntityType joinEntityType) + { + var skipNavigation = declaringEntityType.AddSkipNavigation( + "Principals", + targetEntityType, + joinEntityType.FindForeignKey( + new[] { joinEntityType.FindProperty("DerivedsId")!, joinEntityType.FindProperty("DerivedsAlternateId")! }, + declaringEntityType.FindKey(new[] { declaringEntityType.FindProperty("Id")!, declaringEntityType.FindProperty("AlternateId")! })!, + declaringEntityType)!, + true, + false, + typeof(ICollection), + propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalDerived>).GetProperty("Principals", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalDerived>).GetField("k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + eagerLoaded: true, + lazyLoadingEnabled: false); + + var inverse = targetEntityType.FindSkipNavigation("Deriveds"); + if (inverse != null) + { + skipNavigation.Inverse = inverse; + inverse.Inverse = skipNavigation; + } + + return skipNavigation; + } + + public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) + { + runtimeEntityType.AddAnnotation("Relational:FunctionName", null); + runtimeEntityType.AddAnnotation("Relational:Schema", null); + runtimeEntityType.AddAnnotation("Relational:SqlQuery", null); + runtimeEntityType.AddAnnotation("Relational:TableName", "PrincipalBase"); + runtimeEntityType.AddAnnotation("Relational:ViewName", null); + runtimeEntityType.AddAnnotation("Relational:ViewSchema", null); + + Customize(runtimeEntityType); + } + + static partial void Customize(RuntimeEntityType runtimeEntityType); + } +} +""", c)), + model => + { + Assert.Equal( + CoreStrings.RuntimeModelMissingData, + Assert.Throws(() => model.GetCollation()).Message); + Assert.Equal( + new[] { RelationalAnnotationNames.MaxIdentifierLength, SqlServerAnnotationNames.ValueGenerationStrategy }, + model.GetAnnotations().Select(a => a.Name)); + Assert.Equal(SqlServerValueGenerationStrategy.IdentityColumn, model.GetValueGenerationStrategy()); + Assert.Equal( + CoreStrings.RuntimeModelMissingData, + Assert.Throws(() => model.GetPropertyAccessMode()).Message); + Assert.Null(model[SqlServerAnnotationNames.IdentitySeed]); + Assert.Equal( + CoreStrings.RuntimeModelMissingData, + Assert.Throws(() => model.GetIdentitySeed()).Message); + Assert.Null(model[SqlServerAnnotationNames.IdentityIncrement]); + Assert.Equal( + CoreStrings.RuntimeModelMissingData, + Assert.Throws(() => model.GetIdentityIncrement()).Message); + + Assert.Null(model.FindEntityType(typeof(AbstractBase))); + var principalBase = model.FindEntityType(typeof(PrincipalBase)); + Assert.Equal(typeof(PrincipalBase).FullName, principalBase.Name); + Assert.False(principalBase.HasSharedClrType); + Assert.False(principalBase.IsPropertyBag); + Assert.False(principalBase.IsOwned()); + Assert.Null(principalBase.BaseType); + Assert.IsType(principalBase.ConstructorBinding); + Assert.Null(principalBase.FindIndexerPropertyInfo()); + Assert.Equal(ChangeTrackingStrategy.Snapshot, principalBase.GetChangeTrackingStrategy()); + Assert.Null(principalBase.GetQueryFilter()); + Assert.Equal("PrincipalBase", principalBase.GetTableName()); + Assert.Null(principalBase.GetSchema()); + Assert.Equal( + CoreStrings.RuntimeModelMissingData, + Assert.Throws(() => principalBase.GetSeedData()).Message); + + var principalId = principalBase.FindProperty(nameof(PrincipalBase.Id)); + Assert.Equal( + new[] { SqlServerAnnotationNames.ValueGenerationStrategy }, + principalId.GetAnnotations().Select(a => a.Name)); + Assert.Equal(typeof(long?), principalId.ClrType); + Assert.Equal(typeof(long?), principalId.PropertyInfo.PropertyType); + Assert.Equal(typeof(long?), principalId.FieldInfo.FieldType); + Assert.False(principalId.IsNullable); + Assert.Equal(ValueGenerated.Never, principalId.ValueGenerated); + Assert.Equal(PropertySaveBehavior.Throw, principalId.GetAfterSaveBehavior()); + Assert.Equal(PropertySaveBehavior.Save, principalId.GetBeforeSaveBehavior()); + Assert.Null(principalId[CoreAnnotationNames.BeforeSaveBehavior]); + Assert.Null(principalId[CoreAnnotationNames.AfterSaveBehavior]); + Assert.Equal("Id", principalId.GetColumnName()); + Assert.Equal("bigint", principalId.GetColumnType()); + Assert.Null(principalId.GetValueConverter()); + Assert.NotNull(principalId.GetValueComparer()); + Assert.NotNull(principalId.GetKeyValueComparer()); + Assert.Equal(SqlServerValueGenerationStrategy.None, principalId.GetValueGenerationStrategy()); + Assert.Null(principalId[SqlServerAnnotationNames.IdentitySeed]); + Assert.Equal( + CoreStrings.RuntimeModelMissingData, + Assert.Throws(() => principalId.GetIdentitySeed()).Message); + Assert.Null(principalId[SqlServerAnnotationNames.IdentityIncrement]); + Assert.Equal( + CoreStrings.RuntimeModelMissingData, + Assert.Throws(() => principalId.GetIdentityIncrement()).Message); + + var pointProperty = principalBase.FindProperty("Point"); + Assert.Equal(typeof(Point), pointProperty.ClrType); + Assert.True(pointProperty.IsNullable); + Assert.Equal(ValueGenerated.OnAdd, pointProperty.ValueGenerated); + Assert.Equal("Point", pointProperty.GetColumnName()); + Assert.Equal("geometry", pointProperty.GetColumnType()); + Assert.Equal(0, ((Point)pointProperty.GetDefaultValue()).SRID); + Assert.IsType>(pointProperty.GetValueConverter()); + Assert.IsType>(pointProperty.GetValueComparer()); + Assert.IsType>(pointProperty.GetKeyValueComparer()); + Assert.IsType>(pointProperty.GetProviderValueComparer()); + Assert.Equal(SqlServerValueGenerationStrategy.None, pointProperty.GetValueGenerationStrategy()); + Assert.Null(pointProperty[CoreAnnotationNames.PropertyAccessMode]); + + var discriminatorProperty = principalBase.FindDiscriminatorProperty(); + Assert.Equal("Discriminator", discriminatorProperty.Name); + Assert.Equal(typeof(string), discriminatorProperty.ClrType); + + var principalAlternateId = principalBase.FindProperty(nameof(PrincipalBase.AlternateId)); + var compositeIndex = principalBase.GetIndexes().Single(); + Assert.Equal(PropertyAccessMode.FieldDuringConstruction, principalAlternateId.GetPropertyAccessMode()); + Assert.Empty(compositeIndex.GetAnnotations()); + Assert.Equal(new[] { principalAlternateId, principalId }, compositeIndex.Properties); + Assert.False(compositeIndex.IsUnique); + Assert.Null(compositeIndex.Name); + Assert.Equal("IX_PrincipalBase_AlternateId_Id", compositeIndex.GetDatabaseName()); + + Assert.Equal(new[] { compositeIndex }, principalAlternateId.GetContainingIndexes()); + + Assert.Equal(2, principalBase.GetKeys().Count()); + + var principalAlternateKey = principalBase.GetKeys().First(); + Assert.Same(principalId, principalAlternateKey.Properties.Single()); + Assert.False(principalAlternateKey.IsPrimaryKey()); + Assert.Equal("AK_PrincipalBase_Id", principalAlternateKey.GetName()); + + var principalKey = principalBase.GetKeys().Last(); + Assert.Equal( + new[] { RelationalAnnotationNames.Name }, + principalKey.GetAnnotations().Select(a => a.Name)); + Assert.Equal(new[] { principalId, principalAlternateId }, principalKey.Properties); + Assert.True(principalKey.IsPrimaryKey()); + Assert.Equal("PK", principalKey.GetName()); + Assert.Null(principalKey[SqlServerAnnotationNames.Clustered]); + Assert.Equal( + CoreStrings.RuntimeModelMissingData, + Assert.Throws(() => principalKey.IsClustered()).Message); + + Assert.Equal(new[] { principalAlternateKey, principalKey }, principalId.GetContainingKeys()); + + var referenceOwnedNavigation = principalBase.GetNavigations().Single(); + Assert.Equal( + new[] { CoreAnnotationNames.EagerLoaded }, + referenceOwnedNavigation.GetAnnotations().Select(a => a.Name)); + Assert.Equal(nameof(PrincipalBase.Owned), referenceOwnedNavigation.Name); + Assert.False(referenceOwnedNavigation.IsCollection); + Assert.True(referenceOwnedNavigation.IsEagerLoaded); + Assert.False(referenceOwnedNavigation.IsOnDependent); + Assert.Equal(typeof(OwnedType), referenceOwnedNavigation.ClrType); + Assert.Equal("_ownedField", referenceOwnedNavigation.FieldInfo.Name); + Assert.Equal(nameof(PrincipalBase.Owned), referenceOwnedNavigation.PropertyInfo.Name); + Assert.Null(referenceOwnedNavigation.Inverse); + Assert.Equal(principalBase, referenceOwnedNavigation.DeclaringEntityType); + Assert.Equal(PropertyAccessMode.Field, referenceOwnedNavigation.GetPropertyAccessMode()); + Assert.Null(referenceOwnedNavigation[CoreAnnotationNames.PropertyAccessMode]); + + var referenceOwnedType = referenceOwnedNavigation.TargetEntityType; + Assert.Equal(typeof(PrincipalBase).FullName + ".Owned#OwnedType", referenceOwnedType.Name); + Assert.Equal(typeof(OwnedType), referenceOwnedType.ClrType); + Assert.True(referenceOwnedType.HasSharedClrType); + Assert.False(referenceOwnedType.IsPropertyBag); + Assert.True(referenceOwnedType.IsOwned()); + Assert.Null(referenceOwnedType.BaseType); + Assert.False(referenceOwnedType.IsMemoryOptimized()); + Assert.IsType(referenceOwnedType.ConstructorBinding); + Assert.Null(referenceOwnedType.FindIndexerPropertyInfo()); + Assert.Equal( + ChangeTrackingStrategy.ChangingAndChangedNotificationsWithOriginalValues, + referenceOwnedType.GetChangeTrackingStrategy()); + Assert.Null(referenceOwnedType.GetQueryFilter()); + Assert.Null(referenceOwnedType[CoreAnnotationNames.PropertyAccessMode]); + Assert.Null(referenceOwnedType[CoreAnnotationNames.NavigationAccessMode]); + Assert.Equal( + CoreStrings.RuntimeModelMissingData, + Assert.Throws(() => referenceOwnedType.GetPropertyAccessMode()).Message); + Assert.Equal( + CoreStrings.RuntimeModelMissingData, + Assert.Throws(() => referenceOwnedType.GetNavigationAccessMode()).Message); + + var principalTable = StoreObjectIdentifier.Create(referenceOwnedType, StoreObjectType.Table).Value; + + var ownedId = referenceOwnedType.FindProperty("PrincipalBaseId"); + Assert.True(ownedId.IsPrimaryKey()); + Assert.Equal( + SqlServerValueGenerationStrategy.None, + principalId.GetValueGenerationStrategy(principalTable)); + Assert.Equal( + CoreStrings.RuntimeModelMissingData, + Assert.Throws(() => principalId.GetIdentityIncrement(principalTable)).Message); + Assert.Equal( + CoreStrings.RuntimeModelMissingData, + Assert.Throws(() => principalId.GetIdentitySeed(principalTable)).Message); + + var detailsProperty = referenceOwnedType.FindProperty(nameof(OwnedType.Details)); + Assert.Null(detailsProperty[SqlServerAnnotationNames.Sparse]); + Assert.Equal( + CoreStrings.RuntimeModelMissingData, + Assert.Throws(() => detailsProperty.IsSparse()).Message); + Assert.Null(detailsProperty[RelationalAnnotationNames.Collation]); + Assert.Equal( + CoreStrings.RuntimeModelMissingData, + Assert.Throws(() => detailsProperty.GetCollation()).Message); + + Assert.Null(detailsProperty.GetColumnName(principalTable)); + + var referenceOwnership = referenceOwnedNavigation.ForeignKey; + Assert.Empty(referenceOwnership.GetAnnotations()); + Assert.Same(referenceOwnership, referenceOwnedType.FindOwnership()); + Assert.True(referenceOwnership.IsOwnership); + Assert.True(referenceOwnership.IsRequired); + Assert.True(referenceOwnership.IsRequiredDependent); + Assert.True(referenceOwnership.IsUnique); + Assert.Null(referenceOwnership.DependentToPrincipal); + Assert.Same(referenceOwnedNavigation, referenceOwnership.PrincipalToDependent); + Assert.Equal(DeleteBehavior.Cascade, referenceOwnership.DeleteBehavior); + Assert.Equal(2, referenceOwnership.Properties.Count()); + Assert.Same(principalKey, referenceOwnership.PrincipalKey); + + var ownedServiceProperty = referenceOwnedType.GetServiceProperties().Single(); + Assert.Empty(ownedServiceProperty.GetAnnotations()); + Assert.Equal(typeof(DbContext), ownedServiceProperty.ClrType); + Assert.Equal(typeof(DbContext), ownedServiceProperty.PropertyInfo.PropertyType); + Assert.Null(ownedServiceProperty.FieldInfo); + Assert.Same(referenceOwnedType, ownedServiceProperty.DeclaringEntityType); + var ownedServicePropertyBinding = ownedServiceProperty.ParameterBinding; + Assert.IsType(ownedServicePropertyBinding); + Assert.Equal(typeof(DbContext), ownedServicePropertyBinding.ServiceType); + Assert.Equal(ownedServiceProperty, ownedServicePropertyBinding.ConsumedProperties.Single()); + Assert.Equal(PropertyAccessMode.PreferField, ownedServiceProperty.GetPropertyAccessMode()); + Assert.Null(ownedServiceProperty[CoreAnnotationNames.PropertyAccessMode]); + + var principalDerived = model.FindEntityType(typeof(PrincipalDerived>)); + Assert.Equal(principalBase, principalDerived.BaseType); + Assert.Equal( + "Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+" + + "PrincipalDerived>", + principalDerived.Name); + Assert.False(principalDerived.IsOwned()); + Assert.IsType(principalDerived.ConstructorBinding); + Assert.Equal(ChangeTrackingStrategy.Snapshot, principalDerived.GetChangeTrackingStrategy()); + Assert.Equal("PrincipalDerived>", principalDerived.GetDiscriminatorValue()); + + Assert.Equal(2, principalDerived.GetDeclaredNavigations().Count()); + var dependentNavigation = principalDerived.GetDeclaredNavigations().First(); + Assert.Equal("Dependent", dependentNavigation.Name); + Assert.Equal("Dependent", dependentNavigation.PropertyInfo.Name); + Assert.Equal("k__BackingField", dependentNavigation.FieldInfo.Name); + Assert.False(dependentNavigation.IsCollection); + Assert.True(dependentNavigation.IsEagerLoaded); + Assert.False(dependentNavigation.LazyLoadingEnabled); + Assert.False(dependentNavigation.IsOnDependent); + Assert.Equal(principalDerived, dependentNavigation.DeclaringEntityType); + Assert.Equal("Principal", dependentNavigation.Inverse.Name); + + var ownedCollectionNavigation = principalDerived.GetDeclaredNavigations().Last(); + Assert.Equal("ManyOwned", ownedCollectionNavigation.Name); + Assert.Null(ownedCollectionNavigation.PropertyInfo); + Assert.Equal("ManyOwned", ownedCollectionNavigation.FieldInfo.Name); + Assert.Equal(typeof(ICollection), ownedCollectionNavigation.ClrType); + Assert.True(ownedCollectionNavigation.IsCollection); + Assert.True(ownedCollectionNavigation.IsEagerLoaded); + Assert.False(ownedCollectionNavigation.IsOnDependent); + Assert.Null(ownedCollectionNavigation.Inverse); + Assert.Equal(principalDerived, ownedCollectionNavigation.DeclaringEntityType); + + var collectionOwnedType = ownedCollectionNavigation.TargetEntityType; + Assert.Equal(principalDerived.Name + ".ManyOwned#OwnedType", collectionOwnedType.Name); + Assert.Equal(typeof(OwnedType), collectionOwnedType.ClrType); + Assert.True(collectionOwnedType.HasSharedClrType); + Assert.False(collectionOwnedType.IsPropertyBag); + Assert.True(collectionOwnedType.IsOwned()); + Assert.False(collectionOwnedType.IsMemoryOptimized()); + Assert.Null(collectionOwnedType[RelationalAnnotationNames.IsTableExcludedFromMigrations]); + Assert.Equal( + CoreStrings.RuntimeModelMissingData, + Assert.Throws(() => collectionOwnedType.IsTableExcludedFromMigrations()).Message); + Assert.Null(collectionOwnedType.BaseType); + Assert.IsType(collectionOwnedType.ConstructorBinding); + Assert.Equal(ChangeTrackingStrategy.Snapshot, collectionOwnedType.GetChangeTrackingStrategy()); + + var collectionOwnership = ownedCollectionNavigation.ForeignKey; + Assert.Same(collectionOwnership, collectionOwnedType.FindOwnership()); + Assert.True(collectionOwnership.IsOwnership); + Assert.True(collectionOwnership.IsRequired); + Assert.False(collectionOwnership.IsRequiredDependent); + Assert.False(collectionOwnership.IsUnique); + Assert.Null(collectionOwnership.DependentToPrincipal); + Assert.Same(ownedCollectionNavigation, collectionOwnership.PrincipalToDependent); + Assert.Equal(DeleteBehavior.Cascade, collectionOwnership.DeleteBehavior); + Assert.Equal(2, collectionOwnership.Properties.Count()); + + var derivedSkipNavigation = principalDerived.GetDeclaredSkipNavigations().Single(); + Assert.Equal("Principals", derivedSkipNavigation.Name); + Assert.Equal("Principals", derivedSkipNavigation.PropertyInfo.Name); + Assert.Equal("k__BackingField", derivedSkipNavigation.FieldInfo.Name); + Assert.Equal(typeof(ICollection), derivedSkipNavigation.ClrType); + Assert.True(derivedSkipNavigation.IsCollection); + Assert.True(derivedSkipNavigation.IsEagerLoaded); + Assert.False(derivedSkipNavigation.LazyLoadingEnabled); + Assert.False(derivedSkipNavigation.IsOnDependent); + Assert.Equal(principalDerived, derivedSkipNavigation.DeclaringEntityType); + Assert.Equal("Deriveds", derivedSkipNavigation.Inverse.Name); + Assert.Same(principalBase.GetSkipNavigations().Single(), derivedSkipNavigation.Inverse); + + Assert.Same(derivedSkipNavigation, derivedSkipNavigation.ForeignKey.GetReferencingSkipNavigations().Single()); + Assert.Same( + derivedSkipNavigation.Inverse, derivedSkipNavigation.Inverse.ForeignKey.GetReferencingSkipNavigations().Single()); + + Assert.Equal(new[] { derivedSkipNavigation.Inverse, derivedSkipNavigation }, principalDerived.GetSkipNavigations()); + + var joinType = derivedSkipNavigation.JoinEntityType; + + Assert.Equal("PrincipalBasePrincipalDerived>", joinType.Name); + Assert.Equal(typeof(Dictionary), joinType.ClrType); + Assert.True(joinType.HasSharedClrType); + Assert.True(joinType.IsPropertyBag); + Assert.False(joinType.IsOwned()); + Assert.Null(joinType.BaseType); + Assert.IsType(joinType.ConstructorBinding); + Assert.Equal("Item", joinType.FindIndexerPropertyInfo().Name); + Assert.Equal(ChangeTrackingStrategy.Snapshot, joinType.GetChangeTrackingStrategy()); + Assert.Null(joinType[RelationalAnnotationNames.Comment]); + Assert.Equal( + CoreStrings.RuntimeModelMissingData, + Assert.Throws(() => joinType.GetComment()).Message); + Assert.Null(joinType.GetQueryFilter()); + + var rowid = joinType.GetProperties().Single(p => !p.IsForeignKey()); + Assert.Equal(typeof(byte[]), rowid.ClrType); + Assert.True(rowid.IsIndexerProperty()); + Assert.Same(joinType.FindIndexerPropertyInfo(), rowid.PropertyInfo); + Assert.Null(rowid.FieldInfo); + Assert.True(rowid.IsNullable); + Assert.False(rowid.IsShadowProperty()); + Assert.True(rowid.IsConcurrencyToken); + Assert.Equal(ValueGenerated.OnAddOrUpdate, rowid.ValueGenerated); + Assert.Equal("rowid", rowid.GetColumnName()); + Assert.Equal("rowversion", rowid.GetColumnType()); + Assert.Null(rowid[RelationalAnnotationNames.Comment]); + Assert.Equal( + CoreStrings.RuntimeModelMissingData, + Assert.Throws(() => rowid.GetComment()).Message); + Assert.Null(rowid[RelationalAnnotationNames.ColumnOrder]); + Assert.Equal( + CoreStrings.RuntimeModelMissingData, + Assert.Throws(() => rowid.GetColumnOrder()).Message); + Assert.Null(rowid.GetValueConverter()); + Assert.NotNull(rowid.GetValueComparer()); + Assert.NotNull(rowid.GetKeyValueComparer()); + Assert.Equal(SqlServerValueGenerationStrategy.None, rowid.GetValueGenerationStrategy()); + + var dependentForeignKey = dependentNavigation.ForeignKey; + Assert.False(dependentForeignKey.IsOwnership); + Assert.True(dependentForeignKey.IsRequired); + Assert.False(dependentForeignKey.IsRequiredDependent); + Assert.True(dependentForeignKey.IsUnique); + Assert.Same(dependentNavigation.Inverse, dependentForeignKey.DependentToPrincipal); + Assert.Same(dependentNavigation, dependentForeignKey.PrincipalToDependent); + Assert.Equal(DeleteBehavior.ClientNoAction, dependentForeignKey.DeleteBehavior); + Assert.Equal(new[] { "PrincipalId", "PrincipalAlternateId" }, dependentForeignKey.Properties.Select(p => p.Name)); + Assert.Same(principalKey, dependentForeignKey.PrincipalKey); + + var dependentBase = dependentNavigation.TargetEntityType; + + Assert.False(dependentBase.GetIsDiscriminatorMappingComplete()); + var principalDiscriminator = dependentBase.FindDiscriminatorProperty(); + Assert.IsType( + principalDiscriminator.GetValueGeneratorFactory()(principalDiscriminator, dependentBase)); + Assert.Equal(Enum1.One, dependentBase.GetDiscriminatorValue()); + + var dependentBaseForeignKey = dependentBase.GetForeignKeys().Single(fk => fk != dependentForeignKey); + var dependentForeignKeyProperty = dependentBaseForeignKey.Properties.Single(); + + Assert.Equal( + new[] { dependentBaseForeignKey, dependentForeignKey }, dependentForeignKeyProperty.GetContainingForeignKeys()); + + var dependentDerived = dependentBase.GetDerivedTypes().Single(); + Assert.Equal(Enum1.Two, dependentDerived.GetDiscriminatorValue()); + + Assert.Equal(2, dependentDerived.GetDeclaredProperties().Count()); + + var dependentData = dependentDerived.GetDeclaredProperties().First(); + Assert.Equal(typeof(string), dependentData.ClrType); + Assert.Equal("Data", dependentData.Name); + Assert.Equal("Data", dependentData.PropertyInfo.Name); + Assert.Equal("k__BackingField", dependentData.FieldInfo.Name); + Assert.True(dependentData.IsNullable); + Assert.False(dependentData.IsShadowProperty()); + Assert.False(dependentData.IsConcurrencyToken); + Assert.Equal(ValueGenerated.Never, dependentData.ValueGenerated); + Assert.Equal("Data", dependentData.GetColumnName()); + Assert.Equal("char(20)", dependentData.GetColumnType()); + Assert.Equal(20, dependentData.GetMaxLength()); + Assert.False(dependentData.IsUnicode()); + Assert.True(dependentData.IsFixedLength()); + Assert.Null(dependentData.GetPrecision()); + Assert.Null(dependentData.GetScale()); + + var dependentMoney = dependentDerived.GetDeclaredProperties().Last(); + Assert.Equal(typeof(decimal), dependentMoney.ClrType); + Assert.Equal("Money", dependentMoney.Name); + Assert.Null(dependentMoney.PropertyInfo); + Assert.Null(dependentMoney.FieldInfo); + Assert.False(dependentMoney.IsNullable); + Assert.True(dependentMoney.IsShadowProperty()); + Assert.False(dependentMoney.IsConcurrencyToken); + Assert.Equal(ValueGenerated.Never, dependentMoney.ValueGenerated); + Assert.Equal("Money", dependentMoney.GetColumnName()); + Assert.Equal("decimal(9,3)", dependentMoney.GetColumnType()); + Assert.Null(dependentMoney.GetMaxLength()); + Assert.Null(dependentMoney.IsUnicode()); + Assert.Null(dependentMoney.IsFixedLength()); + Assert.Equal(9, dependentMoney.GetPrecision()); + Assert.Equal(3, dependentMoney.GetScale()); + + Assert.Equal( + new[] + { + derivedSkipNavigation.ForeignKey, + referenceOwnership, + collectionOwnership, + dependentForeignKey, + derivedSkipNavigation.Inverse.ForeignKey + }, + principalKey.GetReferencingForeignKeys()); + + Assert.Equal( + new[] { dependentBaseForeignKey, referenceOwnership, derivedSkipNavigation.Inverse.ForeignKey }, + principalBase.GetReferencingForeignKeys()); + + Assert.Equal( + new[] { derivedSkipNavigation.ForeignKey, collectionOwnership, dependentForeignKey }, + principalDerived.GetDeclaredReferencingForeignKeys()); + + Assert.Equal( + new[] + { + dependentBase, + dependentDerived, + principalBase, + referenceOwnedType, + principalDerived, + collectionOwnedType, + joinType + }, + model.GetEntityTypes()); + }, + typeof(SqlServerNetTopologySuiteDesignTimeServices), + c => + { + c.Set>>().Add( + new PrincipalDerived> + { + Id = 1, + AlternateId = new Guid(), + Dependent = new DependentBase(1), + Owned = new OwnedType(c) + }); + + c.SaveChanges(); + }); + + public class BigContextWithJson : BigContext + { + public BigContextWithJson() + : base(jsonColumns: true) + { + } + } + public class BigContext : SqlServerContextBase { + private readonly bool _jsonColumns; + + public BigContext(bool jsonColumns = false) + { + _jsonColumns = jsonColumns; + } + protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); @@ -2249,8 +3505,11 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity( eb => { - eb.Property(e => e.Id).UseIdentityColumn(2, 3) - .Metadata.SetColumnName("DerivedId", StoreObjectIdentifier.Table("PrincipalDerived")); + if (!_jsonColumns) + { + eb.Property(e => e.Id).UseIdentityColumn(2, 3) + .Metadata.SetColumnName("DerivedId", StoreObjectIdentifier.Table("PrincipalDerived")); + } eb.Property(e => e.AlternateId) .UsePropertyAccessMode(PropertyAccessMode.FieldDuringConstruction); @@ -2278,27 +3537,38 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .IsSparse() .UseCollation("Latin1_General_CI_AI"); - ob.ToTable( - "PrincipalBase", "mySchema", - t => t.Property("PrincipalBaseId").UseIdentityColumn(2, 3)); - - ob.SplitToTable("Details", s => s.Property(e => e.Details)); - - ob.HasData( - new - { - Number = 10, - PrincipalBaseId = 1L, - PrincipalBaseAlternateId = new Guid() - }); + if (_jsonColumns) + { + ob.ToJson(); + } + else + { + ob.ToTable( + "PrincipalBase", "mySchema", + t => t.Property("PrincipalBaseId").UseIdentityColumn(2, 3)); + + ob.SplitToTable("Details", s => s.Property(e => e.Details)); + + ob.HasData( + new + { + Number = 10, + PrincipalBaseId = 1L, + PrincipalBaseAlternateId = new Guid() + }); + } }); eb.Navigation(e => e.Owned).IsRequired().HasField("_ownedField") .UsePropertyAccessMode(PropertyAccessMode.Field); - eb.HasData(new PrincipalBase { Id = 1, AlternateId = new Guid() }); - eb.ToTable("PrincipalBase", "mySchema"); + if (!_jsonColumns) + { + eb.HasData(new PrincipalBase { Id = 1, AlternateId = new Guid() }); + + eb.ToTable("PrincipalBase", "mySchema"); + } }); modelBuilder.Entity>>( @@ -2310,10 +3580,16 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) eb.Navigation(e => e.Dependent).AutoInclude().EnableLazyLoading(false); - eb.OwnsMany( - typeof(OwnedType).FullName, "ManyOwned", ob => + eb.OwnsMany(typeof(OwnedType).FullName, "ManyOwned", ob => { - ob.ToTable("ManyOwned", t => t.IsMemoryOptimized().ExcludeFromMigrations()); + if (_jsonColumns) + { + ob.ToJson(); + } + else + { + ob.ToTable("ManyOwned", t => t.IsMemoryOptimized().ExcludeFromMigrations()); + } }); eb.HasMany(e => e.Principals).WithMany(e => (ICollection>>)e.Deriveds) @@ -2329,7 +3605,10 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) eb.Navigation(e => e.Principals).AutoInclude().EnableLazyLoading(false); - eb.ToTable("PrincipalDerived"); + if (!_jsonColumns) + { + eb.ToTable("PrincipalDerived"); + } }); modelBuilder.Entity>( diff --git a/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderTestBase.cs b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderTestBase.cs index be4c2623667..55ba5b6a859 100644 --- a/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderTestBase.cs +++ b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderTestBase.cs @@ -1822,7 +1822,9 @@ public virtual void Entity_mapped_to_json_and_unwound_afterwards_properly_cleans foreach (var outerOwnedEntity in outerOwnedEntities) { Assert.False(outerOwnedEntity.IsMappedToJson()); +#pragma warning disable CS0618 Assert.Null(outerOwnedEntity.GetContainerColumnTypeMapping()); +#pragma warning restore CS0618 var myEnum = outerOwnedEntity.GetDeclaredProperties().Where(p => p.ClrType.IsEnum).Single(); var typeMapping = myEnum.FindRelationalTypeMapping()!; @@ -1835,7 +1837,9 @@ public virtual void Entity_mapped_to_json_and_unwound_afterwards_properly_cleans foreach (var ownedEntity in ownedEntities) { Assert.False(ownedEntity.IsMappedToJson()); +#pragma warning disable CS0618 Assert.Null(ownedEntity.GetContainerColumnTypeMapping()); +#pragma warning restore CS0618 var myEnum = ownedEntity.GetDeclaredProperties().Where(p => p.ClrType.IsEnum).Single(); var typeMapping = myEnum.FindRelationalTypeMapping()!; Assert.True(typeMapping.Converter is EnumToNumberConverter);