diff --git a/src/EFCore.Relational/Extensions/RelationalTriggerExtensions.cs b/src/EFCore.Relational/Extensions/RelationalTriggerExtensions.cs
index eece3f04c7c..4c27bf2a617 100644
--- a/src/EFCore.Relational/Extensions/RelationalTriggerExtensions.cs
+++ b/src/EFCore.Relational/Extensions/RelationalTriggerExtensions.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.
-using Microsoft.EntityFrameworkCore.Metadata.Internal;
-
// ReSharper disable once CheckNamespace
namespace Microsoft.EntityFrameworkCore;
diff --git a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs
index db46effec2a..90661d5ef40 100644
--- a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs
+++ b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs
@@ -1117,6 +1117,19 @@ protected virtual void ValidateSharedColumnsCompatibility(
continue;
}
+ if (property.DeclaringEntityType.IsAssignableFrom(duplicateProperty.DeclaringEntityType)
+ || duplicateProperty.DeclaringEntityType.IsAssignableFrom(property.DeclaringEntityType))
+ {
+ throw new InvalidOperationException(
+ RelationalStrings.DuplicateColumnNameSameHierarchy(
+ duplicateProperty.DeclaringEntityType.DisplayName(),
+ duplicateProperty.Name,
+ property.DeclaringEntityType.DisplayName(),
+ property.Name,
+ columnName,
+ storeObject.DisplayName()));
+ }
+
ValidateCompatible(property, duplicateProperty, columnName, storeObject, logger);
}
diff --git a/src/EFCore.Relational/Metadata/Conventions/BlankTriggerAddingConvention.cs b/src/EFCore.Relational/Metadata/Conventions/BlankTriggerAddingConvention.cs
new file mode 100644
index 00000000000..7e617d5ff7c
--- /dev/null
+++ b/src/EFCore.Relational/Metadata/Conventions/BlankTriggerAddingConvention.cs
@@ -0,0 +1,62 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.EntityFrameworkCore.Metadata.Conventions;
+
+///
+/// A convention that makes sure there is a trigger on all entity types.
+///
+///
+/// See Model building conventions for more information and examples.
+///
+public class BlankTriggerAddingConvention : IModelFinalizingConvention
+{
+ ///
+ /// Creates a new instance of .
+ ///
+ /// Parameter object containing dependencies for this convention.
+ /// Parameter object containing relational dependencies for this convention.
+ public BlankTriggerAddingConvention(
+ ProviderConventionSetBuilderDependencies dependencies,
+ RelationalConventionSetBuilderDependencies relationalDependencies)
+ {
+ Dependencies = dependencies;
+ RelationalDependencies = relationalDependencies;
+ }
+
+ ///
+ /// Dependencies for this convention.
+ ///
+ protected virtual ProviderConventionSetBuilderDependencies Dependencies { get; }
+
+ ///
+ /// Relational provider-specific dependencies for this service.
+ ///
+ protected virtual RelationalConventionSetBuilderDependencies RelationalDependencies { get; }
+
+ ///
+ /// Called when a model is being finalized.
+ ///
+ /// The builder for the model.
+ /// Additional information associated with convention execution.
+ public virtual void ProcessModelFinalizing(IConventionModelBuilder modelBuilder, IConventionContext context)
+ {
+ foreach (var entityType in modelBuilder.Metadata.GetEntityTypes())
+ {
+ var table = StoreObjectIdentifier.Create(entityType, StoreObjectType.Table);
+ if (table != null
+ && entityType.GetDeclaredTriggers().All(t => t.GetName(table.Value) == null))
+ {
+ entityType.Builder.HasTrigger(table.Value.Name + "_Trigger");
+ }
+
+ foreach (var fragment in entityType.GetMappingFragments(StoreObjectType.Table))
+ {
+ if (entityType.GetDeclaredTriggers().All(t => t.GetName(fragment.StoreObject) == null))
+ {
+ entityType.Builder.HasTrigger(fragment.StoreObject.Name + "_Trigger");
+ }
+ }
+ }
+ }
+}
diff --git a/src/EFCore.Relational/Metadata/Conventions/TableSharingConcurrencyTokenConvention.cs b/src/EFCore.Relational/Metadata/Conventions/TableSharingConcurrencyTokenConvention.cs
index 99b7f6f44b0..0d9f1a621ef 100644
--- a/src/EFCore.Relational/Metadata/Conventions/TableSharingConcurrencyTokenConvention.cs
+++ b/src/EFCore.Relational/Metadata/Conventions/TableSharingConcurrencyTokenConvention.cs
@@ -99,7 +99,7 @@ public virtual void ProcessModelFinalizing(
continue;
}
- RemoveDerivedEntityTypes(entityTypesMissingConcurrencyColumn);
+ RemoveDerivedEntityTypes(entityTypesMissingConcurrencyColumn, mappedTypes);
foreach (var (conventionEntityType, exampleProperty) in entityTypesMissingConcurrencyColumn)
{
@@ -194,10 +194,11 @@ public static bool IsConcurrencyTokenMissing(
{
var declaringEntityType = mappedProperty.DeclaringEntityType;
if (declaringEntityType.IsAssignableFrom(entityType)
+ || entityType.IsAssignableFrom(declaringEntityType)
|| declaringEntityType.IsInOwnershipPath(entityType)
|| entityType.IsInOwnershipPath(declaringEntityType))
{
- // The concurrency token is on the base type or in the same aggregate
+ // The concurrency token is on the base type, derived type or in the same aggregate
propertyMissing = false;
continue;
}
@@ -220,21 +221,31 @@ public static bool IsConcurrencyTokenMissing(
return propertyMissing;
}
- private static void RemoveDerivedEntityTypes(Dictionary entityTypeDictionary)
+ private static void RemoveDerivedEntityTypes(
+ Dictionary entityTypeDictionary,
+ List mappedTypes)
{
- foreach (var entityType in entityTypeDictionary.Keys)
+ foreach (var (entityType, property) in entityTypeDictionary)
{
+ var removed = false;
var baseType = entityType.BaseType;
while (baseType != null)
{
if (entityTypeDictionary.ContainsKey(baseType))
{
entityTypeDictionary.Remove(entityType);
+ removed = true;
break;
}
baseType = baseType.BaseType;
}
+
+ if (!removed
+ && entityType.IsAssignableFrom(property.DeclaringEntityType))
+ {
+ entityTypeDictionary.Remove(entityType);
+ }
}
}
}
diff --git a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs
index 86b0ea4614f..a3a71a97123 100644
--- a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs
+++ b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs
@@ -422,7 +422,7 @@ public static string DuplicateColumnNameMaxLengthMismatch(object? entityType1, o
entityType1, property1, entityType2, property2, columnName, table, maxLength1, maxLength2);
///
- /// '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}', but are configured with different nullability settings.
+ /// '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}', but are configured with different column nullability settings.
///
public static string DuplicateColumnNameNullabilityMismatch(object? entityType1, object? property1, object? entityType2, object? property2, object? columnName, object? table)
=> string.Format(
@@ -453,6 +453,14 @@ public static string DuplicateColumnNameProviderTypeMismatch(object? entityType1
GetString("DuplicateColumnNameProviderTypeMismatch", nameof(entityType1), nameof(property1), nameof(entityType2), nameof(property2), nameof(columnName), nameof(table), nameof(type1), nameof(type2)),
entityType1, property1, entityType2, property2, columnName, table, type1, type2);
+ ///
+ /// '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}', but the properties are contained within the same hierarchy. All properties on an entity type must be mapped to unique different columns.
+ ///
+ public static string DuplicateColumnNameSameHierarchy(object? entityType1, object? property1, object? entityType2, object? property2, object? columnName, object? table)
+ => string.Format(
+ GetString("DuplicateColumnNameSameHierarchy", nameof(entityType1), nameof(property1), nameof(entityType2), nameof(property2), nameof(columnName), nameof(table)),
+ entityType1, property1, entityType2, property2, columnName, table);
+
///
/// '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}', but are configured with different scales ('{scale1}' and '{scale2}').
///
@@ -1575,14 +1583,6 @@ public static string StoredProcedureOutputParameterNotGenerated(object? entityTy
GetString("StoredProcedureOutputParameterNotGenerated", nameof(entityType), nameof(property), nameof(sproc)),
entityType, property, sproc);
- ///
- /// Stored procedure '{sproc]' was configured with a rows affected output parameter or return value, but a valid value was not found when executing the procedure.
- ///
- public static string StoredProcedureRowsAffectedNotPopulated(object? sproc)
- => string.Format(
- GetString("StoredProcedureRowsAffectedNotPopulated", nameof(sproc)),
- sproc);
-
///
/// The property '{propertySpecification}' has specific configuration for the stored procedure '{sproc}', but it isn't mapped to a parameter or a result column on that stored procedure. Remove the specific configuration, or map an entity type that contains this property to '{sproc}'.
///
@@ -1655,6 +1655,14 @@ public static string StoredProcedureResultColumnParameterConflict(object? entity
GetString("StoredProcedureResultColumnParameterConflict", nameof(entityType), nameof(property), nameof(sproc)),
entityType, property, sproc);
+ ///
+ /// Stored procedure '{sproc}' was configured with a rows affected output parameter or return value, but a valid value was not found when executing the procedure.
+ ///
+ public static string StoredProcedureRowsAffectedNotPopulated(object? sproc)
+ => string.Format(
+ GetString("StoredProcedureRowsAffectedNotPopulated", nameof(sproc)),
+ sproc);
+
///
/// The stored procedure '{sproc}' cannot be configured to return the rows affected because a rows affected parameter or a rows affected result column for this stored procedure already exists.
///
diff --git a/src/EFCore.Relational/Properties/RelationalStrings.resx b/src/EFCore.Relational/Properties/RelationalStrings.resx
index 3eb627796b8..df48b4a53a0 100644
--- a/src/EFCore.Relational/Properties/RelationalStrings.resx
+++ b/src/EFCore.Relational/Properties/RelationalStrings.resx
@@ -272,7 +272,7 @@
'{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}', but are configured with different maximum lengths ('{maxLength1}' and '{maxLength2}').
- '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}', but are configured with different nullability settings.
+ '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}', but are configured with different column nullability settings.
'{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}', but are configured to use different column orders ('{columnOrder1}' and '{columnOrder2}').
@@ -283,6 +283,9 @@
'{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}', but are configured to use differing provider types ('{type1}' and '{type2}').
+
+ '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}', but the properties are contained within the same hierarchy. All properties on an entity type must be mapped to unique different columns.
+
'{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}', but are configured with different scales ('{scale1}' and '{scale2}').
@@ -1008,9 +1011,6 @@
The property '{entityType}.{property}' is mapped to an output parameter of the stored procedure '{sproc}', but it is not configured as store-generated. Either configure it as store-generated or don't configure the parameter as output.
-
- Stored procedure '{sproc}' was configured with a rows affected output parameter or return value, but a valid value was not found when executing the procedure.
-
The property '{propertySpecification}' has specific configuration for the stored procedure '{sproc}', but it isn't mapped to a parameter or a result column on that stored procedure. Remove the specific configuration, or map an entity type that contains this property to '{sproc}'.
@@ -1038,6 +1038,9 @@
The property '{entityType}.{property}' is mapped to a result column of the stored procedure '{sproc}', but it is also mapped to an output parameter. A store-generated property can only be mapped to one of these.
+
+ Stored procedure '{sproc}' was configured with a rows affected output parameter or return value, but a valid value was not found when executing the procedure.
+
The stored procedure '{sproc}' cannot be configured to return the rows affected because a rows affected parameter or a rows affected result column for this stored procedure already exists.
diff --git a/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerModelValidator.cs b/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerModelValidator.cs
index 22a4c46891e..093687b789e 100644
--- a/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerModelValidator.cs
+++ b/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerModelValidator.cs
@@ -308,29 +308,11 @@ private static void ValidateTemporalPeriodProperty(IEntityType temporalEntityTyp
temporalEntityType.DisplayName(), periodProperty.Name));
}
- if (temporalEntityType.GetTableName() is string tableName)
+ if (periodProperty.ValueGenerated != ValueGenerated.OnAddOrUpdate)
{
- var storeObjectIdentifier = StoreObjectIdentifier.Table(tableName, temporalEntityType.GetSchema());
- var periodColumnName = periodProperty.GetColumnName(storeObjectIdentifier);
-
- var propertiesMappedToPeriodColumn = temporalEntityType.GetProperties().Where(
- p => p.Name != periodProperty.Name && p.GetColumnName(storeObjectIdentifier) == periodColumnName).ToList();
- foreach (var propertyMappedToPeriodColumn in propertiesMappedToPeriodColumn)
- {
- if (propertyMappedToPeriodColumn.ValueGenerated != ValueGenerated.OnAddOrUpdate)
- {
- throw new InvalidOperationException(
- SqlServerStrings.TemporalPropertyMappedToPeriodColumnMustBeValueGeneratedOnAddOrUpdate(
- temporalEntityType.DisplayName(), propertyMappedToPeriodColumn.Name, nameof(ValueGenerated.OnAddOrUpdate)));
- }
-
- if (propertyMappedToPeriodColumn.TryGetDefaultValue(out var _))
- {
- throw new InvalidOperationException(
- SqlServerStrings.TemporalPropertyMappedToPeriodColumnCantHaveDefaultValue(
- temporalEntityType.DisplayName(), propertyMappedToPeriodColumn.Name));
- }
- }
+ throw new InvalidOperationException(
+ SqlServerStrings.TemporalPropertyMappedToPeriodColumnMustBeValueGeneratedOnAddOrUpdate(
+ temporalEntityType.DisplayName(), periodProperty.Name, nameof(ValueGenerated.OnAddOrUpdate)));
}
// TODO: check that period property is excluded from query (once the annotation is added)
diff --git a/src/EFCore.SqlServer/Metadata/Builders/TemporalPeriodPropertyBuilder.cs b/src/EFCore.SqlServer/Metadata/Builders/TemporalPeriodPropertyBuilder.cs
index 65154026676..c2cbeb28dc9 100644
--- a/src/EFCore.SqlServer/Metadata/Builders/TemporalPeriodPropertyBuilder.cs
+++ b/src/EFCore.SqlServer/Metadata/Builders/TemporalPeriodPropertyBuilder.cs
@@ -9,7 +9,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Builders;
/// Instances of this class are returned from methods when using the API
/// and it is not designed to be directly constructed in your application code.
///
-public class TemporalPeriodPropertyBuilder
+public class TemporalPeriodPropertyBuilder : IInfrastructure
{
private readonly PropertyBuilder _propertyBuilder;
@@ -59,6 +59,9 @@ public virtual TemporalPeriodPropertyBuilder HasPrecision(int precision)
return this;
}
+ PropertyBuilder IInfrastructure.Instance
+ => _propertyBuilder;
+
#region Hidden System.Object members
///
diff --git a/src/EFCore.SqlServer/Properties/SqlServerStrings.Designer.cs b/src/EFCore.SqlServer/Properties/SqlServerStrings.Designer.cs
index 05880ce5dd9..ccbf7ea4af5 100644
--- a/src/EFCore.SqlServer/Properties/SqlServerStrings.Designer.cs
+++ b/src/EFCore.SqlServer/Properties/SqlServerStrings.Designer.cs
@@ -333,14 +333,6 @@ public static string TemporalPeriodPropertyMustBeNonNullableDateTime(object? ent
GetString("TemporalPeriodPropertyMustBeNonNullableDateTime", nameof(entityType), nameof(propertyName), nameof(dateTimeType)),
entityType, propertyName, dateTimeType);
- ///
- /// Property '{entityType}.{propertyName}' is mapped to the period column and can't have default value specified.
- ///
- public static string TemporalPropertyMappedToPeriodColumnCantHaveDefaultValue(object? entityType, object? propertyName)
- => string.Format(
- GetString("TemporalPropertyMappedToPeriodColumnCantHaveDefaultValue", nameof(entityType), nameof(propertyName)),
- entityType, propertyName);
-
///
/// Property '{entityType}.{propertyName}' is mapped to the period column and must have ValueGenerated set to '{valueGeneratedValue}'.
///
diff --git a/src/EFCore.SqlServer/Properties/SqlServerStrings.resx b/src/EFCore.SqlServer/Properties/SqlServerStrings.resx
index 1e30c0c3c20..9815e413d51 100644
--- a/src/EFCore.SqlServer/Properties/SqlServerStrings.resx
+++ b/src/EFCore.SqlServer/Properties/SqlServerStrings.resx
@@ -332,9 +332,6 @@
Period property '{entityType}.{propertyName}' must be non-nullable and of type '{dateTimeType}'.
-
- Property '{entityType}.{propertyName}' is mapped to the period column and can't have default value specified.
-
Property '{entityType}.{propertyName}' is mapped to the period column and must have ValueGenerated set to '{valueGeneratedValue}'.
diff --git a/src/EFCore/Extensions/Internal/DbContextExtensions.cs b/src/EFCore/Extensions/Internal/DbContextExtensions.cs
new file mode 100644
index 00000000000..e747f45c422
--- /dev/null
+++ b/src/EFCore/Extensions/Internal/DbContextExtensions.cs
@@ -0,0 +1,26 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+// ReSharper disable CheckNamespace
+
+namespace Microsoft.EntityFrameworkCore.Internal;
+
+///
+/// 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 static class DbContextExtensions
+{
+ ///
+ /// 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 static void ConfigureConventions(
+ this DbContext context,
+ ModelConfigurationBuilder configurationBuilder)
+ => context.ConfigureConventions(configurationBuilder);
+}
diff --git a/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs b/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs
index 378bb6eb950..e629cebace4 100644
--- a/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs
+++ b/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs
@@ -478,6 +478,20 @@ public virtual void Detects_incompatible_shared_columns_in_shared_table_with_dif
modelBuilder);
}
+ [ConditionalFact]
+ public virtual void Detects_properties_mapped_to_the_same_column_within_hierarchy()
+ {
+ var modelBuilder = CreateConventionModelBuilder();
+
+ modelBuilder.Entity().Property(a => a.P0).HasColumnName(nameof(A.P0));
+ modelBuilder.Entity().Property("PC").HasColumnName(nameof(A.P0));
+
+ VerifyError(
+ RelationalStrings.DuplicateColumnNameSameHierarchy(
+ nameof(A), nameof(A.P0), nameof(C), "PC", nameof(A.P0), nameof(A)),
+ modelBuilder);
+ }
+
[ConditionalFact]
public virtual void Detects_incompatible_shared_columns_in_shared_table_with_different_provider_types()
{
@@ -869,35 +883,18 @@ public virtual void Detects_unmapped_foreign_keys_in_entity_splitting()
LogLevel.Error);
}
-
-
- [ConditionalFact]
- public virtual void Detects_duplicate_column_names()
- {
- var modelBuilder = CreateConventionModelBuilder();
-
- modelBuilder.Entity().Property(b => b.Id).HasColumnName("Name");
- modelBuilder.Entity().Property(d => d.Name).HasColumnName("Name").IsRequired();
-
- VerifyError(
- RelationalStrings.DuplicateColumnNameDataTypeMismatch(
- nameof(Animal), nameof(Animal.Id),
- nameof(Animal), nameof(Animal.Name), "Name", nameof(Animal), "default_int_mapping", "just_string(max)"),
- modelBuilder);
- }
-
[ConditionalFact]
public virtual void Detects_duplicate_columns_in_derived_types_with_different_types()
{
var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity();
- modelBuilder.Entity().Property(c => c.Type).HasColumnName("Type").IsRequired();
- modelBuilder.Entity().Property(d => d.Type).HasColumnName("Type");
+ modelBuilder.Entity().Property(c => c.Type).HasColumnName("Type").HasColumnType("someInt");
+ modelBuilder.Entity().Property(d => d.Type).HasColumnName("Type").HasColumnType("default_int_mapping");
VerifyError(
RelationalStrings.DuplicateColumnNameDataTypeMismatch(
- nameof(Cat), nameof(Cat.Type), nameof(Dog), nameof(Dog.Type), nameof(Cat.Type), nameof(Animal), "just_string(max)",
+ nameof(Cat), nameof(Cat.Type), nameof(Dog), nameof(Dog.Type), nameof(Cat.Type), nameof(Animal), "someInt",
"default_int_mapping"), modelBuilder);
}
@@ -1018,16 +1015,20 @@ public virtual void Detects_duplicate_column_names_within_hierarchy_with_differe
}
[ConditionalFact]
- public virtual void Detects_duplicate_column_names_within_hierarchy_with_different_nullability()
+ public virtual void Detects_duplicate_column_names_with_different_column_nullability()
{
var modelBuilder = CreateConventionModelBuilder();
- modelBuilder.Entity();
- modelBuilder.Entity();
- modelBuilder.Entity().Property("OtherId").HasColumnName("Id");
+
+ modelBuilder.Entity().HasOne().WithOne(b => b.A).HasForeignKey(a => a.Id).HasPrincipalKey(b => b.Id);
+ modelBuilder.Entity().HasOne().WithOne(g => g.A).HasForeignKey(a => a.Id).HasPrincipalKey(b => b.Id)
+ .Metadata.IsRequiredDependent = true;
+ modelBuilder.Entity().ToTable("Table").Ignore(a => a.P0);
+ modelBuilder.Entity().ToTable("Table").Property(b => b.P0).HasColumnName(nameof(A.P0));
+ modelBuilder.Entity().ToTable("Table").Property(g => g.P0).HasColumnName(nameof(A.P0)).IsRequired();
VerifyError(
RelationalStrings.DuplicateColumnNameNullabilityMismatch(
- nameof(Animal), nameof(Animal.Id), nameof(Dog), "OtherId", nameof(Animal.Id), nameof(Animal)),
+ nameof(B), nameof(B.P0), nameof(G), nameof(G.P0), nameof(A.P0), "Table"),
modelBuilder);
}
@@ -1686,7 +1687,7 @@ private class Address
}
[ConditionalFact]
- public virtual void Detects_missing_concurrency_token_on_the_base_type_without_convention()
+ public virtual void Passes_with_missing_concurrency_token_on_the_base_type_without_convention()
{
var modelBuilder = CreateModelBuilderWithoutConvention();
modelBuilder.Entity().ToTable(nameof(Animal))
@@ -1695,9 +1696,7 @@ public virtual void Detects_missing_concurrency_token_on_the_base_type_without_c
modelBuilder.Entity()
.Property("Version").IsRowVersion().HasColumnName("Version");
- VerifyError(
- RelationalStrings.MissingConcurrencyColumn(nameof(Animal), "Version", nameof(Animal)),
- modelBuilder);
+ Validate(modelBuilder);
}
[ConditionalFact]
@@ -1723,7 +1722,7 @@ public virtual void Passes_with_missing_concurrency_token_property_on_the_base_t
modelBuilder.Entity()
.Property("Version").IsRowVersion().HasColumnName("Version");
- var model = Validate(modelBuilder);
+ Validate(modelBuilder);
}
[ConditionalFact]
diff --git a/test/EFCore.Relational.Tests/Metadata/Conventions/BlankTriggerAddingConventionTest.cs b/test/EFCore.Relational.Tests/Metadata/Conventions/BlankTriggerAddingConventionTest.cs
new file mode 100644
index 00000000000..529b6084437
--- /dev/null
+++ b/test/EFCore.Relational.Tests/Metadata/Conventions/BlankTriggerAddingConventionTest.cs
@@ -0,0 +1,55 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+// ReSharper disable InconsistentNaming
+namespace Microsoft.EntityFrameworkCore.Metadata.Conventions;
+
+public class BlankTriggerAddingConventionTest
+{
+ [ConditionalFact]
+ public virtual void Adds_triggers_with_table_splitting()
+ {
+ var modelBuilder = CreateModelBuilder();
+
+ modelBuilder.Entity().SplitToTable("OrderDetails", s => s.Property(o => o.CustomerId));
+
+ var model = modelBuilder.FinalizeModel();
+
+ var entity = model.FindEntityType(typeof(Order))!;
+
+ Assert.Equal(new[] { "OrderDetails_Trigger", "Order_Trigger" }, entity.GetDeclaredTriggers().Select(t => t.ModelName));
+ }
+
+ [ConditionalFact]
+ public virtual void Does_not_add_triggers_without_tables()
+ {
+ var modelBuilder = CreateModelBuilder();
+
+ modelBuilder.Entity().ToView("Orders");
+ modelBuilder.Entity().SplitToView("OrderDetails", s => s.Property(o => o.CustomerId));
+
+ var model = modelBuilder.FinalizeModel();
+
+ var entity = model.FindEntityType(typeof(Order))!;
+
+ Assert.Empty(entity.GetDeclaredTriggers());
+ }
+
+ protected class Order
+ {
+ public int OrderId { get; set; }
+
+ public int? CustomerId { get; set; }
+ public Guid AnotherCustomerId { get; set; }
+ }
+
+ protected virtual ModelBuilder CreateModelBuilder()
+ => FakeRelationalTestHelpers.Instance.CreateConventionBuilder(
+ configureModel:
+ b => b.Conventions.Add(
+ p => new BlankTriggerAddingConvention(
+ p.GetRequiredService(),
+ p.GetRequiredService())));
+}
diff --git a/test/EFCore.Specification.Tests/TestUtilities/TestModelSource.cs b/test/EFCore.Specification.Tests/TestUtilities/TestModelSource.cs
index 41ddbec93d9..fb7f21cdf7e 100644
--- a/test/EFCore.Specification.Tests/TestUtilities/TestModelSource.cs
+++ b/test/EFCore.Specification.Tests/TestUtilities/TestModelSource.cs
@@ -1,6 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using Microsoft.EntityFrameworkCore.Internal;
+
namespace Microsoft.EntityFrameworkCore.TestUtilities;
public class TestModelSource : ModelSource
@@ -26,6 +28,7 @@ protected override IModel CreateModel(
var modelConfigurationBuilder = new ModelConfigurationBuilder(
conventionSetBuilder.CreateConventionSet(),
context.GetInfrastructure());
+ context.ConfigureConventions(modelConfigurationBuilder);
_configureConventions?.Invoke(modelConfigurationBuilder);
var modelBuilder = modelConfigurationBuilder.CreateModelBuilder(modelDependencies);
diff --git a/test/EFCore.SqlServer.FunctionalTests/SqlServerTriggersTest.cs b/test/EFCore.SqlServer.FunctionalTests/SqlServerTriggersTest.cs
index 17fc19a4dcc..85d914fdc58 100644
--- a/test/EFCore.SqlServer.FunctionalTests/SqlServerTriggersTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/SqlServerTriggersTest.cs
@@ -107,12 +107,6 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
modelBuilder.Entity(
eb =>
{
- eb.ToTable(tb =>
- {
- tb.HasTrigger("TRG_InsertProduct");
- tb.HasTrigger("TRG_UpdateProduct");
- tb.HasTrigger("TRG_DeleteProduct");
- });
eb.Property(e => e.Version)
.ValueGeneratedOnAddOrUpdate()
.IsConcurrencyToken();
@@ -122,6 +116,14 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
modelBuilder.Entity()
.Property(e => e.Id).ValueGeneratedNever();
}
+
+ protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
+ {
+ configurationBuilder.Conventions.Add(p =>
+ new BlankTriggerAddingConvention(
+ p.GetRequiredService(),
+ p.GetRequiredService()));
+ }
}
protected class Product
diff --git a/test/EFCore.SqlServer.Tests/Infrastructure/SqlServerModelValidatorTest.cs b/test/EFCore.SqlServer.Tests/Infrastructure/SqlServerModelValidatorTest.cs
index 83b527d9591..69139900baf 100644
--- a/test/EFCore.SqlServer.Tests/Infrastructure/SqlServerModelValidatorTest.cs
+++ b/test/EFCore.SqlServer.Tests/Infrastructure/SqlServerModelValidatorTest.cs
@@ -11,20 +11,6 @@ namespace Microsoft.EntityFrameworkCore.Infrastructure;
public class SqlServerModelValidatorTest : RelationalModelValidatorTest
{
- public override void Detects_duplicate_column_names()
- {
- var modelBuilder = CreateConventionModelBuilder();
-
- modelBuilder.Entity().Property(b => b.Id).HasColumnName("Name");
- modelBuilder.Entity().Property(d => d.Name).IsRequired().HasColumnName("Name");
-
- VerifyError(
- RelationalStrings.DuplicateColumnNameDataTypeMismatch(
- nameof(Animal), nameof(Animal.Id),
- nameof(Animal), nameof(Animal.Name), "Name", nameof(Animal), "int", "nvarchar(max)"),
- modelBuilder);
- }
-
public override void Detects_duplicate_columns_in_derived_types_with_different_types()
{
var modelBuilder = CreateConventionModelBuilder();
@@ -838,27 +824,12 @@ public void Temporal_period_property_must_be_mapped_to_datetime2()
public void Temporal_all_properties_mapped_to_period_column_must_have_value_generated_OnAddOrUpdate()
{
var modelBuilder = CreateConventionModelBuilder();
- modelBuilder.Entity().Property(typeof(DateTime), "Start2").HasColumnName("StartColumn").ValueGeneratedOnAddOrUpdate();
- modelBuilder.Entity().Property(typeof(DateTime), "Start3").HasColumnName("StartColumn");
- modelBuilder.Entity().ToTable(tb => tb.IsTemporal(ttb => ttb.HasPeriodStart("Start").HasColumnName("StartColumn")));
+ modelBuilder.Entity().ToTable(tb => tb.IsTemporal(ttb =>
+ ttb.HasPeriodStart("Start").HasColumnName("StartColumn").GetInfrastructure().ValueGeneratedNever()));
VerifyError(
SqlServerStrings.TemporalPropertyMappedToPeriodColumnMustBeValueGeneratedOnAddOrUpdate(
- nameof(Dog), "Start3", nameof(ValueGenerated.OnAddOrUpdate)), modelBuilder);
- }
-
- [ConditionalFact]
- public void Temporal_all_properties_mapped_to_period_column_cant_have_default_values()
- {
- var modelBuilder = CreateConventionModelBuilder();
- modelBuilder.Entity().Property(typeof(DateTime), "Start2").HasColumnName("StartColumn").ValueGeneratedOnAddOrUpdate();
- modelBuilder.Entity().Property(typeof(DateTime), "Start3").HasColumnName("StartColumn").ValueGeneratedOnAddOrUpdate()
- .HasDefaultValue(DateTime.MinValue);
- modelBuilder.Entity().ToTable(tb => tb.IsTemporal(ttb => ttb.HasPeriodStart("Start").HasColumnName("StartColumn")));
-
- VerifyError(
- SqlServerStrings.TemporalPropertyMappedToPeriodColumnCantHaveDefaultValue(
- nameof(Dog), "Start3"), modelBuilder);
+ nameof(Dog), "Start", nameof(ValueGenerated.OnAddOrUpdate)), modelBuilder);
}
[ConditionalFact]
diff --git a/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderTestBase.cs b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderTestBase.cs
index 0a8dfe29cf1..c07162a4ba7 100644
--- a/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderTestBase.cs
+++ b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderTestBase.cs
@@ -1313,12 +1313,12 @@ public virtual void Temporal_table_with_explicit_properties_mapped_to_the_period
}));
modelBuilder.Entity()
- .Property("MappedStart")
+ .Property("Start")
.HasColumnName("PeriodStartColumn")
.ValueGeneratedOnAddOrUpdate();
modelBuilder.Entity()
- .Property("MappedEnd")
+ .Property("End")
.HasColumnName("PeriodEndColumn")
.ValueGeneratedOnAddOrUpdate();
@@ -1326,7 +1326,7 @@ public virtual void Temporal_table_with_explicit_properties_mapped_to_the_period
var entity = model.FindEntityType(typeof(Customer))!;
Assert.True(entity.IsTemporal());
- Assert.Equal(7, entity.GetProperties().Count());
+ Assert.Equal(5, entity.GetProperties().Count());
Assert.Equal("HistoryTable", entity.GetHistoryTableName());
@@ -1344,12 +1344,6 @@ public virtual void Temporal_table_with_explicit_properties_mapped_to_the_period
Assert.True(periodEnd.IsShadowProperty());
Assert.Equal(typeof(DateTime), periodEnd.ClrType);
Assert.Equal(ValueGenerated.OnAddOrUpdate, periodEnd.ValueGenerated);
-
- var propertyMappedToStart = entity.GetProperty("MappedStart");
- Assert.Equal("PeriodStartColumn", propertyMappedToStart[RelationalAnnotationNames.ColumnName]);
-
- var propertyMappedToEnd = entity.GetProperty("MappedEnd");
- Assert.Equal("PeriodEndColumn", propertyMappedToEnd[RelationalAnnotationNames.ColumnName]);
}
[ConditionalFact]
diff --git a/test/EFCore.Sqlite.Tests/Infrastructure/SqliteModelValidatorTest.cs b/test/EFCore.Sqlite.Tests/Infrastructure/SqliteModelValidatorTest.cs
index fb7d17922d2..fd176d9a659 100644
--- a/test/EFCore.Sqlite.Tests/Infrastructure/SqliteModelValidatorTest.cs
+++ b/test/EFCore.Sqlite.Tests/Infrastructure/SqliteModelValidatorTest.cs
@@ -9,33 +9,6 @@ namespace Microsoft.EntityFrameworkCore.Infrastructure;
public class SqliteModelValidatorTest : RelationalModelValidatorTest
{
- public override void Detects_duplicate_column_names()
- {
- var modelBuilder = CreateConventionModelBuilder();
-
- modelBuilder.Entity().Property(b => b.Id).HasColumnName("Name");
- modelBuilder.Entity().Property(d => d.Name).IsRequired().HasColumnName("Name");
-
- VerifyError(
- RelationalStrings.DuplicateColumnNameDataTypeMismatch(
- nameof(Animal), nameof(Animal.Id),
- nameof(Animal), nameof(Animal.Name), "Name", nameof(Animal), "INTEGER", "TEXT"),
- modelBuilder);
- }
-
- public override void Detects_duplicate_columns_in_derived_types_with_different_types()
- {
- var modelBuilder = CreateConventionModelBuilder();
- modelBuilder.Entity();
-
- modelBuilder.Entity().Property(c => c.Type).IsRequired().HasColumnName("Type");
- modelBuilder.Entity().Property(d => d.Type).HasColumnName("Type");
-
- VerifyError(
- RelationalStrings.DuplicateColumnNameDataTypeMismatch(
- typeof(Cat).Name, "Type", typeof(Dog).Name, "Type", "Type", nameof(Animal), "TEXT", "INTEGER"), modelBuilder);
- }
-
[ConditionalFact]
public virtual void Detects_duplicate_column_names_within_hierarchy_with_different_srid()
{
diff --git a/test/EFCore.Tests/Infrastructure/ModelValidatorTestBase.cs b/test/EFCore.Tests/Infrastructure/ModelValidatorTestBase.cs
index 75d7a2a33f6..46ea3e9a6a1 100644
--- a/test/EFCore.Tests/Infrastructure/ModelValidatorTestBase.cs
+++ b/test/EFCore.Tests/Infrastructure/ModelValidatorTestBase.cs
@@ -97,6 +97,18 @@ protected class F : D
{
}
+ protected class G
+ {
+ public int Id { get; set; }
+
+ public int? P0 { get; set; }
+ public int? P1 { get; set; }
+ public int? P2 { get; set; }
+ public int? P3 { get; set; }
+
+ public A A { get; set; }
+ }
+
protected abstract class Abstract : A
{
}