diff --git a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs
index 98f1c51809f..634aae7f1df 100644
--- a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs
+++ b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs
@@ -845,8 +845,8 @@ private void GenerateTableMapping(
var table = StoreObjectIdentifier.Create(entityType, StoreObjectType.Table);
var tableName = (string?)tableNameAnnotation?.Value ?? table?.Name;
if (tableNameAnnotation == null
- && (tableName == null
- || entityType.BaseType?.GetTableName() == tableName))
+ && entityType.BaseType != null
+ && entityType.BaseType.GetTableName() == tableName)
{
return;
}
diff --git a/src/EFCore.Relational/Extensions/RelationalEntityTypeBuilderExtensions.cs b/src/EFCore.Relational/Extensions/RelationalEntityTypeBuilderExtensions.cs
index 853202cc686..17375d35a1b 100644
--- a/src/EFCore.Relational/Extensions/RelationalEntityTypeBuilderExtensions.cs
+++ b/src/EFCore.Relational/Extensions/RelationalEntityTypeBuilderExtensions.cs
@@ -19,6 +19,9 @@ public static partial class RelationalEntityTypeBuilderExtensions
/// Configures TPC as the mapping strategy for the derived types. Each type will be mapped to a different database object.
/// All properties will be mapped to columns on the corresponding object.
///
+ ///
+ /// See Entity type hierarchy mapping for more information and examples.
+ ///
/// The builder for the entity type being configured.
/// The same builder instance so that multiple calls can be chained.
public static EntityTypeBuilder UseTpcMappingStrategy(this EntityTypeBuilder entityTypeBuilder)
@@ -32,6 +35,9 @@ public static EntityTypeBuilder UseTpcMappingStrategy(this EntityTypeBuilder ent
/// Configures TPH as the mapping strategy for the derived types. All types will be mapped to the same database object.
/// This is the default mapping strategy.
///
+ ///
+ /// See Entity type hierarchy mapping for more information and examples.
+ ///
/// The builder for the entity type being configured.
/// The same builder instance so that multiple calls can be chained.
public static EntityTypeBuilder UseTphMappingStrategy(this EntityTypeBuilder entityTypeBuilder)
@@ -45,6 +51,9 @@ public static EntityTypeBuilder UseTphMappingStrategy(this EntityTypeBuilder ent
/// Configures TPT as the mapping strategy for the derived types. Each type will be mapped to a different database object.
/// Only the declared properties will be mapped to columns on the corresponding object.
///
+ ///
+ /// See Entity type hierarchy mapping for more information and examples.
+ ///
/// The builder for the entity type being configured.
/// The same builder instance so that multiple calls can be chained.
public static EntityTypeBuilder UseTptMappingStrategy(this EntityTypeBuilder entityTypeBuilder)
@@ -58,6 +67,9 @@ public static EntityTypeBuilder UseTptMappingStrategy(this EntityTypeBuilder ent
/// Configures TPC as the mapping strategy for the derived types. Each type will be mapped to a different database object.
/// All properties will be mapped to columns on the corresponding object.
///
+ ///
+ /// See Entity type hierarchy mapping for more information and examples.
+ ///
/// The builder for the entity type being configured.
/// The same builder instance so that multiple calls can be chained.
public static EntityTypeBuilder UseTpcMappingStrategy(this EntityTypeBuilder entityTypeBuilder)
@@ -68,6 +80,9 @@ public static EntityTypeBuilder UseTpcMappingStrategy(this Ent
/// Configures TPH as the mapping strategy for the derived types. All types will be mapped to the same database object.
/// This is the default mapping strategy.
///
+ ///
+ /// See Entity type hierarchy mapping for more information and examples.
+ ///
/// The builder for the entity type being configured.
/// The same builder instance so that multiple calls can be chained.
public static EntityTypeBuilder UseTphMappingStrategy(this EntityTypeBuilder entityTypeBuilder)
@@ -78,12 +93,60 @@ public static EntityTypeBuilder UseTphMappingStrategy(this Ent
/// Configures TPT as the mapping strategy for the derived types. Each type will be mapped to a different database object.
/// Only the declared properties will be mapped to columns on the corresponding object.
///
+ ///
+ /// See Entity type hierarchy mapping for more information and examples.
+ ///
/// The builder for the entity type being configured.
/// The same builder instance so that multiple calls can be chained.
public static EntityTypeBuilder UseTptMappingStrategy(this EntityTypeBuilder entityTypeBuilder)
where TEntity : class
=> (EntityTypeBuilder)((EntityTypeBuilder)entityTypeBuilder).UseTptMappingStrategy();
+ ///
+ /// Sets the hierarchy mapping strategy.
+ ///
+ ///
+ /// See Entity type hierarchy mapping for more information and examples.
+ ///
+ /// The builder for the entity type being configured.
+ /// The mapping strategy for the derived types.
+ /// Indicates whether the configuration was specified using a data annotation.
+ ///
+ /// The same builder instance if the configuration was applied,
+ /// otherwise.
+ ///
+ public static IConventionEntityTypeBuilder? UseMappingStrategy(
+ this IConventionEntityTypeBuilder entityTypeBuilder,
+ string? strategy,
+ bool fromDataAnnotation = false)
+ {
+ if (!entityTypeBuilder.CanSetMappingStrategy(strategy, fromDataAnnotation))
+ {
+ return null;
+ }
+
+ entityTypeBuilder.Metadata.SetMappingStrategy(strategy, fromDataAnnotation);
+ return entityTypeBuilder;
+ }
+
+ ///
+ /// Returns a value indicating whether the hierarchy mapping strategy can be configured
+ /// using the specified configuration source.
+ ///
+ ///
+ /// See Database migrations for more information and examples.
+ ///
+ /// The builder for the entity type being configured.
+ /// The mapping strategy for the derived types.
+ /// Indicates whether the configuration was specified using a data annotation.
+ /// if the configuration can be applied.
+ public static bool CanSetMappingStrategy(
+ this IConventionEntityTypeBuilder entityTypeBuilder,
+ string? strategy,
+ bool fromDataAnnotation = false)
+ => entityTypeBuilder.CanSetAnnotation
+ (RelationalAnnotationNames.MappingStrategy, strategy, fromDataAnnotation);
+
///
/// Mark the table that this entity type is mapped to as excluded from migrations.
///
diff --git a/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs b/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs
index e14f264d4ad..40d5ae86af7 100644
--- a/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs
+++ b/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs
@@ -1451,7 +1451,7 @@ public static IEnumerable GetOverrides(thi
/// The property.
/// The property facet overrides.
public static IEnumerable GetOverrides(this IMutableProperty property)
- => ((IEnumerable?)RelationalPropertyOverrides.Get(property))
+ => RelationalPropertyOverrides.Get(property)?.Cast()
?? Enumerable.Empty();
///
@@ -1466,7 +1466,7 @@ public static IEnumerable GetOverrides(this
/// The property.
/// The property facet overrides.
public static IEnumerable GetOverrides(this IConventionProperty property)
- => ((IEnumerable?)RelationalPropertyOverrides.Get(property))
+ => RelationalPropertyOverrides.Get(property)?.Cast()
?? Enumerable.Empty();
///
@@ -1481,7 +1481,7 @@ public static IEnumerable GetOverrides(t
/// The property.
/// The property facet overrides.
public static IEnumerable GetOverrides(this IProperty property)
- => ((IEnumerable?)RelationalPropertyOverrides.Get(property))
+ => RelationalPropertyOverrides.Get(property)?.Cast()
?? Enumerable.Empty();
///
diff --git a/src/EFCore.Relational/Metadata/Conventions/EntityTypeHierarchyMappingConvention.cs b/src/EFCore.Relational/Metadata/Conventions/EntityTypeHierarchyMappingConvention.cs
index 0e5d5ef3eb4..0b5b3c92df4 100644
--- a/src/EFCore.Relational/Metadata/Conventions/EntityTypeHierarchyMappingConvention.cs
+++ b/src/EFCore.Relational/Metadata/Conventions/EntityTypeHierarchyMappingConvention.cs
@@ -40,6 +40,7 @@ public virtual void ProcessModelFinalizing(
IConventionModelBuilder modelBuilder,
IConventionContext context)
{
+ var allRoots = new HashSet();
var nonTphRoots = new HashSet();
foreach (var entityType in modelBuilder.Metadata.GetEntityTypes())
@@ -49,15 +50,24 @@ public virtual void ProcessModelFinalizing(
continue;
}
+ var root = entityType.GetRootType();
+ allRoots.Add(root);
var mappingStrategy = (string?)entityType[RelationalAnnotationNames.MappingStrategy];
if (mappingStrategy == null)
{
- mappingStrategy = (string?)entityType.GetRootType()[RelationalAnnotationNames.MappingStrategy];
+ mappingStrategy = (string?)root[RelationalAnnotationNames.MappingStrategy];
+ if (mappingStrategy == null
+ && root.GetDiscriminatorPropertyConfigurationSource() == ConfigurationSource.Explicit)
+ {
+ mappingStrategy = RelationalAnnotationNames.TphMappingStrategy;
+ root.Builder.UseMappingStrategy(RelationalAnnotationNames.TphMappingStrategy);
+ continue;
+ }
}
if (mappingStrategy == RelationalAnnotationNames.TpcMappingStrategy)
{
- nonTphRoots.Add(entityType.GetRootType());
+ nonTphRoots.Add(root);
continue;
}
@@ -70,6 +80,7 @@ public virtual void ProcessModelFinalizing(
|| entityType.GetSchema() != entityType.BaseType.GetSchema())
{
mappingStrategy = RelationalAnnotationNames.TptMappingStrategy;
+ root.Builder.UseMappingStrategy(mappingStrategy);
}
}
@@ -96,7 +107,7 @@ public virtual void ProcessModelFinalizing(
}
}
- nonTphRoots.Add(entityType.GetRootType());
+ nonTphRoots.Add(root);
continue;
}
}
@@ -106,13 +117,20 @@ public virtual void ProcessModelFinalizing(
&& (viewName != entityType.BaseType.GetViewName()
|| entityType.GetViewSchema() != entityType.BaseType.GetViewSchema()))
{
- nonTphRoots.Add(entityType.GetRootType());
+ nonTphRoots.Add(root);
+ continue;
}
}
foreach (var root in nonTphRoots)
{
+ allRoots.Remove(root);
root.Builder.HasNoDiscriminator();
}
+
+ foreach (var root in allRoots)
+ {
+ root.Builder.UseMappingStrategy(RelationalAnnotationNames.TphMappingStrategy);
+ }
}
}
diff --git a/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilder.cs b/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilder.cs
index 33d2dfcb622..e3812030b50 100644
--- a/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilder.cs
+++ b/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilder.cs
@@ -83,6 +83,8 @@ public override ConventionSet CreateConventionSet()
conventionSet.EntityTypeBaseTypeChangedConventions.Add(checkConstraintConvention);
conventionSet.EntityTypeBaseTypeChangedConventions.Add(triggerConvention);
+ conventionSet.EntityTypeAnnotationChangedConventions.Add(tableNameFromDbSetConvention);
+
ReplaceConvention(conventionSet.ForeignKeyPropertiesChangedConventions, valueGenerationConvention);
ReplaceConvention(conventionSet.ForeignKeyOwnershipChangedConventions, valueGenerationConvention);
diff --git a/src/EFCore.Relational/Metadata/Conventions/TableNameFromDbSetConvention.cs b/src/EFCore.Relational/Metadata/Conventions/TableNameFromDbSetConvention.cs
index b063958ca3a..6c740d98fbd 100644
--- a/src/EFCore.Relational/Metadata/Conventions/TableNameFromDbSetConvention.cs
+++ b/src/EFCore.Relational/Metadata/Conventions/TableNameFromDbSetConvention.cs
@@ -12,6 +12,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions;
public class TableNameFromDbSetConvention :
IEntityTypeAddedConvention,
IEntityTypeBaseTypeChangedConvention,
+ IEntityTypeAnnotationChangedConvention,
IModelFinalizingConvention
{
private readonly IDictionary _sets;
@@ -63,13 +64,7 @@ public TableNameFromDbSetConvention(
///
protected virtual RelationalConventionSetBuilderDependencies RelationalDependencies { get; }
- ///
- /// Called after the base type of an entity type changes.
- ///
- /// The builder for the entity type.
- /// The new base entity type.
- /// The old base entity type.
- /// Additional information associated with convention execution.
+ ///
public virtual void ProcessEntityTypeBaseTypeChanged(
IConventionEntityTypeBuilder entityTypeBuilder,
IConventionEntityType? newBaseType,
@@ -79,7 +74,9 @@ public virtual void ProcessEntityTypeBaseTypeChanged(
var entityType = entityTypeBuilder.Metadata;
if (oldBaseType == null
- && newBaseType != null)
+ && newBaseType != null
+ && (entityType.GetMappingStrategy() ?? RelationalAnnotationNames.TphMappingStrategy)
+ == RelationalAnnotationNames.TphMappingStrategy)
{
entityTypeBuilder.HasNoAnnotation(RelationalAnnotationNames.TableName);
}
@@ -92,24 +89,46 @@ public virtual void ProcessEntityTypeBaseTypeChanged(
}
}
- ///
- /// Called after an entity type is added to the model.
- ///
- /// The builder for the entity type.
- /// Additional information associated with convention execution.
+ ///
public virtual void ProcessEntityTypeAdded(
IConventionEntityTypeBuilder entityTypeBuilder,
IConventionContext context)
{
var entityType = entityTypeBuilder.Metadata;
- if (entityType.BaseType == null
- && !entityType.HasSharedClrType
+ if (!entityType.HasSharedClrType
+ && (entityType.BaseType == null
+ || (entityType.GetMappingStrategy() ?? RelationalAnnotationNames.TphMappingStrategy)
+ != RelationalAnnotationNames.TphMappingStrategy)
&& _sets.TryGetValue(entityType.ClrType, out var setName))
{
entityTypeBuilder.ToTable(setName);
}
}
+ ///
+ public virtual void ProcessEntityTypeAnnotationChanged(
+ IConventionEntityTypeBuilder entityTypeBuilder,
+ string name,
+ IConventionAnnotation? annotation,
+ IConventionAnnotation? oldAnnotation,
+ IConventionContext context)
+ {
+ if (name == RelationalAnnotationNames.MappingStrategy
+ && annotation != null
+ && (entityTypeBuilder.Metadata.GetMappingStrategy() ?? RelationalAnnotationNames.TphMappingStrategy)
+ != RelationalAnnotationNames.TphMappingStrategy)
+ {
+ foreach (var deriverEntityType in entityTypeBuilder.Metadata.GetDerivedTypesInclusive())
+ {
+ if (!deriverEntityType.HasSharedClrType
+ && _sets.TryGetValue(deriverEntityType.ClrType, out var setName))
+ {
+ deriverEntityType.Builder.ToTable(setName);
+ }
+ }
+ }
+ }
+
///
public virtual void ProcessModelFinalizing(
IConventionModelBuilder modelBuilder,
diff --git a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs
index d507f8515a6..09cb794579e 100644
--- a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs
+++ b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs
@@ -18,6 +18,7 @@ public class CSharpMigrationsGeneratorTest
{
private static readonly string _nl = Environment.NewLine;
private static readonly string _toTable = _nl + @"entityTypeBuilder.ToTable(""WithAnnotations"")";
+ private static readonly string _toNullTable = _nl + @"entityTypeBuilder.ToTable((string)null)";
[ConditionalFact]
public void Test_new_annotations_handled_for_entity_types()
@@ -139,19 +140,22 @@ public void Test_new_annotations_handled_for_entity_types()
#pragma warning disable CS0612 // Type or member is obsolete
CoreAnnotationNames.DefiningQuery,
#pragma warning restore CS0612 // Type or member is obsolete
- (Expression.Lambda(Expression.Constant(null)), "")
+ (Expression.Lambda(Expression.Constant(null)), _toNullTable)
},
{
RelationalAnnotationNames.ViewName,
- ("MyView", _nl + "entityTypeBuilder." + nameof(RelationalEntityTypeBuilderExtensions.ToView) + @"(""MyView"")")
+ ("MyView", _toNullTable + ";" + _nl + _nl
+ + "entityTypeBuilder." + nameof(RelationalEntityTypeBuilderExtensions.ToView) + @"(""MyView"")")
},
{
RelationalAnnotationNames.FunctionName,
- (null, _nl + "entityTypeBuilder." + nameof(RelationalEntityTypeBuilderExtensions.ToFunction) + @"(null)")
+ (null, _toNullTable + ";" + _nl + _nl
+ + "entityTypeBuilder." + nameof(RelationalEntityTypeBuilderExtensions.ToFunction) + @"(null)")
},
{
RelationalAnnotationNames.SqlQuery,
- (null, _nl + "entityTypeBuilder." + nameof(RelationalEntityTypeBuilderExtensions.ToSqlQuery) + @"(null)")
+ (null, _toNullTable + ";" + _nl + _nl
+ + "entityTypeBuilder." + nameof(RelationalEntityTypeBuilderExtensions.ToSqlQuery) + @"(null)")
}
};
diff --git a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs
index 3645ca93e4f..109664c73ec 100644
--- a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs
+++ b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs
@@ -215,10 +215,13 @@ public enum TestEnum
Value2
}
- private class BaseEntity
+ private abstract class AbstractBase
{
public int Id { get; set; }
-
+ }
+
+ private class BaseEntity : AbstractBase
+ {
public string Discriminator { get; set; }
}
@@ -455,11 +458,12 @@ public virtual void Entities_are_stored_in_model_snapshot_for_TPT()
builder.Entity()
.ToTable("DerivedEntity", "foo");
builder.Entity();
+ builder.Entity();
},
AddBoilerPlate(
GetHeading()
+ @"
- modelBuilder.Entity(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+BaseEntity"", b =>
+ modelBuilder.Entity(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+AbstractBase"", b =>
{
b.Property(""Id"")
.ValueGeneratedOnAdd()
@@ -467,11 +471,17 @@ public virtual void Entities_are_stored_in_model_snapshot_for_TPT()
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1);
- b.Property(""Discriminator"")
- .HasColumnType(""nvarchar(max)"");
-
b.HasKey(""Id"");
+ b.ToTable(""AbstractBase"");
+
+ b.UseTptMappingStrategy();
+ });
+
+ modelBuilder.Entity(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+BaseEntity"", b =>
+ {
+ b.HasBaseType(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+AbstractBase"");
+
b.ToTable(""BaseEntity"");
});
@@ -493,14 +503,21 @@ public virtual void Entities_are_stored_in_model_snapshot_for_TPT()
.OnDelete(DeleteBehavior.ClientCascade)
.IsRequired();
});"),
- o =>
+ model =>
{
- Assert.Equal(4, o.GetAnnotations().Count());
+ Assert.Equal(4, model.GetAnnotations().Count());
+ Assert.Equal(3, model.GetEntityTypes().Count());
- Assert.Equal(
- "DerivedEntity",
- o.FindEntityType("Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+DerivedEntity")
- .GetTableName());
+ var abstractBase = model.FindEntityType("Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+AbstractBase");
+ Assert.Equal("AbstractBase", abstractBase.GetTableName());
+ Assert.Equal("TPT", abstractBase.GetMappingStrategy());
+
+ var baseType = model.FindEntityType("Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+BaseEntity");
+ Assert.Equal("BaseEntity", baseType.GetTableName());
+
+ var derived = model.FindEntityType("Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+DerivedEntity");
+ Assert.Equal("DerivedEntity", derived.GetTableName());
+ Assert.Equal("foo", derived.GetSchema());
});
[ConditionalFact]
@@ -529,6 +546,8 @@ public virtual void Entities_are_stored_in_model_snapshot_for_TPT_with_one_exclu
b.HasKey(""Id"");
b.ToTable(""BaseEntity"");
+
+ b.UseTptMappingStrategy();
});
modelBuilder.Entity(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+DerivedEntity"", b =>
@@ -576,6 +595,8 @@ public void Views_are_stored_in_the_model_snapshot()
b.HasKey(""Id"");
+ b.ToTable((string)null);
+
b.ToView(""EntityWithOneProperty"", (string)null);
});"),
o => Assert.Equal("EntityWithOneProperty", o.GetEntityTypes().Single().GetViewName()));
@@ -596,6 +617,8 @@ public void Views_with_schemas_are_stored_in_the_model_snapshot()
b.HasKey(""Id"");
+ b.ToTable((string)null);
+
b.ToView(""EntityWithOneProperty"", ""ViewSchema"");
});"),
o =>
@@ -612,12 +635,13 @@ public virtual void Entities_are_stored_in_model_snapshot_for_TPC()
builder.Entity()
.ToTable("DerivedEntity", "foo")
.ToView("DerivedView", "foo");
- builder.Entity().UseTpcMappingStrategy();
+ builder.Entity();
+ builder.Entity().UseTpcMappingStrategy();
},
AddBoilerPlate(
GetHeading()
+ @"
- modelBuilder.Entity(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+BaseEntity"", b =>
+ modelBuilder.Entity(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+AbstractBase"", b =>
{
b.Property(""Id"")
.ValueGeneratedOnAdd()
@@ -625,16 +649,20 @@ public virtual void Entities_are_stored_in_model_snapshot_for_TPC()
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1);
- b.Property(""Discriminator"")
- .HasColumnType(""nvarchar(max)"");
-
b.HasKey(""Id"");
- b.ToTable(""BaseEntity"");
+ b.ToTable((string)null);
b.UseTpcMappingStrategy();
});
+ modelBuilder.Entity(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+BaseEntity"", b =>
+ {
+ b.HasBaseType(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+AbstractBase"");
+
+ b.ToTable(""BaseEntity"");
+ });
+
modelBuilder.Entity(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+DerivedEntity"", b =>
{
b.HasBaseType(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+BaseEntity"");
@@ -646,11 +674,21 @@ public virtual void Entities_are_stored_in_model_snapshot_for_TPC()
b.ToView(""DerivedView"", ""foo"");
});"),
- o =>
+ model =>
{
- Assert.Equal(4, o.GetAnnotations().Count());
+ Assert.Equal(4, model.GetAnnotations().Count());
+ Assert.Equal(3, model.GetEntityTypes().Count());
+
+ var abstractBase = model.FindEntityType("Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+AbstractBase");
+ Assert.Null(abstractBase.GetTableName());
+ Assert.Null(abstractBase.GetViewName());
+ Assert.Equal("TPC", abstractBase.GetMappingStrategy());
- var derived = o.FindEntityType("Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+DerivedEntity");
+ var baseType = model.FindEntityType("Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+BaseEntity");
+ Assert.Equal("BaseEntity", baseType.GetTableName());
+ Assert.Null(baseType.GetViewName());
+
+ var derived = model.FindEntityType("Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+DerivedEntity");
Assert.Equal("DerivedEntity", derived.GetTableName());
Assert.Equal("DerivedView", derived.GetViewName());
});
@@ -979,6 +1017,8 @@ public virtual void Entity_splitting_is_stored_in_snapshot_with_views()
b.HasKey(""Id"");
+ b.ToTable((string)null);
+
b.ToView(""EntityWithOneProperty"", null, v =>
{
v.Property(""Shadow"");
@@ -1002,6 +1042,8 @@ public virtual void Entity_splitting_is_stored_in_snapshot_with_views()
b1.HasKey(""Id"");
+ b1.ToTable((string)null);
+
b1.ToView(""EntityWithOneProperty"", null, v =>
{
v.Property(""AlternateId"")
@@ -1132,6 +1174,8 @@ public void Entity_types_mapped_to_functions_are_stored_in_the_model_snapshot()
b.Property(""Something"")
.HasColumnType(""nvarchar(max)"");
+ b.ToTable((string)null);
+
b.ToFunction(""GetCount"");
});"),
o => Assert.Equal("GetCount", o.GetEntityTypes().Single().GetFunctionName()));
@@ -1150,6 +1194,8 @@ public void Entity_types_mapped_to_queries_are_stored_in_the_model_snapshot()
b.HasKey(""Id"");
+ b.ToTable((string)null);
+
b.ToSqlQuery(""query"");
});"),
o => Assert.Equal("query", o.GetEntityTypes().Single().GetSqlQuery()));
@@ -1264,6 +1310,8 @@ public virtual void CheckConstraint_is_only_stored_in_snapshot_once_for_TPH()
b.ToTable(""BaseEntity"");
b.HasDiscriminator(""Discriminator"").HasValue(""BaseEntity"");
+
+ b.UseTphMappingStrategy();
});
modelBuilder.Entity(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+DerivedEntity"", b =>
@@ -1567,6 +1615,8 @@ public virtual void BaseType_is_stored_in_snapshot()
b.ToTable(""BaseEntity"");
b.HasDiscriminator(""Discriminator"").HasValue(""BaseEntity"");
+
+ b.UseTphMappingStrategy();
});
modelBuilder.Entity(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+AnotherDerivedEntity"", b =>
@@ -1634,6 +1684,8 @@ public virtual void Discriminator_annotations_are_stored_in_snapshot()
b.ToTable(""BaseEntity"");
b.HasDiscriminator(""Discriminator"").IsComplete(true).HasValue(""BaseEntity"");
+
+ b.UseTphMappingStrategy();
});
modelBuilder.Entity(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+AnotherDerivedEntity"", b =>
@@ -1707,6 +1759,8 @@ public virtual void Converted_discriminator_annotations_are_stored_in_snapshot()
b.ToTable(""BaseEntityWithStructDiscriminator"");
b.HasDiscriminator(""Discriminator"").IsComplete(true).HasValue(""Base"");
+
+ b.UseTphMappingStrategy();
});
modelBuilder.Entity(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+AnotherDerivedEntityWithStructDiscriminator"", b =>
@@ -3368,6 +3422,8 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b1.HasKey(""TestOwnerId"", ""Id"");
+ b1.ToTable((string)null);
+
b1.ToView(""OwnedView"", (string)null);
b1.WithOwner()
@@ -5810,6 +5866,8 @@ public virtual void Do_not_generate_entity_type_builder_again_if_no_foreign_key_
b.ToTable(""BaseType"");
b.HasDiscriminator(""Discriminator"").HasValue(""BaseType"");
+
+ b.UseTphMappingStrategy();
});
modelBuilder.Entity(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+EntityWithOneProperty"", b =>
diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs
index e7bb3a7ec0a..ded382af523 100644
--- a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs
+++ b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs
@@ -1048,6 +1048,7 @@ 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"", ""PrincipalDerived"");
@@ -1166,6 +1167,7 @@ public static RuntimeSkipNavigation CreateSkipNavigation1(RuntimeEntityType decl
public static void CreateAnnotations(RuntimeEntityType runtimeEntityType)
{
runtimeEntityType.AddAnnotation(""Relational:FunctionName"", null);
+ runtimeEntityType.AddAnnotation(""Relational:MappingStrategy"", ""TPT"");
runtimeEntityType.AddAnnotation(""Relational:Schema"", ""mySchema"");
runtimeEntityType.AddAnnotation(""Relational:SqlQuery"", null);
runtimeEntityType.AddAnnotation(""Relational:TableName"", ""PrincipalBase"");
diff --git a/test/EFCore.Relational.Specification.Tests/EntitySplittingTestBase.cs b/test/EFCore.Relational.Specification.Tests/EntitySplittingTestBase.cs
index 8bb4e5698c6..7adb3cc2777 100644
--- a/test/EFCore.Relational.Specification.Tests/EntitySplittingTestBase.cs
+++ b/test/EFCore.Relational.Specification.Tests/EntitySplittingTestBase.cs
@@ -63,15 +63,21 @@ protected virtual void OnModelCreating(ModelBuilder modelBuilder)
});
}
- protected async Task InitializeAsync(Action onModelCreating, bool sensitiveLogEnabled = true)
- => ContextFactory = await InitializeAsync(
+ protected async Task InitializeAsync(
+ Action onModelCreating,
+ Action onConfiguring = null,
+ Action seed = null,
+ bool sensitiveLogEnabled = true)
+ => ContextFactory = await InitializeAsync(
onModelCreating,
+ seed: seed,
shouldLogCategory: _ => true,
onConfiguring: options =>
{
options.ConfigureWarnings(w => w.Log(RelationalEventId.OptionalDependentWithAllNullPropertiesWarning))
.ConfigureWarnings(w => w.Log(RelationalEventId.OptionalDependentWithoutIdentifyingPropertyWarning))
.EnableSensitiveDataLogging(sensitiveLogEnabled);
+ onConfiguring?.Invoke(options);
}
);
diff --git a/test/EFCore.Relational.Specification.Tests/Query/TPCGearsOfWarQueryRelationalFixture.cs b/test/EFCore.Relational.Specification.Tests/Query/TPCGearsOfWarQueryRelationalFixture.cs
index f05df02116b..fd03855e562 100644
--- a/test/EFCore.Relational.Specification.Tests/Query/TPCGearsOfWarQueryRelationalFixture.cs
+++ b/test/EFCore.Relational.Specification.Tests/Query/TPCGearsOfWarQueryRelationalFixture.cs
@@ -20,8 +20,7 @@ protected override bool ShouldLogCategory(string logCategory)
public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder)
=> base.AddOptions(builder).ConfigureWarnings(
- w =>
- w.Log(RelationalEventId.ForeignKeyTpcPrincipalWarning));
+ w => w.Log(RelationalEventId.ForeignKeyTpcPrincipalWarning));
protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context)
{
@@ -31,12 +30,8 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con
modelBuilder.Entity().UseTpcMappingStrategy();
modelBuilder.Entity().UseTpcMappingStrategy();
- modelBuilder.Entity().ToTable("Gears");
- modelBuilder.Entity().ToTable("Officers");
-
modelBuilder.Entity().ToTable("LocustHordes");
- modelBuilder.Entity().ToTable("LocustLeaders");
modelBuilder.Entity().ToTable("LocustCommanders");
}
}
diff --git a/test/EFCore.Relational.Specification.Tests/Query/TPCInheritanceQueryFixture.cs b/test/EFCore.Relational.Specification.Tests/Query/TPCInheritanceQueryFixture.cs
index a2139b79b18..333f775354f 100644
--- a/test/EFCore.Relational.Specification.Tests/Query/TPCInheritanceQueryFixture.cs
+++ b/test/EFCore.Relational.Specification.Tests/Query/TPCInheritanceQueryFixture.cs
@@ -20,7 +20,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con
{
base.OnModelCreating(modelBuilder, context);
- // Configure TPT for hierarchies
+ // Configure TPC for hierarchies
modelBuilder.Entity().UseTpcMappingStrategy();
modelBuilder.Entity().UseTpcMappingStrategy();
modelBuilder.Entity().UseTpcMappingStrategy();
@@ -31,12 +31,10 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con
modelBuilder.Entity().Property(e => e.Id).ValueGeneratedNever();
modelBuilder.Entity().ToTable("Birds");
- modelBuilder.Entity().ToTable("Eagle");
modelBuilder.Entity().ToTable("Kiwi");
modelBuilder.Entity().Property(e => e.Species).HasMaxLength(100);
modelBuilder.Entity().HasMany(e => e.Prey).WithOne().HasForeignKey(e => e.EagleId).IsRequired(false);
- modelBuilder.Entity().ToTable("Drinks");
modelBuilder.Entity().ToTable("Coke");
modelBuilder.Entity().ToTable("Lilt");
modelBuilder.Entity().ToTable("Tea");
diff --git a/test/EFCore.Relational.Specification.Tests/Query/TPTGearsOfWarQueryRelationalFixture.cs b/test/EFCore.Relational.Specification.Tests/Query/TPTGearsOfWarQueryRelationalFixture.cs
index 02a0a44096c..b6a64a0ea02 100644
--- a/test/EFCore.Relational.Specification.Tests/Query/TPTGearsOfWarQueryRelationalFixture.cs
+++ b/test/EFCore.Relational.Specification.Tests/Query/TPTGearsOfWarQueryRelationalFixture.cs
@@ -22,13 +22,10 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con
{
base.OnModelCreating(modelBuilder, context);
- modelBuilder.Entity().ToTable("Gears");
- modelBuilder.Entity().ToTable("Officers");
+ modelBuilder.Entity().UseTptMappingStrategy();
- modelBuilder.Entity().ToTable("Factions");
modelBuilder.Entity().ToTable("LocustHordes");
- modelBuilder.Entity().ToTable("LocustLeaders");
modelBuilder.Entity().ToTable("LocustCommanders");
}
}
diff --git a/test/EFCore.Relational.Specification.Tests/Query/TPTInheritanceQueryFixture.cs b/test/EFCore.Relational.Specification.Tests/Query/TPTInheritanceQueryFixture.cs
index 117da367aa7..385a84d0c61 100644
--- a/test/EFCore.Relational.Specification.Tests/Query/TPTInheritanceQueryFixture.cs
+++ b/test/EFCore.Relational.Specification.Tests/Query/TPTInheritanceQueryFixture.cs
@@ -20,20 +20,16 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con
{
base.OnModelCreating(modelBuilder, context);
- modelBuilder.Entity().ToTable("Plants");
modelBuilder.Entity().ToTable("Flowers");
modelBuilder.Entity().ToTable("Roses");
modelBuilder.Entity().ToTable("Daisies");
modelBuilder.Entity().Property(e => e.Id).ValueGeneratedNever();
- modelBuilder.Entity().ToTable("Animals");
modelBuilder.Entity().ToTable("Birds");
- modelBuilder.Entity().ToTable("Eagle");
modelBuilder.Entity().ToTable("Kiwi");
modelBuilder.Entity().Property(e => e.Species).HasMaxLength(100);
modelBuilder.Entity().HasMany(e => e.Prey).WithOne().HasForeignKey(e => e.EagleId).IsRequired(false);
- modelBuilder.Entity().ToTable("Drinks");
modelBuilder.Entity().ToTable("Coke");
modelBuilder.Entity().ToTable("Lilt");
modelBuilder.Entity().ToTable("Tea");
diff --git a/test/EFCore.Specification.Tests/TestModels/GearsOfWarModel/GearsOfWarContext.cs b/test/EFCore.Specification.Tests/TestModels/GearsOfWarModel/GearsOfWarContext.cs
index 07b412e130c..906c9a67340 100644
--- a/test/EFCore.Specification.Tests/TestModels/GearsOfWarModel/GearsOfWarContext.cs
+++ b/test/EFCore.Specification.Tests/TestModels/GearsOfWarModel/GearsOfWarContext.cs
@@ -11,6 +11,7 @@ public GearsOfWarContext(DbContextOptions options)
}
public DbSet Gears { get; set; }
+ public DbSet Officers { get; set; }
public DbSet Squads { get; set; }
public DbSet Tags { get; set; }
public DbSet Weapons { get; set; }
diff --git a/test/EFCore.SqlServer.FunctionalTests/EntitySplittingSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/EntitySplittingSqlServerTest.cs
index 881e49d40ea..73bbaf2ddd5 100644
--- a/test/EFCore.SqlServer.FunctionalTests/EntitySplittingSqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/EntitySplittingSqlServerTest.cs
@@ -9,12 +9,59 @@ public EntitySplittingSqlServerTest(ITestOutputHelper testOutputHelper)
: base(testOutputHelper)
{
}
-
- protected override ITestStoreFactory TestStoreFactory
- => SqlServerTestStoreFactory.Instance;
- protected override void OnModelCreating(ModelBuilder modelBuilder)
+ [ConditionalFact(Skip = "Entity splitting query Issue #620")]
+ public virtual async Task Can_roundtrip_with_triggers()
{
- base.OnModelCreating(modelBuilder);
+ await InitializeAsync(modelBuilder =>
+ {
+ OnModelCreating(modelBuilder);
+
+ modelBuilder.Entity(
+ ob =>
+ {
+ ob.SplitToTable(
+ "MeterReadingDetails", t =>
+ {
+ t.HasTrigger("MeterReadingsDetails_Trigger");
+ });
+ });
+ },
+ sensitiveLogEnabled: false,
+ seed: c =>
+ {
+ c.Database.ExecuteSqlRaw($@"
+CREATE OR ALTER TRIGGER [MeterReadingsDetails_Trigger]
+ON [MeterReadingDetails]
+FOR INSERT, UPDATE, DELETE AS
+BEGIN
+ IF @@ROWCOUNT = 0
+ return
+END");
+ });
+
+ await using (var context = CreateContext())
+ {
+ var meterReading = new MeterReading { ReadingStatus = MeterReadingStatus.NotAccesible, CurrentRead = "100" };
+
+ context.Add(meterReading);
+
+ TestSqlLoggerFactory.Clear();
+
+ await context.SaveChangesAsync();
+
+ Assert.Empty(TestSqlLoggerFactory.Log.Where(l => l.Level == LogLevel.Warning));
+ }
+
+ await using (var context = CreateContext())
+ {
+ var reading = await context.MeterReadings.SingleAsync();
+
+ Assert.Equal(MeterReadingStatus.NotAccesible, reading.ReadingStatus);
+ Assert.Equal("100", reading.CurrentRead);
+ }
}
+
+ protected override ITestStoreFactory TestStoreFactory
+ => SqlServerTestStoreFactory.Instance;
}
diff --git a/test/EFCore.SqlServer.FunctionalTests/SqlServerTriggersTest.cs b/test/EFCore.SqlServer.FunctionalTests/SqlServerTriggersTest.cs
index 344e5b10b1a..ba68ce68328 100644
--- a/test/EFCore.SqlServer.FunctionalTests/SqlServerTriggersTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/SqlServerTriggersTest.cs
@@ -1,8 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-
-
// ReSharper disable ParameterOnlyUsedForPreconditionCheck.Local
// ReSharper disable InconsistentNaming
namespace Microsoft.EntityFrameworkCore;
diff --git a/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerTestModelBuilderExtensions.cs b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerTestModelBuilderExtensions.cs
index 030428c099a..68826a9ad65 100644
--- a/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerTestModelBuilderExtensions.cs
+++ b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerTestModelBuilderExtensions.cs
@@ -64,18 +64,18 @@ public static RelationalModelBuilderTest.TestTableBuilder IsTemporal IsTemporal(
this RelationalModelBuilderTest.TestTableBuilder builder,
- Action> buildAction)
+ Action> buildAction)
where TEntity : class
{
switch (builder)
{
case IInfrastructure> genericBuilder:
genericBuilder.Instance.IsTemporal(
- b => buildAction(new SqlServerModelBuilderGenericTest.GenericTestTemporalTableBuilder(b)));
+ b => buildAction(new SqlServerModelBuilderTestBase.GenericTestTemporalTableBuilder(b)));
break;
case IInfrastructure nongenericBuilder:
nongenericBuilder.Instance.IsTemporal(
- b => buildAction(new SqlServerModelBuilderGenericTest.NonGenericTestTemporalTableBuilder(b)));
+ b => buildAction(new SqlServerModelBuilderTestBase.NonGenericTestTemporalTableBuilder(b)));
break;
}