From f8e5211af7cdb77e3d508dda0a6c2f0b89834c3d Mon Sep 17 00:00:00 2001 From: Smit Patel Date: Thu, 5 Mar 2020 12:52:41 -0800 Subject: [PATCH] Introduce complete discriminator mapping - Add fluent API IsComplete over DiscriminatorBuilder - In query if mapping is complete - Don't generate predicate when creating Select for an entityType - Don't apply predicate when doing OfType of derived type when the discriminator is not needed Current default: Mapping is incomplete unless user uses fluent API to mark it as complete. Resolves #18106 --- .../Design/CSharpSnapshotGenerator.cs | 16 +- ...yableMethodTranslatingExpressionVisitor.cs | 72 +-- .../Query/SqlExpressionFactory.cs | 12 + .../ConventionEntityTypeExtensions.cs | 14 + src/EFCore/Extensions/EntityTypeExtensions.cs | 14 + .../Extensions/MutableEntityTypeExtensions.cs | 12 + .../Metadata/Builders/DiscriminatorBuilder.cs | 37 ++ .../Builders/DiscriminatorBuilder`.cs | 11 + .../IConventionDiscriminatorBuilder.cs | 16 + .../Metadata/Internal/CoreAnnotationNames.cs | 9 + .../Migrations/ModelSnapshotSqlServerTest.cs | 3 +- ...mpleteMappingInheritanceInMemoryFixture.cs | 10 + .../CompleteMappingInheritanceInMemoryTest.cs | 32 ++ .../Query/InheritanceFixtureBase.cs | 3 + ...pleteMappingInheritanceSqlServerFixture.cs | 10 + ...CompleteMappingInheritanceSqlServerTest.cs | 500 ++++++++++++++++++ ...CompleteMappingInheritanceSqliteFixture.cs | 10 + .../CompleteMappingInheritanceSqliteTest.cs | 20 + .../ModelBuilding/InheritanceTestBase.cs | 19 + .../ModelBuilding/ModelBuilderGenericTest.cs | 3 + .../ModelBuilderNonGenericTest.cs | 3 + .../ModelBuilding/ModelBuilderTestBase.cs | 2 + 22 files changed, 795 insertions(+), 33 deletions(-) create mode 100644 test/EFCore.InMemory.FunctionalTests/Query/CompleteMappingInheritanceInMemoryFixture.cs create mode 100644 test/EFCore.InMemory.FunctionalTests/Query/CompleteMappingInheritanceInMemoryTest.cs create mode 100644 test/EFCore.SqlServer.FunctionalTests/Query/CompleteMappingInheritanceSqlServerFixture.cs create mode 100644 test/EFCore.SqlServer.FunctionalTests/Query/CompleteMappingInheritanceSqlServerTest.cs create mode 100644 test/EFCore.Sqlite.FunctionalTests/Query/CompleteMappingInheritanceSqliteFixture.cs create mode 100644 test/EFCore.Sqlite.FunctionalTests/Query/CompleteMappingInheritanceSqliteTest.cs diff --git a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs index aa1d6ff2b56..a9439adf114 100644 --- a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs +++ b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs @@ -804,9 +804,10 @@ protected virtual void GenerateEntityTypeAnnotations( } var discriminatorPropertyAnnotation = annotations.FirstOrDefault(a => a.Name == CoreAnnotationNames.DiscriminatorProperty); + var discriminatorMappingCompleteAnnotation = annotations.FirstOrDefault(a => a.Name == CoreAnnotationNames.DiscriminatorMappingComplete); var discriminatorValueAnnotation = annotations.FirstOrDefault(a => a.Name == CoreAnnotationNames.DiscriminatorValue); - if ((discriminatorPropertyAnnotation ?? discriminatorValueAnnotation) != null) + if ((discriminatorPropertyAnnotation ?? discriminatorMappingCompleteAnnotation ?? discriminatorValueAnnotation) != null) { stringBuilder .AppendLine() @@ -833,6 +834,18 @@ protected virtual void GenerateEntityTypeAnnotations( .Append("()"); } + if (discriminatorMappingCompleteAnnotation?.Value != null) + { + var value = discriminatorMappingCompleteAnnotation.Value; + + stringBuilder + .Append(".") + .Append("IsComplete") + .Append("(") + .Append(Code.UnknownLiteral(value)) + .Append(")"); + } + if (discriminatorValueAnnotation?.Value != null) { var value = discriminatorValueAnnotation.Value; @@ -857,6 +870,7 @@ protected virtual void GenerateEntityTypeAnnotations( stringBuilder.AppendLine(";"); annotations.Remove(discriminatorPropertyAnnotation); + annotations.Remove(discriminatorMappingCompleteAnnotation); annotations.Remove(discriminatorValueAnnotation); } diff --git a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs index 5dcfe586de1..c3bf4cfd730 100644 --- a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs @@ -65,7 +65,7 @@ protected RelationalQueryableMethodTranslatingExpressionVisitor( protected override Expression VisitExtension(Expression extensionExpression) { - switch(extensionExpression) + switch (extensionExpression) { case FromSqlQueryRootExpression fromSqlQueryRootExpression: return CreateShapedQueryExpression( @@ -691,37 +691,42 @@ protected override ShapedQueryExpression TranslateOfType(ShapedQueryExpression s var derivedType = entityType.GetDerivedTypes().SingleOrDefault(et => et.ClrType == resultType); if (derivedType != null) { - var selectExpression = (SelectExpression)source.QueryExpression; - var concreteEntityTypes = derivedType.GetConcreteDerivedTypesInclusive().ToList(); - var projectionBindingExpression = (ProjectionBindingExpression)entityShaperExpression.ValueBufferExpression; - var entityProjectionExpression = (EntityProjectionExpression)selectExpression.GetMappedProjection( - projectionBindingExpression.ProjectionMember); - var discriminatorColumn = entityProjectionExpression.BindProperty(entityType.GetDiscriminatorProperty()); - - var predicate = concreteEntityTypes.Count == 1 - ? _sqlExpressionFactory.Equal( - discriminatorColumn, - _sqlExpressionFactory.Constant(concreteEntityTypes[0].GetDiscriminatorValue())) - : (SqlExpression)_sqlExpressionFactory.In( - discriminatorColumn, - _sqlExpressionFactory.Constant(concreteEntityTypes.Select(et => et.GetDiscriminatorValue())), - negated: false); - - selectExpression.ApplyPredicate(predicate); - - var projectionMember = projectionBindingExpression.ProjectionMember; - - Check.DebugAssert( - new ProjectionMember().Equals(projectionMember), - "Invalid ProjectionMember when processing OfType"); - - var entityProjection = (EntityProjectionExpression)selectExpression.GetMappedProjection(projectionMember); - - selectExpression.ReplaceProjectionMapping( - new Dictionary - { + if (!derivedType.GetIsDiscriminatorMappingComplete() + || !derivedType.GetAllBaseTypesInclusiveAscending() + .All(e => (e == derivedType || e.IsAbstract()) && HasSiblings(e))) + { + var selectExpression = (SelectExpression)source.QueryExpression; + var concreteEntityTypes = derivedType.GetConcreteDerivedTypesInclusive().ToList(); + var projectionBindingExpression = (ProjectionBindingExpression)entityShaperExpression.ValueBufferExpression; + var entityProjectionExpression = (EntityProjectionExpression)selectExpression.GetMappedProjection( + projectionBindingExpression.ProjectionMember); + var discriminatorColumn = entityProjectionExpression.BindProperty(entityType.GetDiscriminatorProperty()); + + var predicate = concreteEntityTypes.Count == 1 + ? _sqlExpressionFactory.Equal( + discriminatorColumn, + _sqlExpressionFactory.Constant(concreteEntityTypes[0].GetDiscriminatorValue())) + : (SqlExpression)_sqlExpressionFactory.In( + discriminatorColumn, + _sqlExpressionFactory.Constant(concreteEntityTypes.Select(et => et.GetDiscriminatorValue())), + negated: false); + + selectExpression.ApplyPredicate(predicate); + + var projectionMember = projectionBindingExpression.ProjectionMember; + + Check.DebugAssert( + new ProjectionMember().Equals(projectionMember), + "Invalid ProjectionMember when processing OfType"); + + var entityProjection = (EntityProjectionExpression)selectExpression.GetMappedProjection(projectionMember); + + selectExpression.ReplaceProjectionMapping( + new Dictionary + { { projectionMember, entityProjection.UpdateEntityType(derivedType) } - }); + }); + } return source.UpdateShaperExpression(entityShaperExpression.WithEntityType(derivedType)); } @@ -730,6 +735,11 @@ protected override ShapedQueryExpression TranslateOfType(ShapedQueryExpression s } return null; + + bool HasSiblings(IEntityType entityType) + { + return entityType.BaseType?.GetDirectlyDerivedTypes().Any(i => i != entityType) != true; + } } protected override ShapedQueryExpression TranslateOrderBy( diff --git a/src/EFCore.Relational/Query/SqlExpressionFactory.cs b/src/EFCore.Relational/Query/SqlExpressionFactory.cs index 02e067c4ff6..f89a123f749 100644 --- a/src/EFCore.Relational/Query/SqlExpressionFactory.cs +++ b/src/EFCore.Relational/Query/SqlExpressionFactory.cs @@ -849,6 +849,13 @@ private SqlExpression GenerateJoinPredicate( private bool AddDiscriminatorCondition(SelectExpression selectExpression, IEntityType entityType) { + if (entityType.GetIsDiscriminatorMappingComplete() + && entityType.GetAllBaseTypesInclusiveAscending() + .All(e => (e == entityType || e.IsAbstract()) && HasSiblings(e))) + { + return false; + } + SqlExpression predicate; var concreteEntityTypes = entityType.GetConcreteDerivedTypesInclusive().ToList(); if (concreteEntityTypes.Count == 1) @@ -876,6 +883,11 @@ private bool AddDiscriminatorCondition(SelectExpression selectExpression, IEntit selectExpression.ApplyPredicate(predicate); return true; + + bool HasSiblings(IEntityType entityType) + { + return entityType.BaseType?.GetDirectlyDerivedTypes().Any(i => i != entityType) != true; + } } private void AddOptionalDependentConditions( diff --git a/src/EFCore/Extensions/ConventionEntityTypeExtensions.cs b/src/EFCore/Extensions/ConventionEntityTypeExtensions.cs index 15505ce55a9..3765de7ab21 100644 --- a/src/EFCore/Extensions/ConventionEntityTypeExtensions.cs +++ b/src/EFCore/Extensions/ConventionEntityTypeExtensions.cs @@ -669,6 +669,20 @@ public static void SetDiscriminatorProperty( => entityType.FindAnnotation(CoreAnnotationNames.DiscriminatorProperty) ?.GetConfigurationSource(); + /// + /// Sets the value indicating whether the discriminator mapping is complete. + /// + /// The entity type to set the discriminator mapping complete for. + /// The value indicating whether the discriminator mapping is complete. + /// Indicates whether the configuration was specified using a data annotation. + public static void SetDiscriminatorMappingComplete( + [NotNull] this IConventionEntityType entityType, bool? complete, bool fromDataAnnotation = false) + { + Check.NotNull(entityType, nameof(entityType)); + + entityType.GetRootType().SetOrRemoveAnnotation(CoreAnnotationNames.DiscriminatorMappingComplete, complete, fromDataAnnotation); + } + /// /// Sets the discriminator value for this entity type. /// diff --git a/src/EFCore/Extensions/EntityTypeExtensions.cs b/src/EFCore/Extensions/EntityTypeExtensions.cs index 2cadbc5c1ff..3fa3ee16741 100644 --- a/src/EFCore/Extensions/EntityTypeExtensions.cs +++ b/src/EFCore/Extensions/EntityTypeExtensions.cs @@ -647,6 +647,20 @@ public static IProperty GetDiscriminatorProperty([NotNull] this IEntityType enti return propertyName == null ? null : entityType.FindProperty(propertyName); } + /// + /// Returns the value indicating whether the discriminator mapping is complete for this entity type. + /// + /// The entity type to check whether the discriminator mapping is complete. + public static bool GetIsDiscriminatorMappingComplete([NotNull] this IEntityType entityType) + { + if (entityType.BaseType != null) + { + return entityType.GetRootType().GetIsDiscriminatorMappingComplete(); + } + + return (bool?)entityType[CoreAnnotationNames.DiscriminatorMappingComplete] ?? false; + } + /// /// Returns the discriminator value for this entity type. /// diff --git a/src/EFCore/Extensions/MutableEntityTypeExtensions.cs b/src/EFCore/Extensions/MutableEntityTypeExtensions.cs index c891baba173..70a101b840d 100644 --- a/src/EFCore/Extensions/MutableEntityTypeExtensions.cs +++ b/src/EFCore/Extensions/MutableEntityTypeExtensions.cs @@ -571,6 +571,18 @@ public static void SetDiscriminatorProperty([NotNull] this IMutableEntityType en => Check.NotNull(entityType, nameof(entityType)).AsEntityType() .SetDiscriminatorProperty(property, ConfigurationSource.Explicit); + /// + /// Sets the value indicating whether the discriminator mapping is complete. + /// + /// The entity type to set the discriminator mapping complete for. + /// The value indicating whether the discriminator mapping is complete. + public static void SetDiscriminatorMappingComplete([NotNull] this IMutableEntityType entityType, bool? complete) + { + Check.NotNull(entityType, nameof(entityType)); + + entityType.GetRootType().SetOrRemoveAnnotation(CoreAnnotationNames.DiscriminatorMappingComplete, complete); + } + /// /// Sets the discriminator value for this entity type. /// diff --git a/src/EFCore/Metadata/Builders/DiscriminatorBuilder.cs b/src/EFCore/Metadata/Builders/DiscriminatorBuilder.cs index 4e21b5c029f..fa02ee06e6e 100644 --- a/src/EFCore/Metadata/Builders/DiscriminatorBuilder.cs +++ b/src/EFCore/Metadata/Builders/DiscriminatorBuilder.cs @@ -36,6 +36,34 @@ public DiscriminatorBuilder([NotNull] IMutableEntityType entityType) [EntityFrameworkInternal] protected virtual InternalEntityTypeBuilder EntityTypeBuilder { get; } + /// + /// Configures if the discriminator mapping is complete. + /// + /// The value indicating if this discriminator mapping is complete. + /// The same builder so that multiple calls can be chained. + public virtual DiscriminatorBuilder IsComplete(bool complete = true) + => IsComplete(complete, ConfigurationSource.Explicit); + + private DiscriminatorBuilder IsComplete(bool complete, ConfigurationSource configurationSource) + { + if (configurationSource == ConfigurationSource.Explicit) + { + EntityTypeBuilder.Metadata.SetDiscriminatorMappingComplete(complete); + } + else + { + if (!EntityTypeBuilder.CanSetAnnotation( + CoreAnnotationNames.DiscriminatorMappingComplete, complete, configurationSource)) + { + return null; + } + + EntityTypeBuilder.Metadata.SetDiscriminatorValue(complete, configurationSource == ConfigurationSource.DataAnnotation); + } + + return this; + } + /// /// Configures the default discriminator value to use. /// @@ -119,6 +147,15 @@ private DiscriminatorBuilder HasValue( return this; } + /// + IConventionDiscriminatorBuilder IConventionDiscriminatorBuilder.IsComplete(bool complete, bool fromDataAnnotation) + => IsComplete(complete, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + bool IConventionDiscriminatorBuilder.CanSetIsComplete(bool complete, bool fromDataAnnotation) + => ((IConventionEntityTypeBuilder)EntityTypeBuilder).CanSetAnnotation( + CoreAnnotationNames.DiscriminatorMappingComplete, fromDataAnnotation); + /// IConventionDiscriminatorBuilder IConventionDiscriminatorBuilder.HasValue(object value, bool fromDataAnnotation) => HasValue( diff --git a/src/EFCore/Metadata/Builders/DiscriminatorBuilder`.cs b/src/EFCore/Metadata/Builders/DiscriminatorBuilder`.cs index 3ea463cb2b8..1ec429c7036 100644 --- a/src/EFCore/Metadata/Builders/DiscriminatorBuilder`.cs +++ b/src/EFCore/Metadata/Builders/DiscriminatorBuilder`.cs @@ -30,6 +30,17 @@ public DiscriminatorBuilder([NotNull] DiscriminatorBuilder builder) private DiscriminatorBuilder Builder { get; } + /// + /// Configures if the discriminator mapping is complete. + /// + /// The value indicating if this discriminator mapping is complete. + /// The same builder so that multiple calls can be chained. + public virtual DiscriminatorBuilder IsComplete(bool complete = true) + { + var builder = Builder.IsComplete(complete); + return builder == null ? null : new DiscriminatorBuilder(builder); + } + /// /// Configures the default discriminator value to use. /// diff --git a/src/EFCore/Metadata/Builders/IConventionDiscriminatorBuilder.cs b/src/EFCore/Metadata/Builders/IConventionDiscriminatorBuilder.cs index f3bf417a9eb..005feec53e0 100644 --- a/src/EFCore/Metadata/Builders/IConventionDiscriminatorBuilder.cs +++ b/src/EFCore/Metadata/Builders/IConventionDiscriminatorBuilder.cs @@ -10,6 +10,22 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Builders /// public interface IConventionDiscriminatorBuilder { + /// + /// Configures if the discriminator mapping is complete. + /// + /// The value indicating if this discriminator mapping is complete. + /// Indicates whether the configuration was specified using a data annotation. + /// The same builder so that multiple calls can be chained. + IConventionDiscriminatorBuilder IsComplete(bool complete, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the discriminator mapping is complete can be set from this configuration source. + /// + /// The value indicating if this discriminator mapping is complete. + /// Indicates whether the configuration was specified using a data annotation. + /// true if the discriminator value can be set from this configuration source. + bool CanSetIsComplete(bool complete, bool fromDataAnnotation = false); + /// /// Configures the discriminator value to use. /// diff --git a/src/EFCore/Metadata/Internal/CoreAnnotationNames.cs b/src/EFCore/Metadata/Internal/CoreAnnotationNames.cs index 23e8fd8856e..85c980d8b00 100644 --- a/src/EFCore/Metadata/Internal/CoreAnnotationNames.cs +++ b/src/EFCore/Metadata/Internal/CoreAnnotationNames.cs @@ -86,6 +86,14 @@ public static class CoreAnnotationNames /// public const string DiscriminatorProperty = "DiscriminatorProperty"; + /// + /// 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 const string DiscriminatorMappingComplete = "DiscriminatorMappingComplete"; + /// /// 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 @@ -248,6 +256,7 @@ public static class CoreAnnotationNames ChangeTrackingStrategy, OwnedTypes, DiscriminatorProperty, + DiscriminatorMappingComplete, DiscriminatorValue, ConstructorBinding, TypeMapping, diff --git a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs index 673fb09d686..2db46cadbc8 100644 --- a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs +++ b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs @@ -551,6 +551,7 @@ public virtual void Discriminator_annotations_are_stored_in_snapshot() builder.Entity().HasBaseType(); builder.Entity() .HasDiscriminator(e => e.Discriminator) + .IsComplete(complete: true) .HasValue(typeof(BaseEntity), typeof(BaseEntity).Name) .HasValue(typeof(DerivedEntity), typeof(DerivedEntity).Name) .HasValue(typeof(AnotherDerivedEntity), typeof(AnotherDerivedEntity).Name); @@ -573,7 +574,7 @@ public virtual void Discriminator_annotations_are_stored_in_snapshot() b.ToTable(""BaseEntity""); - b.HasDiscriminator(""Discriminator"").HasValue(""BaseEntity""); + b.HasDiscriminator(""Discriminator"").IsComplete(true).HasValue(""BaseEntity""); }); modelBuilder.Entity(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+AnotherDerivedEntity"", b => diff --git a/test/EFCore.InMemory.FunctionalTests/Query/CompleteMappingInheritanceInMemoryFixture.cs b/test/EFCore.InMemory.FunctionalTests/Query/CompleteMappingInheritanceInMemoryFixture.cs new file mode 100644 index 00000000000..a39bb1a144b --- /dev/null +++ b/test/EFCore.InMemory.FunctionalTests/Query/CompleteMappingInheritanceInMemoryFixture.cs @@ -0,0 +1,10 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.EntityFrameworkCore.Query +{ + public class CompleteMappingInheritanceInMemoryFixture : InheritanceInMemoryFixture + { + protected override bool IsDiscriminatorMappingComplete => true; + } +} diff --git a/test/EFCore.InMemory.FunctionalTests/Query/CompleteMappingInheritanceInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/CompleteMappingInheritanceInMemoryTest.cs new file mode 100644 index 00000000000..4a03b744684 --- /dev/null +++ b/test/EFCore.InMemory.FunctionalTests/Query/CompleteMappingInheritanceInMemoryTest.cs @@ -0,0 +1,32 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Xunit; + +namespace Microsoft.EntityFrameworkCore.Query +{ + public class CompleteMappingInheritanceInMemoryTest : InheritanceTestBase + { + public CompleteMappingInheritanceInMemoryTest(CompleteMappingInheritanceInMemoryFixture fixture) + : base(fixture) + { + } + + [ConditionalFact] + public override void Can_query_all_animal_views() + { + var message = Assert.Throws(() => base.Can_query_all_animal_views()).Message; + + Assert.Equal( + CoreStrings.TranslationFailed( + @"DbSet() + .Select(b => InheritanceInMemoryFixture.MaterializeView(b)) + .OrderBy(a => a.CountryId)"), + message); + } + + protected override bool EnforcesFkConstraints => false; + } +} diff --git a/test/EFCore.Specification.Tests/Query/InheritanceFixtureBase.cs b/test/EFCore.Specification.Tests/Query/InheritanceFixtureBase.cs index 8263a61d69b..d08079e5d24 100644 --- a/test/EFCore.Specification.Tests/Query/InheritanceFixtureBase.cs +++ b/test/EFCore.Specification.Tests/Query/InheritanceFixtureBase.cs @@ -9,6 +9,7 @@ public abstract class InheritanceFixtureBase : SharedStoreFixtureBase false; + protected virtual bool IsDiscriminatorMappingComplete => false; protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context) { @@ -26,6 +27,8 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con modelBuilder.Entity(); modelBuilder.Entity(); + modelBuilder.Entity().HasDiscriminator("Discriminator").IsComplete(IsDiscriminatorMappingComplete); + if (EnableFilters) { modelBuilder.Entity().HasQueryFilter(a => a.CountryId == 1); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/CompleteMappingInheritanceSqlServerFixture.cs b/test/EFCore.SqlServer.FunctionalTests/Query/CompleteMappingInheritanceSqlServerFixture.cs new file mode 100644 index 00000000000..718354d58c4 --- /dev/null +++ b/test/EFCore.SqlServer.FunctionalTests/Query/CompleteMappingInheritanceSqlServerFixture.cs @@ -0,0 +1,10 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.EntityFrameworkCore.Query +{ + public class CompleteMappingInheritanceSqlServerFixture : InheritanceSqlServerFixture + { + protected override bool IsDiscriminatorMappingComplete => true; + } +} diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/CompleteMappingInheritanceSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/CompleteMappingInheritanceSqlServerTest.cs new file mode 100644 index 00000000000..dbdbce7188e --- /dev/null +++ b/test/EFCore.SqlServer.FunctionalTests/Query/CompleteMappingInheritanceSqlServerTest.cs @@ -0,0 +1,500 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.EntityFrameworkCore.TestModels.Inheritance; +using Xunit; +using Xunit.Abstractions; + +// ReSharper disable InconsistentNaming +namespace Microsoft.EntityFrameworkCore.Query +{ + public class CompleteMappingInheritanceSqlServerTest : InheritanceRelationalTestBase + { +#pragma warning disable IDE0060 // Remove unused parameter + public CompleteMappingInheritanceSqlServerTest( + CompleteMappingInheritanceSqlServerFixture fixture, ITestOutputHelper testOutputHelper) +#pragma warning restore IDE0060 // Remove unused parameter + : base(fixture) + { + Fixture.TestSqlLoggerFactory.Clear(); + //Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } + + [ConditionalFact] + public virtual void Common_property_shares_column() + { + using var context = CreateContext(); + var liltType = context.Model.FindEntityType(typeof(Lilt)); + var cokeType = context.Model.FindEntityType(typeof(Coke)); + var teaType = context.Model.FindEntityType(typeof(Tea)); + + Assert.Equal("SugarGrams", cokeType.FindProperty("SugarGrams").GetColumnName()); + Assert.Equal("CaffeineGrams", cokeType.FindProperty("CaffeineGrams").GetColumnName()); + Assert.Equal("CokeCO2", cokeType.FindProperty("Carbonation").GetColumnName()); + + Assert.Equal("SugarGrams", liltType.FindProperty("SugarGrams").GetColumnName()); + Assert.Equal("LiltCO2", liltType.FindProperty("Carbonation").GetColumnName()); + + Assert.Equal("CaffeineGrams", teaType.FindProperty("CaffeineGrams").GetColumnName()); + Assert.Equal("HasMilk", teaType.FindProperty("HasMilk").GetColumnName()); + } + + public override void Can_query_when_shared_column() + { + base.Can_query_when_shared_column(); + + AssertSql( + @"SELECT TOP(2) [d].[Id], [d].[Discriminator], [d].[CaffeineGrams], [d].[CokeCO2], [d].[SugarGrams] +FROM [Drink] AS [d] +WHERE [d].[Discriminator] = N'Coke'", + // + @"SELECT TOP(2) [d].[Id], [d].[Discriminator], [d].[LiltCO2], [d].[SugarGrams] +FROM [Drink] AS [d] +WHERE [d].[Discriminator] = N'Lilt'", + // + @"SELECT TOP(2) [d].[Id], [d].[Discriminator], [d].[CaffeineGrams], [d].[HasMilk] +FROM [Drink] AS [d] +WHERE [d].[Discriminator] = N'Tea'"); + } + + public override void FromSql_on_root() + { + base.FromSql_on_root(); + + AssertSql( + @"select * from ""Animal"""); + } + + public override void FromSql_on_derived() + { + base.FromSql_on_derived(); + + AssertSql( + @"SELECT [a].[Species], [a].[CountryId], [a].[Discriminator], [a].[Name], [a].[EagleId], [a].[IsFlightless], [a].[Group] +FROM ( + select * from ""Animal"" +) AS [a] +WHERE [a].[Discriminator] = N'Eagle'"); + } + + public override void Can_query_all_types_when_shared_column() + { + base.Can_query_all_types_when_shared_column(); + + AssertSql( + @"SELECT [d].[Id], [d].[Discriminator], [d].[CaffeineGrams], [d].[CokeCO2], [d].[SugarGrams], [d].[LiltCO2], [d].[HasMilk] +FROM [Drink] AS [d] +WHERE [d].[Discriminator] IN (N'Drink', N'Coke', N'Lilt', N'Tea')"); + } + + public override void Can_use_of_type_animal() + { + base.Can_use_of_type_animal(); + + AssertSql( + @"SELECT [a].[Species], [a].[CountryId], [a].[Discriminator], [a].[Name], [a].[EagleId], [a].[IsFlightless], [a].[Group], [a].[FoundOn] +FROM [Animal] AS [a] +ORDER BY [a].[Species]"); + } + + public override void Can_use_is_kiwi() + { + base.Can_use_is_kiwi(); + + AssertSql( + @"SELECT [a].[Species], [a].[CountryId], [a].[Discriminator], [a].[Name], [a].[EagleId], [a].[IsFlightless], [a].[Group], [a].[FoundOn] +FROM [Animal] AS [a] +WHERE [a].[Discriminator] = N'Kiwi'"); + } + + public override void Can_use_is_kiwi_with_other_predicate() + { + base.Can_use_is_kiwi_with_other_predicate(); + + AssertSql( + @"SELECT [a].[Species], [a].[CountryId], [a].[Discriminator], [a].[Name], [a].[EagleId], [a].[IsFlightless], [a].[Group], [a].[FoundOn] +FROM [Animal] AS [a] +WHERE ([a].[Discriminator] = N'Kiwi') AND ([a].[CountryId] = 1)"); + } + + public override void Can_use_is_kiwi_in_projection() + { + base.Can_use_is_kiwi_in_projection(); + + AssertSql( + @"SELECT CASE + WHEN [a].[Discriminator] = N'Kiwi' THEN CAST(1 AS bit) + ELSE CAST(0 AS bit) +END +FROM [Animal] AS [a]"); + } + + public override void Can_use_of_type_bird() + { + base.Can_use_of_type_bird(); + + AssertSql( + @"SELECT [a].[Species], [a].[CountryId], [a].[Discriminator], [a].[Name], [a].[EagleId], [a].[IsFlightless], [a].[Group], [a].[FoundOn] +FROM [Animal] AS [a] +ORDER BY [a].[Species]"); + } + + public override void Can_use_of_type_bird_predicate() + { + base.Can_use_of_type_bird_predicate(); + + AssertSql( + @"SELECT [a].[Species], [a].[CountryId], [a].[Discriminator], [a].[Name], [a].[EagleId], [a].[IsFlightless], [a].[Group], [a].[FoundOn] +FROM [Animal] AS [a] +WHERE [a].[CountryId] = 1 +ORDER BY [a].[Species]"); + } + + public override void Can_use_of_type_bird_with_projection() + { + base.Can_use_of_type_bird_with_projection(); + + AssertSql( + @"SELECT [a].[EagleId] +FROM [Animal] AS [a]"); + } + + public override void Can_use_of_type_bird_first() + { + base.Can_use_of_type_bird_first(); + + AssertSql( + @"SELECT TOP(1) [a].[Species], [a].[CountryId], [a].[Discriminator], [a].[Name], [a].[EagleId], [a].[IsFlightless], [a].[Group], [a].[FoundOn] +FROM [Animal] AS [a] +ORDER BY [a].[Species]"); + } + + public override void Can_use_of_type_kiwi() + { + base.Can_use_of_type_kiwi(); + + AssertSql( + @"SELECT [a].[Species], [a].[CountryId], [a].[Discriminator], [a].[Name], [a].[EagleId], [a].[IsFlightless], [a].[FoundOn] +FROM [Animal] AS [a] +WHERE [a].[Discriminator] = N'Kiwi'"); + } + + public override void Can_use_of_type_rose() + { + base.Can_use_of_type_rose(); + + AssertSql( + @"SELECT [p].[Species], [p].[CountryId], [p].[Genus], [p].[Name], [p].[HasThorns] +FROM [Plant] AS [p] +WHERE [p].[Genus] IN (1, 0) AND ([p].[Genus] = 0)"); + } + + public override void Can_query_all_animals() + { + base.Can_query_all_animals(); + + AssertSql( + @"SELECT [a].[Species], [a].[CountryId], [a].[Discriminator], [a].[Name], [a].[EagleId], [a].[IsFlightless], [a].[Group], [a].[FoundOn] +FROM [Animal] AS [a] +ORDER BY [a].[Species]"); + } + + public override void Can_query_all_animal_views() + { + base.Can_query_all_animal_views(); + + AssertSql( + @"SELECT [a].[CountryId], [a].[Discriminator], [a].[Name], [a].[EagleId], [a].[IsFlightless], [a].[Group], [a].[FoundOn] +FROM ( + SELECT * FROM Animal +) AS [a] +WHERE [a].[Discriminator] IN (N'Eagle', N'Kiwi') +ORDER BY [a].[CountryId]"); + } + + public override void Can_query_all_plants() + { + base.Can_query_all_plants(); + + AssertSql( + @"SELECT [p].[Species], [p].[CountryId], [p].[Genus], [p].[Name], [p].[HasThorns] +FROM [Plant] AS [p] +WHERE [p].[Genus] IN (1, 0) +ORDER BY [p].[Species]"); + } + + public override void Can_filter_all_animals() + { + base.Can_filter_all_animals(); + + AssertSql( + @"SELECT [a].[Species], [a].[CountryId], [a].[Discriminator], [a].[Name], [a].[EagleId], [a].[IsFlightless], [a].[Group], [a].[FoundOn] +FROM [Animal] AS [a] +WHERE [a].[Name] = N'Great spotted kiwi' +ORDER BY [a].[Species]"); + } + + public override void Can_query_all_birds() + { + base.Can_query_all_birds(); + + AssertSql( + @"SELECT [a].[Species], [a].[CountryId], [a].[Discriminator], [a].[Name], [a].[EagleId], [a].[IsFlightless], [a].[Group], [a].[FoundOn] +FROM [Animal] AS [a] +ORDER BY [a].[Species]"); + } + + public override void Can_query_just_kiwis() + { + base.Can_query_just_kiwis(); + + AssertSql( + @"SELECT TOP(2) [a].[Species], [a].[CountryId], [a].[Discriminator], [a].[Name], [a].[EagleId], [a].[IsFlightless], [a].[FoundOn] +FROM [Animal] AS [a] +WHERE [a].[Discriminator] = N'Kiwi'"); + } + + public override void Can_query_just_roses() + { + base.Can_query_just_roses(); + + AssertSql( + @"SELECT TOP(2) [p].[Species], [p].[CountryId], [p].[Genus], [p].[Name], [p].[HasThorns] +FROM [Plant] AS [p] +WHERE [p].[Genus] = 0" + ); + } + + public override void Can_include_prey() + { + base.Can_include_prey(); + + AssertSql( + @"SELECT [t].[Species], [t].[CountryId], [t].[Discriminator], [t].[Name], [t].[EagleId], [t].[IsFlightless], [t].[Group], [a0].[Species], [a0].[CountryId], [a0].[Discriminator], [a0].[Name], [a0].[EagleId], [a0].[IsFlightless], [a0].[Group], [a0].[FoundOn] +FROM ( + SELECT TOP(2) [a].[Species], [a].[CountryId], [a].[Discriminator], [a].[Name], [a].[EagleId], [a].[IsFlightless], [a].[Group] + FROM [Animal] AS [a] + WHERE [a].[Discriminator] = N'Eagle' +) AS [t] +LEFT JOIN [Animal] AS [a0] ON [t].[Species] = [a0].[EagleId] +ORDER BY [t].[Species], [a0].[Species]"); + } + + public override void Can_include_animals() + { + base.Can_include_animals(); + + AssertSql( + @"SELECT [c].[Id], [c].[Name], [a].[Species], [a].[CountryId], [a].[Discriminator], [a].[Name], [a].[EagleId], [a].[IsFlightless], [a].[Group], [a].[FoundOn] +FROM [Country] AS [c] +LEFT JOIN [Animal] AS [a] ON [c].[Id] = [a].[CountryId] +ORDER BY [c].[Name], [c].[Id], [a].[Species]"); + } + + public override void Can_use_of_type_kiwi_where_north_on_derived_property() + { + base.Can_use_of_type_kiwi_where_north_on_derived_property(); + + AssertSql( + @"SELECT [a].[Species], [a].[CountryId], [a].[Discriminator], [a].[Name], [a].[EagleId], [a].[IsFlightless], [a].[FoundOn] +FROM [Animal] AS [a] +WHERE ([a].[Discriminator] = N'Kiwi') AND ([a].[FoundOn] = CAST(0 AS tinyint))"); + } + + public override void Can_use_of_type_kiwi_where_south_on_derived_property() + { + base.Can_use_of_type_kiwi_where_south_on_derived_property(); + + AssertSql( + @"SELECT [a].[Species], [a].[CountryId], [a].[Discriminator], [a].[Name], [a].[EagleId], [a].[IsFlightless], [a].[FoundOn] +FROM [Animal] AS [a] +WHERE ([a].[Discriminator] = N'Kiwi') AND ([a].[FoundOn] = CAST(1 AS tinyint))"); + } + + public override void Discriminator_used_when_projection_over_derived_type() + { + base.Discriminator_used_when_projection_over_derived_type(); + + AssertSql( + @"SELECT [a].[FoundOn] +FROM [Animal] AS [a] +WHERE [a].[Discriminator] = N'Kiwi'"); + } + + public override void Discriminator_used_when_projection_over_derived_type2() + { + base.Discriminator_used_when_projection_over_derived_type2(); + + AssertSql( + @"SELECT [a].[IsFlightless], [a].[Discriminator] +FROM [Animal] AS [a]"); + } + + public override void Discriminator_used_when_projection_over_of_type() + { + base.Discriminator_used_when_projection_over_of_type(); + + AssertSql( + @"SELECT [a].[FoundOn] +FROM [Animal] AS [a] +WHERE [a].[Discriminator] = N'Kiwi'"); + } + + public override void Can_insert_update_delete() + { + base.Can_insert_update_delete(); + + AssertSql( + @"SELECT TOP(2) [c].[Id], [c].[Name] +FROM [Country] AS [c] +WHERE [c].[Id] = 1", + // + @"@p0='Apteryx owenii' (Nullable = false) (Size = 100) +@p1='1' +@p2='Kiwi' (Nullable = false) (Size = 4000) +@p3='Little spotted kiwi' (Size = 4000) +@p4=NULL (Size = 100) +@p5='True' +@p6='0' (Size = 1) + +SET NOCOUNT ON; +INSERT INTO [Animal] ([Species], [CountryId], [Discriminator], [Name], [EagleId], [IsFlightless], [FoundOn]) +VALUES (@p0, @p1, @p2, @p3, @p4, @p5, @p6);", + // + @"SELECT TOP(2) [a].[Species], [a].[CountryId], [a].[Discriminator], [a].[Name], [a].[EagleId], [a].[IsFlightless], [a].[FoundOn] +FROM [Animal] AS [a] +WHERE ([a].[Discriminator] = N'Kiwi') AND ([a].[Species] LIKE N'%owenii')", + // + @"@p1='Apteryx owenii' (Nullable = false) (Size = 100) +@p0='Aquila chrysaetos canadensis' (Size = 100) + +SET NOCOUNT ON; +UPDATE [Animal] SET [EagleId] = @p0 +WHERE [Species] = @p1; +SELECT @@ROWCOUNT;", + // + @"SELECT TOP(2) [a].[Species], [a].[CountryId], [a].[Discriminator], [a].[Name], [a].[EagleId], [a].[IsFlightless], [a].[FoundOn] +FROM [Animal] AS [a] +WHERE ([a].[Discriminator] = N'Kiwi') AND ([a].[Species] LIKE N'%owenii')", + // + @"@p0='Apteryx owenii' (Nullable = false) (Size = 100) + +SET NOCOUNT ON; +DELETE FROM [Animal] +WHERE [Species] = @p0; +SELECT @@ROWCOUNT;", + // + @"SELECT COUNT(*) +FROM [Animal] AS [a] +WHERE ([a].[Discriminator] = N'Kiwi') AND ([a].[Species] LIKE N'%owenii')"); + } + + public override void Byte_enum_value_constant_used_in_projection() + { + base.Byte_enum_value_constant_used_in_projection(); + + AssertSql( + @"SELECT CASE + WHEN [a].[IsFlightless] = CAST(1 AS bit) THEN CAST(0 AS tinyint) + ELSE CAST(1 AS tinyint) +END +FROM [Animal] AS [a] +WHERE [a].[Discriminator] = N'Kiwi'"); + } + + public override void Union_siblings_with_duplicate_property_in_subquery() + { + base.Union_siblings_with_duplicate_property_in_subquery(); + + AssertSql( + @"SELECT [t].[Id], [t].[Discriminator], [t].[CaffeineGrams], [t].[CokeCO2], [t].[SugarGrams], [t].[Carbonation], [t].[SugarGrams0], [t].[CaffeineGrams0], [t].[HasMilk] +FROM ( + SELECT [d].[Id], [d].[Discriminator], [d].[CaffeineGrams], [d].[CokeCO2], [d].[SugarGrams], NULL AS [CaffeineGrams0], NULL AS [HasMilk], NULL AS [Carbonation], NULL AS [SugarGrams0] + FROM [Drink] AS [d] + WHERE [d].[Discriminator] = N'Coke' + UNION + SELECT [d0].[Id], [d0].[Discriminator], NULL AS [CaffeineGrams], NULL AS [CokeCO2], NULL AS [SugarGrams], [d0].[CaffeineGrams] AS [CaffeineGrams0], [d0].[HasMilk], NULL AS [Carbonation], NULL AS [SugarGrams0] + FROM [Drink] AS [d0] + WHERE [d0].[Discriminator] = N'Tea' +) AS [t] +WHERE [t].[Id] > 0"); + } + + public override void OfType_Union_subquery() + { + base.OfType_Union_subquery(); + + AssertSql( + @"SELECT [t].[Species], [t].[CountryId], [t].[Discriminator], [t].[Name], [t].[EagleId], [t].[IsFlightless], [t].[FoundOn] +FROM ( + SELECT [a].[Species], [a].[CountryId], [a].[Discriminator], [a].[Name], [a].[EagleId], [a].[IsFlightless], [a].[FoundOn] + FROM [Animal] AS [a] + WHERE [a].[Discriminator] IN (N'Eagle', N'Kiwi') AND ([a].[Discriminator] = N'Kiwi') + UNION + SELECT [a0].[Species], [a0].[CountryId], [a0].[Discriminator], [a0].[Name], [a0].[EagleId], [a0].[IsFlightless], [a0].[FoundOn] + FROM [Animal] AS [a0] + WHERE [a0].[Discriminator] IN (N'Eagle', N'Kiwi') AND ([a0].[Discriminator] = N'Kiwi') +) AS [t] +WHERE ([t].[FoundOn] = CAST(0 AS tinyint)) AND [t].[FoundOn] IS NOT NULL"); + } + + public override void OfType_Union_OfType() + { + base.OfType_Union_OfType(); + + AssertSql(" "); + } + + public override void Subquery_OfType() + { + base.Subquery_OfType(); + + AssertSql( + @"@__p_0='5' + +SELECT DISTINCT [t].[Species], [t].[CountryId], [t].[Discriminator], [t].[Name], [t].[EagleId], [t].[IsFlightless], [t].[FoundOn] +FROM ( + SELECT TOP(@__p_0) [a].[Species], [a].[CountryId], [a].[Discriminator], [a].[Name], [a].[EagleId], [a].[IsFlightless], [a].[Group], [a].[FoundOn] + FROM [Animal] AS [a] +) AS [t] +WHERE [t].[Discriminator] = N'Kiwi'"); + } + + public override void Union_entity_equality() + { + base.Union_entity_equality(); + + AssertSql( + @"SELECT [t].[Species], [t].[CountryId], [t].[Discriminator], [t].[Name], [t].[EagleId], [t].[IsFlightless], [t].[Group], [t].[FoundOn] +FROM ( + SELECT [a].[Species], [a].[CountryId], [a].[Discriminator], [a].[Name], [a].[EagleId], [a].[IsFlightless], [a].[FoundOn], NULL AS [Group] + FROM [Animal] AS [a] + WHERE [a].[Discriminator] = N'Kiwi' + UNION + SELECT [a0].[Species], [a0].[CountryId], [a0].[Discriminator], [a0].[Name], [a0].[EagleId], [a0].[IsFlightless], NULL AS [FoundOn], [a0].[Group] + FROM [Animal] AS [a0] + WHERE [a0].[Discriminator] = N'Eagle' +) AS [t] +WHERE 0 = 1"); + } + + public override void Member_access_on_intermediate_type_works() + { + base.Member_access_on_intermediate_type_works(); + + AssertSql( + @"SELECT [a].[Name] +FROM [Animal] AS [a] +WHERE [a].[Discriminator] = N'Kiwi' +ORDER BY [a].[Name]"); + } + + protected override void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) + => facade.UseTransaction(transaction.GetDbTransaction()); + + private void AssertSql(params string[] expected) + => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); + } +} diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/CompleteMappingInheritanceSqliteFixture.cs b/test/EFCore.Sqlite.FunctionalTests/Query/CompleteMappingInheritanceSqliteFixture.cs new file mode 100644 index 00000000000..e7f01ea6aa5 --- /dev/null +++ b/test/EFCore.Sqlite.FunctionalTests/Query/CompleteMappingInheritanceSqliteFixture.cs @@ -0,0 +1,10 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.EntityFrameworkCore.Query +{ + public class CompleteMappingInheritanceSqliteFixture : InheritanceSqliteFixture + { + protected override bool IsDiscriminatorMappingComplete => true; + } +} diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/CompleteMappingInheritanceSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/CompleteMappingInheritanceSqliteTest.cs new file mode 100644 index 00000000000..660ff44bd9c --- /dev/null +++ b/test/EFCore.Sqlite.FunctionalTests/Query/CompleteMappingInheritanceSqliteTest.cs @@ -0,0 +1,20 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Xunit; + +namespace Microsoft.EntityFrameworkCore.Query +{ + public class CompleteMappingInheritanceSqliteTest : InheritanceTestBase + { + public CompleteMappingInheritanceSqliteTest(CompleteMappingInheritanceSqliteFixture fixture) + : base(fixture) + { + } + + public override void Can_insert_update_delete() + { + // Test from InheritanceSqliteTest causes transaction failure. We only need to test it once. + } + } +} diff --git a/test/EFCore.Tests/ModelBuilding/InheritanceTestBase.cs b/test/EFCore.Tests/ModelBuilding/InheritanceTestBase.cs index 4e3b1a1b792..c03955304e0 100644 --- a/test/EFCore.Tests/ModelBuilding/InheritanceTestBase.cs +++ b/test/EFCore.Tests/ModelBuilding/InheritanceTestBase.cs @@ -848,6 +848,25 @@ public void Base_type_can_be_discovered_after_creating_foreign_keys_on_derived() Assert.Equal(ValueGenerated.OnAdd, mb.Model.FindEntityType(typeof(Q)).FindProperty(nameof(Q.ID)).ValueGenerated); } + [ConditionalFact] + public void Can_get_set_discriminator_mapping_is_complete() + { + var mb = CreateModelBuilder(); + var baseTypeBuilder = mb.Entity(); + var derivedTypeBuilder = mb.Entity(); + + Assert.False(baseTypeBuilder.Metadata.GetIsDiscriminatorMappingComplete()); + + baseTypeBuilder.HasDiscriminator("Discriminator").IsComplete(true); + Assert.True(baseTypeBuilder.Metadata.GetIsDiscriminatorMappingComplete()); + + baseTypeBuilder.HasDiscriminator("Discriminator").IsComplete(false); + Assert.False(baseTypeBuilder.Metadata.GetIsDiscriminatorMappingComplete()); + + derivedTypeBuilder.HasDiscriminator("Discriminator").IsComplete(true); + Assert.True(baseTypeBuilder.Metadata.GetIsDiscriminatorMappingComplete()); + } + protected class L { public int Id { get; set; } diff --git a/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericTest.cs b/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericTest.cs index 3cc835e081f..85fe8054d8e 100644 --- a/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericTest.cs +++ b/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericTest.cs @@ -295,6 +295,9 @@ public GenericTestDiscriminatorBuilder(DiscriminatorBuilder disc protected virtual TestDiscriminatorBuilder Wrap(DiscriminatorBuilder discriminatorBuilder) => new GenericTestDiscriminatorBuilder(discriminatorBuilder); + public override TestDiscriminatorBuilder IsComplete(bool complete) + => Wrap(DiscriminatorBuilder.IsComplete(complete)); + public override TestDiscriminatorBuilder HasValue(TDiscriminator value) => Wrap(DiscriminatorBuilder.HasValue(value)); diff --git a/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericTest.cs b/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericTest.cs index 5edddea28d5..a0e1db88765 100644 --- a/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericTest.cs +++ b/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericTest.cs @@ -259,6 +259,9 @@ public NonGenericTestDiscriminatorBuilder(DiscriminatorBuilder discriminatorBuil protected virtual TestDiscriminatorBuilder Wrap(DiscriminatorBuilder discriminatorBuilder) => new NonGenericTestDiscriminatorBuilder(discriminatorBuilder); + public override TestDiscriminatorBuilder IsComplete(bool complete) + => Wrap(DiscriminatorBuilder.IsComplete(complete)); + public override TestDiscriminatorBuilder HasValue(TDiscriminator value) => Wrap(DiscriminatorBuilder.HasValue(value)); diff --git a/test/EFCore.Tests/ModelBuilding/ModelBuilderTestBase.cs b/test/EFCore.Tests/ModelBuilding/ModelBuilderTestBase.cs index a3639c6f398..0b81ba23d9e 100644 --- a/test/EFCore.Tests/ModelBuilding/ModelBuilderTestBase.cs +++ b/test/EFCore.Tests/ModelBuilding/ModelBuilderTestBase.cs @@ -252,6 +252,8 @@ public abstract TestDiscriminatorBuilder HasDiscriminator { + public abstract TestDiscriminatorBuilder IsComplete(bool complete); + public abstract TestDiscriminatorBuilder HasValue(TDiscriminator value); public abstract TestDiscriminatorBuilder HasValue(TDiscriminator value);