diff --git a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs
index 1166fec64c5..3554478c958 100644
--- a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs
+++ b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs
@@ -1095,10 +1095,19 @@ protected virtual void GenerateCheckConstraint(
stringBuilder
.Append(builderName)
.Append(".HasCheckConstraint(")
- .Append(Code.Literal(checkConstraint.Name))
+ .Append(Code.Literal(checkConstraint.ModelName))
.Append(", ")
- .Append(Code.Literal(checkConstraint.Sql))
- .AppendLine(");");
+ .Append(Code.Literal(checkConstraint.Sql));
+
+ if (checkConstraint.Name != (checkConstraint.GetDefaultName() ?? checkConstraint.ModelName))
+ {
+ stringBuilder
+ .Append(", c => c.HasName(")
+ .Append(Code.Literal(checkConstraint.Name))
+ .Append(")");
+ }
+
+ stringBuilder.AppendLine(");");
}
///
diff --git a/src/EFCore.Relational/Design/RelationalCSharpRuntimeAnnotationCodeGenerator.cs b/src/EFCore.Relational/Design/RelationalCSharpRuntimeAnnotationCodeGenerator.cs
index dfaba72a23c..1b67fba9dbc 100644
--- a/src/EFCore.Relational/Design/RelationalCSharpRuntimeAnnotationCodeGenerator.cs
+++ b/src/EFCore.Relational/Design/RelationalCSharpRuntimeAnnotationCodeGenerator.cs
@@ -326,22 +326,7 @@ public override void Generate(IEntityType entityType, CSharpRuntimeAnnotationCod
}
else
{
- if (annotations.TryGetAndRemove(RelationalAnnotationNames.CheckConstraints,
- out SortedDictionary constraints))
- {
- parameters.Namespaces.Add(typeof(SortedDictionary).Namespace!);
- var constraintsVariable = Dependencies.CSharpHelper.Identifier("constraints", parameters.ScopeVariables, capitalize: false);
- parameters.MainBuilder
- .Append("var ").Append(constraintsVariable).AppendLine(" = new SortedDictionary();");
-
- foreach (var constraintPair in constraints)
- {
- Create(constraintPair.Value, constraintsVariable, parameters);
- }
-
- GenerateSimpleAnnotation(RelationalAnnotationNames.CheckConstraints, constraintsVariable, parameters);
- }
-
+ annotations.Remove(RelationalAnnotationNames.CheckConstraints);
annotations.Remove(RelationalAnnotationNames.Comment);
annotations.Remove(RelationalAnnotationNames.IsTableExcludedFromMigrations);
@@ -357,29 +342,6 @@ public override void Generate(IEntityType entityType, CSharpRuntimeAnnotationCod
base.Generate(entityType, parameters);
}
- private void Create(ICheckConstraint constraint, string constraintsVariable, CSharpRuntimeAnnotationCodeGeneratorParameters parameters)
- {
- var code = Dependencies.CSharpHelper;
- var constraintVariable = code.Identifier(constraint.Name, parameters.ScopeVariables, capitalize: false);
- var mainBuilder = parameters.MainBuilder;
- mainBuilder
- .Append("var ").Append(constraintVariable).AppendLine(" = new RuntimeCheckConstraint(").IncrementIndent()
- .Append(code.Literal(constraint.Name)).AppendLine(",")
- .Append(parameters.TargetName).AppendLine(",")
- .Append(code.Literal(constraint.Sql)).AppendLine(");").DecrementIndent()
- .AppendLine();
-
- CreateAnnotations(
- constraint,
- Generate,
- parameters with { TargetName = constraintVariable });
-
- mainBuilder
- .Append(constraintsVariable).Append("[").Append(code.Literal(constraint.Name)).Append("] = ")
- .Append(constraintVariable).AppendLine(";")
- .AppendLine();
- }
-
///
/// Generates code to create the given annotations.
///
diff --git a/src/EFCore.Relational/Extensions/RelationalEntityTypeBuilderExtensions.cs b/src/EFCore.Relational/Extensions/RelationalEntityTypeBuilderExtensions.cs
index a6cd84696ad..3e724ca0651 100644
--- a/src/EFCore.Relational/Extensions/RelationalEntityTypeBuilderExtensions.cs
+++ b/src/EFCore.Relational/Extensions/RelationalEntityTypeBuilderExtensions.cs
@@ -2,8 +2,10 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
+using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
+using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
@@ -972,27 +974,36 @@ public static EntityTypeBuilder HasCheckConstraint(
string? sql)
{
Check.NotNull(entityTypeBuilder, nameof(entityTypeBuilder));
- Check.NotEmpty(name, nameof(name));
- Check.NullButNotEmpty(sql, nameof(sql));
- var entityType = entityTypeBuilder.Metadata;
+ InternalCheckConstraintBuilder.HasCheckConstraint(
+ (IConventionEntityType)entityTypeBuilder.Metadata,
+ name,
+ sql,
+ ConfigurationSource.Explicit);
- var constraint = entityType.FindCheckConstraint(name);
- if (constraint != null)
- {
- if (constraint.Sql == sql)
- {
- ((CheckConstraint)constraint).UpdateConfigurationSource(ConfigurationSource.Explicit);
- return entityTypeBuilder;
- }
+ return entityTypeBuilder;
+ }
- entityType.RemoveCheckConstraint(name);
- }
+ ///
+ /// Configures a database check constraint when targeting a relational database.
+ ///
+ /// The entity type builder.
+ /// The name of the check constraint.
+ /// The logical constraint sql used in the check constraint.
+ /// An action that performs configuration of the check constraint.
+ /// A builder to further configure the entity type.
+ public static EntityTypeBuilder HasCheckConstraint(
+ this EntityTypeBuilder entityTypeBuilder,
+ string name,
+ string sql,
+ Action buildAction)
+ {
+ Check.NotEmpty(sql, nameof(sql));
+ Check.NotNull(buildAction, nameof(buildAction));
- if (sql != null)
- {
- entityType.AddCheckConstraint(name, sql);
- }
+ entityTypeBuilder.HasCheckConstraint(name, sql);
+
+ buildAction(new CheckConstraintBuilder(entityTypeBuilder.Metadata.FindCheckConstraint(name)!));
return entityTypeBuilder;
}
@@ -1012,6 +1023,23 @@ public static EntityTypeBuilder HasCheckConstraint(
where TEntity : class
=> (EntityTypeBuilder)HasCheckConstraint((EntityTypeBuilder)entityTypeBuilder, name, sql);
+ ///
+ /// Configures a database check constraint when targeting a relational database.
+ ///
+ /// The entity type being configured.
+ /// The entity type builder.
+ /// The name of the check constraint.
+ /// The logical constraint sql used in the check constraint.
+ /// An action that performs configuration of the check constraint.
+ /// A builder to further configure the entity type.
+ public static EntityTypeBuilder HasCheckConstraint(
+ this EntityTypeBuilder entityTypeBuilder,
+ string name,
+ string sql,
+ Action buildAction)
+ where TEntity : class
+ => (EntityTypeBuilder)HasCheckConstraint((EntityTypeBuilder)entityTypeBuilder, name, sql, buildAction);
+
///
/// Configures a database check constraint when targeting a relational database.
///
@@ -1023,44 +1051,17 @@ public static EntityTypeBuilder HasCheckConstraint(
/// The same builder instance if the check constraint was configured,
/// otherwise.
///
- public static IConventionEntityTypeBuilder? HasCheckConstraint(
+ public static IConventionCheckConstraintBuilder? HasCheckConstraint(
this IConventionEntityTypeBuilder entityTypeBuilder,
string name,
string? sql,
bool fromDataAnnotation = false)
- {
- Check.NotNull(entityTypeBuilder, nameof(entityTypeBuilder));
- Check.NotEmpty(name, nameof(name));
- Check.NullButNotEmpty(sql, nameof(sql));
-
- var entityType = entityTypeBuilder.Metadata;
-
- var constraint = entityType.FindCheckConstraint(name);
- if (constraint != null)
- {
- if (constraint.Sql == sql)
- {
- ((CheckConstraint)constraint).UpdateConfigurationSource(
- fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
- return entityTypeBuilder;
- }
-
- if (!(fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention)
- .Overrides(constraint.GetConfigurationSource()))
- {
- return null;
- }
-
- entityType.RemoveCheckConstraint(name);
- }
-
- if (sql != null)
- {
- entityType.AddCheckConstraint(name, sql, fromDataAnnotation);
- }
-
- return entityTypeBuilder;
- }
+ => InternalCheckConstraintBuilder.HasCheckConstraint(
+ Check.NotNull(entityTypeBuilder, nameof(entityTypeBuilder)).Metadata,
+ name,
+ sql,
+ fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention)
+ ?.Builder;
///
/// Returns a value indicating whether the check constraint can be configured.
@@ -1070,23 +1071,32 @@ public static EntityTypeBuilder HasCheckConstraint(
/// The logical constraint sql used in the check constraint.
/// Indicates whether the configuration was specified using a data annotation.
/// if the configuration can be applied.
+ [Obsolete("Use CanHaveCheckConstraint")]
public static bool CanSetCheckConstraint(
this IConventionEntityTypeBuilder entityTypeBuilder,
string name,
string? sql,
bool fromDataAnnotation = false)
- {
- Check.NotNull(entityTypeBuilder, nameof(entityTypeBuilder));
- Check.NotEmpty(name, nameof(name));
- Check.NullButNotEmpty(sql, nameof(sql));
-
- var constraint = entityTypeBuilder.Metadata.FindCheckConstraint(name);
+ => entityTypeBuilder.CanHaveCheckConstraint(name, sql, fromDataAnnotation);
- return constraint == null
- || constraint.Sql == sql
- || (fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention)
- .Overrides(constraint.GetConfigurationSource());
- }
+ ///
+ /// Returns a value indicating whether the check constraint can be configured.
+ ///
+ /// The builder for the entity type being configured.
+ /// The name of the check constraint.
+ /// The logical constraint sql used in the check constraint.
+ /// Indicates whether the configuration was specified using a data annotation.
+ /// if the configuration can be applied.
+ public static bool CanHaveCheckConstraint(
+ this IConventionEntityTypeBuilder entityTypeBuilder,
+ string name,
+ string? sql,
+ bool fromDataAnnotation = false)
+ => InternalCheckConstraintBuilder.CanHaveCheckConstraint(
+ Check.NotNull(entityTypeBuilder, nameof(entityTypeBuilder)).Metadata,
+ name,
+ sql,
+ fromDataAnnotation? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
///
/// Configures a comment to be applied to the table
diff --git a/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs b/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs
index d44cf895115..bd0603e1f55 100644
--- a/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs
+++ b/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs
@@ -600,7 +600,7 @@ public static IEnumerable GetFunctionMappings(this IEntityType
public static IMutableCheckConstraint? FindCheckConstraint(
this IMutableEntityType entityType,
string name)
- => (IMutableCheckConstraint?)((IEntityType)entityType).FindCheckConstraint(name);
+ => (IMutableCheckConstraint?)((IReadOnlyEntityType)entityType).FindCheckConstraint(name);
///
/// Finds an with the given name.
@@ -614,7 +614,7 @@ public static IEnumerable GetFunctionMappings(this IEntityType
public static IConventionCheckConstraint? FindCheckConstraint(
this IConventionEntityType entityType,
string name)
- => (IConventionCheckConstraint?)((IEntityType)entityType).FindCheckConstraint(name);
+ => (IConventionCheckConstraint?)((IReadOnlyEntityType)entityType).FindCheckConstraint(name);
///
/// Finds an with the given name.
@@ -677,7 +677,7 @@ public static IConventionCheckConstraint AddCheckConstraint(
///
/// The entity type to remove the check constraint from.
/// The check constraint name to be removed.
- /// The removed .
+ /// The removed check constraint.
public static IMutableCheckConstraint? RemoveCheckConstraint(
this IMutableEntityType entityType,
string name)
@@ -688,39 +688,96 @@ public static IConventionCheckConstraint AddCheckConstraint(
///
/// The entity type to remove the check constraint from.
/// The check constraint name.
- /// The removed .
+ /// The removed check constraint.
public static IConventionCheckConstraint? RemoveCheckConstraint(
this IConventionEntityType entityType,
string name)
- => CheckConstraint.RemoveCheckConstraint((IMutableEntityType)entityType, Check.NotEmpty(name, nameof(name)));
+ => (IConventionCheckConstraint?)CheckConstraint.RemoveCheckConstraint(
+ (IMutableEntityType)entityType, Check.NotEmpty(name, nameof(name)));
///
- /// Returns all contained in the entity type.
+ /// Returns all check constraints contained in the entity type.
///
/// The entity type to get the check constraints for.
public static IEnumerable GetCheckConstraints(this IReadOnlyEntityType entityType)
=> CheckConstraint.GetCheckConstraints(entityType);
///
- /// Returns all contained in the entity type.
+ /// Returns all check constraints contained in the entity type.
///
/// The entity type to get the check constraints for.
public static IEnumerable GetCheckConstraints(this IMutableEntityType entityType)
=> CheckConstraint.GetCheckConstraints(entityType).Cast();
///
- /// Returns all contained in the entity type.
+ /// Returns all check constraints contained in the entity type.
///
/// The entity type to get the check constraints for.
public static IEnumerable GetCheckConstraints(this IConventionEntityType entityType)
=> CheckConstraint.GetCheckConstraints(entityType).Cast();
///
- /// Returns all contained in the entity type.
+ /// Returns all check constraints contained in the entity type.
///
/// The entity type to get the check constraints for.
public static IEnumerable GetCheckConstraints(this IEntityType entityType)
- => CheckConstraint.GetCheckConstraints(entityType);
+ => CheckConstraint.GetCheckConstraints(entityType).Cast();
+
+ ///
+ ///
+ /// Returns all check constraints declared on the entity type.
+ ///
+ ///
+ /// This method does not return check constraints declared on base types.
+ /// It is useful when iterating over all entity types to avoid processing the same check constraint more than once.
+ /// Use to also return check constraints declared on base types.
+ ///
+ ///
+ /// The entity type to get the check constraints for.
+ public static IEnumerable GetDeclaredCheckConstraints(this IReadOnlyEntityType entityType)
+ => CheckConstraint.GetDeclaredCheckConstraints(entityType);
+
+ ///
+ ///
+ /// Returns all check constraints declared on the entity type.
+ ///
+ ///
+ /// This method does not return check constraints declared on base types.
+ /// It is useful when iterating over all entity types to avoid processing the same check constraint more than once.
+ /// Use to also return check constraints declared on base types.
+ ///
+ ///
+ /// The entity type to get the check constraints for.
+ public static IEnumerable GetDeclaredCheckConstraints(this IMutableEntityType entityType)
+ => CheckConstraint.GetDeclaredCheckConstraints(entityType).Cast();
+
+ ///
+ ///
+ /// Returns all check constraints declared on the entity type.
+ ///
+ ///
+ /// This method does not return check constraints declared on base types.
+ /// It is useful when iterating over all entity types to avoid processing the same check constraint more than once.
+ /// Use to also return check constraints declared on base types.
+ ///
+ ///
+ /// The entity type to get the check constraints for.
+ public static IEnumerable GetDeclaredCheckConstraints(this IConventionEntityType entityType)
+ => CheckConstraint.GetDeclaredCheckConstraints(entityType).Cast();
+
+ ///
+ ///
+ /// Returns all check constraints declared on the entity type.
+ ///
+ ///
+ /// This method does not return check constraints declared on base types.
+ /// It is useful when iterating over all entity types to avoid processing the same check constraint more than once.
+ /// Use to also return check constraints declared on base types.
+ ///
+ ///
+ /// The entity type to get the check constraints for.
+ public static IEnumerable GetDeclaredCheckConstraints(this IEntityType entityType)
+ => CheckConstraint.GetDeclaredCheckConstraints(entityType).Cast();
///
/// Returns the comment for the table this entity is mapped to.
diff --git a/src/EFCore.Relational/Extensions/RelationalOwnedNavigationBuilderExtensions.cs b/src/EFCore.Relational/Extensions/RelationalOwnedNavigationBuilderExtensions.cs
index 5b62022ceaf..0c4ccf8631a 100644
--- a/src/EFCore.Relational/Extensions/RelationalOwnedNavigationBuilderExtensions.cs
+++ b/src/EFCore.Relational/Extensions/RelationalOwnedNavigationBuilderExtensions.cs
@@ -1,6 +1,7 @@
// 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.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
@@ -27,27 +28,54 @@ public static OwnedNavigationBuilder HasCheckConstraint(
string? sql)
{
Check.NotNull(ownedNavigationBuilder, nameof(ownedNavigationBuilder));
- Check.NotEmpty(name, nameof(name));
- Check.NullButNotEmpty(sql, nameof(sql));
- var entityType = ownedNavigationBuilder.OwnedEntityType;
+ InternalCheckConstraintBuilder.HasCheckConstraint(
+ (IConventionEntityType)ownedNavigationBuilder.OwnedEntityType,
+ name,
+ sql,
+ ConfigurationSource.Explicit);
- var constraint = entityType.FindCheckConstraint(name);
- if (constraint != null)
- {
- if (constraint.Sql == sql)
- {
- ((CheckConstraint)constraint).UpdateConfigurationSource(ConfigurationSource.Explicit);
- return ownedNavigationBuilder;
- }
+ return ownedNavigationBuilder;
+ }
+
+ ///
+ /// Configures a database check constraint when targeting a relational database.
+ ///
+ /// The entity type owning the relationship.
+ /// The dependent entity type of the relationship.
+ /// The navigation builder for the owned type.
+ /// The name of the check constraint.
+ /// The logical constraint sql used in the check constraint.
+ /// A builder to further configure the navigation.
+ public static OwnedNavigationBuilder HasCheckConstraint(
+ this OwnedNavigationBuilder ownedNavigationBuilder,
+ string name,
+ string? sql)
+ where TEntity : class
+ where TDependentEntity : class
+ => (OwnedNavigationBuilder)
+ HasCheckConstraint((OwnedNavigationBuilder)ownedNavigationBuilder, name, sql);
- entityType.RemoveCheckConstraint(name);
- }
+ ///
+ /// Configures a database check constraint when targeting a relational database.
+ ///
+ /// The navigation builder for the owned type.
+ /// The name of the check constraint.
+ /// The logical constraint sql used in the check constraint.
+ /// An action that performs configuration of the check constraint.
+ /// A builder to further configure the navigation.
+ public static OwnedNavigationBuilder HasCheckConstraint(
+ this OwnedNavigationBuilder ownedNavigationBuilder,
+ string name,
+ string sql,
+ Action buildAction)
+ {
+ Check.NotEmpty(sql, nameof(sql));
+ Check.NotNull(buildAction, nameof(buildAction));
+
+ ownedNavigationBuilder.HasCheckConstraint(name, sql);
- if (sql != null)
- {
- entityType.AddCheckConstraint(name, sql);
- }
+ buildAction(new CheckConstraintBuilder(ownedNavigationBuilder.OwnedEntityType.FindCheckConstraint(name)!));
return ownedNavigationBuilder;
}
@@ -60,14 +88,16 @@ public static OwnedNavigationBuilder HasCheckConstraint(
/// The navigation builder for the owned type.
/// The name of the check constraint.
/// The logical constraint sql used in the check constraint.
+ /// An action that performs configuration of the check constraint.
/// A builder to further configure the navigation.
public static OwnedNavigationBuilder HasCheckConstraint(
this OwnedNavigationBuilder ownedNavigationBuilder,
string name,
- string? sql)
+ string sql,
+ Action buildAction)
where TEntity : class
where TDependentEntity : class
=> (OwnedNavigationBuilder)
- HasCheckConstraint((OwnedNavigationBuilder)ownedNavigationBuilder, name, sql);
+ HasCheckConstraint((OwnedNavigationBuilder)ownedNavigationBuilder, name, sql, buildAction);
}
}
diff --git a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs
index 189b06c492e..c8b7ff2084a 100644
--- a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs
+++ b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs
@@ -301,6 +301,7 @@ protected virtual void ValidateSharedTableCompatibility(
ValidateSharedKeysCompatibility(mappedTypes, table, logger);
ValidateSharedForeignKeysCompatibility(mappedTypes, table, logger);
ValidateSharedIndexesCompatibility(mappedTypes, table, logger);
+ ValidateSharedCheckConstraintCompatibility(mappedTypes, table, logger);
// Validate optional dependents
if (mappedTypes.Count == 1)
@@ -1164,10 +1165,54 @@ protected virtual void ValidateCompatible(
string keyName,
in StoreObjectIdentifier storeObject,
IDiagnosticsLogger logger)
+ => key.AreCompatible(duplicateKey, storeObject, shouldThrow: true);
+
+ ///
+ /// Validates the compatibility of check constraint in a given shared table.
+ ///
+ /// The mapped entity types.
+ /// The identifier of the store object.
+ /// The logger to use.
+ protected virtual void ValidateSharedCheckConstraintCompatibility(
+ IReadOnlyList mappedTypes,
+ in StoreObjectIdentifier storeObject,
+ IDiagnosticsLogger logger)
{
- key.AreCompatible(duplicateKey, storeObject, shouldThrow: true);
+ var checkConstraintMappings = new Dictionary();
+ foreach (var checkConstraint in mappedTypes.SelectMany(et => et.GetDeclaredCheckConstraints()))
+ {
+ var checkConstraintName = checkConstraint.GetName(storeObject);
+ if (checkConstraintName == null)
+ {
+ continue;
+ }
+
+ if (!checkConstraintMappings.TryGetValue(checkConstraintName, out var duplicateCheckConstraint))
+ {
+ checkConstraintMappings[checkConstraintName] = checkConstraint;
+ continue;
+ }
+
+ ValidateCompatible(checkConstraint, duplicateCheckConstraint, checkConstraintName, storeObject, logger);
+ }
}
+ ///
+ /// Validates the compatibility of two check constraints with the same name.
+ ///
+ /// An check constraints.
+ /// Another check constraints.
+ /// The name of the check constraint.
+ /// The identifier of the store object.
+ /// The logger to use.
+ protected virtual void ValidateCompatible(
+ ICheckConstraint checkConstraint,
+ ICheckConstraint duplicateCheckConstraint,
+ string indexName,
+ in StoreObjectIdentifier storeObject,
+ IDiagnosticsLogger logger)
+ => CheckConstraint.AreCompatible(checkConstraint, duplicateCheckConstraint, storeObject, shouldThrow: true);
+
///
/// Validates the mapping/configuration of inheritance in the model.
///
diff --git a/src/EFCore.Relational/Metadata/Builders/CheckConstraintBuilder.cs b/src/EFCore.Relational/Metadata/Builders/CheckConstraintBuilder.cs
new file mode 100644
index 00000000000..0f6a680251b
--- /dev/null
+++ b/src/EFCore.Relational/Metadata/Builders/CheckConstraintBuilder.cs
@@ -0,0 +1,95 @@
+// 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.ComponentModel;
+using System.Diagnostics;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata.Internal;
+using Microsoft.EntityFrameworkCore.Utilities;
+
+namespace Microsoft.EntityFrameworkCore.Metadata.Builders
+{
+ ///
+ /// Provides a simple API for configuring a check constraint.
+ ///
+ public class CheckConstraintBuilder : IInfrastructure
+ {
+ ///
+ /// 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.
+ ///
+ [EntityFrameworkInternal]
+ public CheckConstraintBuilder(IMutableCheckConstraint checkConstraint)
+ {
+ Check.NotNull(checkConstraint, nameof(checkConstraint));
+
+ Builder = ((CheckConstraint)checkConstraint).Builder;
+ }
+
+ ///
+ /// 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.
+ ///
+ [EntityFrameworkInternal]
+ protected virtual InternalCheckConstraintBuilder Builder { [DebuggerStepThrough] get; }
+
+ ///
+ IConventionCheckConstraintBuilder IInfrastructure.Instance
+ {
+ [DebuggerStepThrough] get => Builder;
+ }
+
+ ///
+ /// The check constraint being configured.
+ ///
+ public virtual IMutableCheckConstraint Metadata
+ => Builder.Metadata;
+
+ ///
+ /// Sets the database name of the check constraint.
+ ///
+ /// The database name of the check constraint.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual CheckConstraintBuilder HasName(string name)
+ {
+ Builder.HasName(name, ConfigurationSource.Explicit);
+
+ return this;
+ }
+
+ #region Hidden System.Object members
+
+ ///
+ /// Returns a string that represents the current object.
+ ///
+ /// A string that represents the current object.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override string? ToString()
+ => base.ToString();
+
+ ///
+ /// Determines whether the specified object is equal to the current object.
+ ///
+ /// The object to compare with the current object.
+ /// if the specified object is equal to the current object; otherwise, .
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ // ReSharper disable once BaseObjectEqualsIsObjectEquals
+ public override bool Equals(object? obj)
+ => base.Equals(obj);
+
+ ///
+ /// Serves as the default hash function.
+ ///
+ /// A hash code for the current object.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ // ReSharper disable once BaseObjectGetHashCodeCallInGetHashCode
+ public override int GetHashCode()
+ => base.GetHashCode();
+
+ #endregion
+ }
+}
diff --git a/src/EFCore.Relational/Metadata/Builders/DbFunctionBuilderBase.cs b/src/EFCore.Relational/Metadata/Builders/DbFunctionBuilderBase.cs
index 38cf2bbb8c5..170a5f7589b 100644
--- a/src/EFCore.Relational/Metadata/Builders/DbFunctionBuilderBase.cs
+++ b/src/EFCore.Relational/Metadata/Builders/DbFunctionBuilderBase.cs
@@ -4,7 +4,6 @@
using System.ComponentModel;
using System.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
-using Microsoft.EntityFrameworkCore.Metadata.Builders.Internal;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Utilities;
diff --git a/src/EFCore.Relational/Metadata/Builders/DbFunctionParameterBuilder.cs b/src/EFCore.Relational/Metadata/Builders/DbFunctionParameterBuilder.cs
index 1600a7ce60c..423fc49074c 100644
--- a/src/EFCore.Relational/Metadata/Builders/DbFunctionParameterBuilder.cs
+++ b/src/EFCore.Relational/Metadata/Builders/DbFunctionParameterBuilder.cs
@@ -4,7 +4,6 @@
using System.ComponentModel;
using System.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
-using Microsoft.EntityFrameworkCore.Metadata.Builders.Internal;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Utilities;
diff --git a/src/EFCore.Relational/Metadata/Builders/IConventionCheckConstraintBuilder.cs b/src/EFCore.Relational/Metadata/Builders/IConventionCheckConstraintBuilder.cs
new file mode 100644
index 00000000000..a159a24bd82
--- /dev/null
+++ b/src/EFCore.Relational/Metadata/Builders/IConventionCheckConstraintBuilder.cs
@@ -0,0 +1,35 @@
+// 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.Metadata.Builders
+{
+ ///
+ /// Provides a simple API for configuring a check constraint.
+ ///
+ public interface IConventionCheckConstraintBuilder : IConventionAnnotatableBuilder
+ {
+ ///
+ /// The check constraint being configured.
+ ///
+ new IConventionCheckConstraint Metadata { get; }
+
+ ///
+ /// Sets the database name of the check constraint.
+ ///
+ /// The database name of the check constraint.
+ /// Indicates whether the configuration was specified using a data annotation.
+ ///
+ /// The same builder instance if the configuration was applied,
+ /// otherwise.
+ ///
+ IConventionCheckConstraintBuilder? HasName(string? name, bool fromDataAnnotation = false);
+
+ ///
+ /// Returns a value indicating whether the given name can be set for the check constraint.
+ ///
+ /// The database name of the check constraint.
+ /// Indicates whether the configuration was specified using a data annotation.
+ /// if the database name can be set for the check constraint.
+ bool CanSetName(string? name, bool fromDataAnnotation = false);
+ }
+}
diff --git a/src/EFCore.Relational/Metadata/Builders/SequenceBuilder.cs b/src/EFCore.Relational/Metadata/Builders/SequenceBuilder.cs
index 9297621380a..74ea347e80b 100644
--- a/src/EFCore.Relational/Metadata/Builders/SequenceBuilder.cs
+++ b/src/EFCore.Relational/Metadata/Builders/SequenceBuilder.cs
@@ -4,7 +4,6 @@
using System.ComponentModel;
using System.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
-using Microsoft.EntityFrameworkCore.Metadata.Builders.Internal;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Utilities;
diff --git a/src/EFCore.Relational/Metadata/Conventions/CheckConstraintConvention.cs b/src/EFCore.Relational/Metadata/Conventions/CheckConstraintConvention.cs
new file mode 100644
index 00000000000..8a718f23ec3
--- /dev/null
+++ b/src/EFCore.Relational/Metadata/Conventions/CheckConstraintConvention.cs
@@ -0,0 +1,142 @@
+// 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 System.Collections.Generic;
+using System.Linq;
+using Microsoft.EntityFrameworkCore.Diagnostics;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata.Internal;
+
+namespace Microsoft.EntityFrameworkCore.Metadata.Conventions
+{
+ ///
+ /// A convention that ensures that the check constraints on the derived types are compatible with
+ /// the check constraints on the base type.
+ ///
+ public class CheckConstraintConvention : IEntityTypeBaseTypeChangedConvention
+ {
+ ///
+ /// Creates a new instance of .
+ ///
+ /// Parameter object containing dependencies for this convention.
+ /// Parameter object containing relational dependencies for this convention.
+ public CheckConstraintConvention(
+ ProviderConventionSetBuilderDependencies dependencies,
+ RelationalConventionSetBuilderDependencies relationalDependencies)
+ {
+ Dependencies = dependencies;
+ RelationalDependencies = relationalDependencies;
+ }
+
+ ///
+ /// Parameter object containing service dependencies.
+ ///
+ protected virtual ProviderConventionSetBuilderDependencies Dependencies { get; }
+
+ ///
+ /// Parameter object containing relational service dependencies.
+ ///
+ 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,
+ IConventionEntityType? oldBaseType,
+ IConventionContext context)
+ {
+ var entityType = entityTypeBuilder.Metadata;
+ if (newBaseType != null)
+ {
+ var configurationSource = entityType.GetBaseTypeConfigurationSource();
+ var baseCheckConstraints = newBaseType.GetCheckConstraints().ToDictionary(c => c.ModelName);
+ List? checkConstraintsToBeDetached = null;
+ List? checkConstraintsToBeRemoved = null;
+ foreach (var checkConstraint in entityType.GetDerivedTypesInclusive().SelectMany(et => et.GetDeclaredCheckConstraints()))
+ {
+ if (baseCheckConstraints.TryGetValue(checkConstraint.ModelName, out var baseCheckConstraint)
+ && baseCheckConstraint.GetConfigurationSource().Overrides(checkConstraint.GetConfigurationSource())
+ && !AreCompatible(checkConstraint, baseCheckConstraint))
+ {
+ if (baseCheckConstraint.GetConfigurationSource() == ConfigurationSource.Explicit
+ && configurationSource == ConfigurationSource.Explicit
+ && checkConstraint.GetConfigurationSource() == ConfigurationSource.Explicit)
+ {
+ throw new InvalidOperationException(RelationalStrings.DuplicateCheckConstraint(
+ checkConstraint.ModelName,
+ checkConstraint.EntityType.DisplayName(),
+ baseCheckConstraint.EntityType.DisplayName()));
+ }
+
+ if (checkConstraintsToBeRemoved == null)
+ {
+ checkConstraintsToBeRemoved = new List();
+ }
+
+ checkConstraintsToBeRemoved.Add(checkConstraint);
+ continue;
+ }
+
+ if (baseCheckConstraint != null)
+ {
+ if (checkConstraintsToBeDetached == null)
+ {
+ checkConstraintsToBeDetached = new List();
+ }
+
+ checkConstraintsToBeDetached.Add(checkConstraint);
+ }
+ }
+
+ if (checkConstraintsToBeRemoved != null)
+ {
+ foreach (var checkConstraintToBeRemoved in checkConstraintsToBeRemoved)
+ {
+ checkConstraintToBeRemoved.EntityType.RemoveCheckConstraint(checkConstraintToBeRemoved.ModelName);
+ }
+ }
+
+ if (checkConstraintsToBeDetached != null)
+ {
+ foreach (var checkConstraintToBeDetached in checkConstraintsToBeDetached)
+ {
+ var baseCheckConstraint = baseCheckConstraints[checkConstraintToBeDetached.ModelName];
+ CheckConstraint.Attach(checkConstraintToBeDetached, baseCheckConstraint);
+
+ checkConstraintToBeDetached.EntityType.RemoveCheckConstraint(checkConstraintToBeDetached.ModelName);
+ }
+ }
+ }
+ }
+
+ private bool AreCompatible(IConventionCheckConstraint checkConstraint, IConventionCheckConstraint baseCheckConstraint)
+ {
+ var baseTable = StoreObjectIdentifier.Create(baseCheckConstraint.EntityType, StoreObjectType.Table);
+ if (baseTable == null)
+ {
+ return true;
+ }
+
+ if (checkConstraint.GetName(baseTable.Value) != baseCheckConstraint.GetName(baseTable.Value)
+ && checkConstraint.GetNameConfigurationSource() is ConfigurationSource nameConfigurationSource
+ && !nameConfigurationSource.OverridesStrictly(baseCheckConstraint.GetNameConfigurationSource()))
+ {
+ return false;
+ }
+
+ return CheckConstraint.AreCompatible(
+ checkConstraint,
+ baseCheckConstraint,
+ baseTable.Value,
+ shouldThrow: false);
+ }
+ }
+}
diff --git a/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilder.cs b/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilder.cs
index 279648abaae..3782b66037e 100644
--- a/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilder.cs
+++ b/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilder.cs
@@ -74,6 +74,7 @@ public override ConventionSet CreateConventionSet()
new RelationalValueGenerationConvention(Dependencies, RelationalDependencies);
ReplaceConvention(conventionSet.EntityTypeBaseTypeChangedConventions, valueGenerationConvention);
conventionSet.EntityTypeBaseTypeChangedConventions.Add(tableNameFromDbSetConvention);
+ conventionSet.EntityTypeBaseTypeChangedConventions.Add(new CheckConstraintConvention(Dependencies, RelationalDependencies));
conventionSet.EntityTypeAnnotationChangedConventions.Add((RelationalValueGenerationConvention)valueGenerationConvention);
diff --git a/src/EFCore.Relational/Metadata/Conventions/RelationalRuntimeModelConvention.cs b/src/EFCore.Relational/Metadata/Conventions/RelationalRuntimeModelConvention.cs
index bd2a118645e..9df97d4313b 100644
--- a/src/EFCore.Relational/Metadata/Conventions/RelationalRuntimeModelConvention.cs
+++ b/src/EFCore.Relational/Metadata/Conventions/RelationalRuntimeModelConvention.cs
@@ -119,21 +119,7 @@ protected override void ProcessEntityTypeAnnotations(
}
else
{
- if (annotations.TryGetValue(RelationalAnnotationNames.CheckConstraints, out var constraints))
- {
- var runtimeCheckConstraints = new SortedDictionary();
- foreach (var constraintPair in (SortedDictionary?)constraints!)
- {
- var runtimeCheckConstraint = Create(constraintPair.Value, runtimeEntityType);
- runtimeCheckConstraints[constraintPair.Key] = runtimeCheckConstraint;
-
- CreateAnnotations(constraintPair.Value, runtimeCheckConstraint, static (convention, annotations, source, target, runtime) =>
- convention.ProcessCheckConstraintAnnotations(annotations, source, target, runtime));
- }
-
- annotations[RelationalAnnotationNames.CheckConstraints] = runtimeCheckConstraints;
- }
-
+ annotations.Remove(RelationalAnnotationNames.CheckConstraints);
annotations.Remove(RelationalAnnotationNames.Comment);
annotations.Remove(RelationalAnnotationNames.IsTableExcludedFromMigrations);
@@ -244,27 +230,6 @@ protected virtual void ProcessSequenceAnnotations(
{
}
- private RuntimeCheckConstraint Create(ICheckConstraint checkConstraint, RuntimeEntityType runtimeEntityType)
- => new RuntimeCheckConstraint(
- checkConstraint.Name,
- runtimeEntityType,
- checkConstraint.Sql);
-
- ///
- /// Updates the check constraint annotations that will be set on the read-only object.
- ///
- /// The annotations to be processed.
- /// The source check constraint.
- /// The target check constraint that will contain the annotations.
- /// Indicates whether the given annotations are runtime annotations.
- protected virtual void ProcessCheckConstraintAnnotations(
- Dictionary annotations,
- ICheckConstraint checkConstraint,
- RuntimeCheckConstraint runtimeCheckConstraint,
- bool runtime)
- {
- }
-
///
/// Updates the property annotations that will be set on the read-only object.
///
diff --git a/src/EFCore.Relational/Metadata/Conventions/SharedTableConvention.cs b/src/EFCore.Relational/Metadata/Conventions/SharedTableConvention.cs
index a52d0a5011a..d872354bb14 100644
--- a/src/EFCore.Relational/Metadata/Conventions/SharedTableConvention.cs
+++ b/src/EFCore.Relational/Metadata/Conventions/SharedTableConvention.cs
@@ -44,10 +44,11 @@ public virtual void ProcessModelFinalizing(
TryUniquifyTableNames(modelBuilder.Metadata, tables, maxLength);
- var columns = new Dictionary(StringComparer.Ordinal);
- var keys = new Dictionary(StringComparer.Ordinal);
- var foreignKeys = new Dictionary(StringComparer.Ordinal);
- var indexes = new Dictionary(StringComparer.Ordinal);
+ var columns = new Dictionary();
+ var keys = new Dictionary();
+ var foreignKeys = new Dictionary();
+ var indexes = new Dictionary();
+ var checkConstraints = new Dictionary<(string, string?), IConventionCheckConstraint>();
foreach (var table in tables)
{
columns.Clear();
@@ -61,6 +62,7 @@ public virtual void ProcessModelFinalizing(
TryUniquifyKeyNames(entityType, keys, storeObject, maxLength);
TryUniquifyForeignKeyNames(entityType, foreignKeys, storeObject, maxLength);
TryUniquifyIndexNames(entityType, indexes, storeObject, maxLength);
+ TryUniquifyCheckConstraintNames(entityType, checkConstraints, storeObject, maxLength);
}
}
}
@@ -482,5 +484,71 @@ protected virtual bool AreCompatible(
return null;
}
+
+ private void TryUniquifyCheckConstraintNames(
+ IConventionEntityType entityType,
+ Dictionary<(string, string?), IConventionCheckConstraint> checkConstraints,
+ in StoreObjectIdentifier storeObject,
+ int maxLength)
+ {
+ foreach (var checkConstraint in entityType.GetDeclaredCheckConstraints())
+ {
+ var constraintName = checkConstraint.GetName(storeObject);
+ if (!checkConstraints.TryGetValue((constraintName, storeObject.Schema), out var otherCheckConstraint))
+ {
+ checkConstraints[(constraintName, storeObject.Schema)] = checkConstraint;
+ continue;
+ }
+
+ if (AreCompatible(checkConstraint, otherCheckConstraint, storeObject))
+ {
+ continue;
+ }
+
+ var newConstraintName = TryUniquify(checkConstraint, constraintName, storeObject.Schema, checkConstraints, maxLength);
+ if (newConstraintName != null)
+ {
+ checkConstraints[(newConstraintName, storeObject.Schema)] = checkConstraint;
+ continue;
+ }
+
+ var newOtherConstraintName = TryUniquify(otherCheckConstraint, constraintName, storeObject.Schema, checkConstraints, maxLength);
+ if (newOtherConstraintName != null)
+ {
+ checkConstraints[(constraintName, storeObject.Schema)] = checkConstraint;
+ checkConstraints[(newOtherConstraintName, storeObject.Schema)] = otherCheckConstraint;
+ }
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether two check constraints with the same name are compatible.
+ ///
+ /// An check constraints.
+ /// Another check constraints.
+ /// The identifier of the store object.
+ /// if compatible
+ protected virtual bool AreCompatible(
+ IReadOnlyCheckConstraint checkConstraint,
+ IReadOnlyCheckConstraint duplicateCheckConstraint,
+ in StoreObjectIdentifier storeObject)
+ => CheckConstraint.AreCompatible(checkConstraint, duplicateCheckConstraint, storeObject, shouldThrow: false);
+
+ private static string? TryUniquify(
+ IConventionCheckConstraint checkConstraint,
+ string checkConstraintName,
+ string? schema,
+ Dictionary<(string, string?), T> checkConstraints,
+ int maxLength)
+ {
+ if (checkConstraint.Builder.CanSetName(null))
+ {
+ checkConstraintName = Uniquifier.Uniquify(checkConstraintName, checkConstraints, n => (n, schema), maxLength);
+ checkConstraint.Builder.HasName(checkConstraintName);
+ return checkConstraintName;
+ }
+
+ return null;
+ }
}
}
diff --git a/src/EFCore.Relational/Metadata/ICheckConstraint.cs b/src/EFCore.Relational/Metadata/ICheckConstraint.cs
index f5c391aa799..3ace8dd7fc4 100644
--- a/src/EFCore.Relational/Metadata/ICheckConstraint.cs
+++ b/src/EFCore.Relational/Metadata/ICheckConstraint.cs
@@ -7,7 +7,7 @@
namespace Microsoft.EntityFrameworkCore.Metadata
{
///
- /// Represents a check constraint in the .
+ /// Represents a check constraint on the entity type.
///
public interface ICheckConstraint : IReadOnlyCheckConstraint, IAnnotatable
{
@@ -37,7 +37,7 @@ string ToDebugString(MetadataDebugStringOptions options = MetadataDebugStringOpt
.Append(indentString)
.Append("Check: ");
- builder.Append(Name)
+ builder.Append(ModelName)
.Append(" \"")
.Append(Sql)
.Append('"');
diff --git a/src/EFCore.Relational/Metadata/IConventionCheckConstraint.cs b/src/EFCore.Relational/Metadata/IConventionCheckConstraint.cs
index fbae9a108ab..295e60c19ec 100644
--- a/src/EFCore.Relational/Metadata/IConventionCheckConstraint.cs
+++ b/src/EFCore.Relational/Metadata/IConventionCheckConstraint.cs
@@ -1,13 +1,22 @@
// 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.Metadata.Builders;
+
namespace Microsoft.EntityFrameworkCore.Metadata
{
///
- /// Represents a check constraint in the .
+ /// Represents a check constraint on the entity type.
///
public interface IConventionCheckConstraint : IReadOnlyCheckConstraint, IConventionAnnotatable
{
+ ///
+ /// Gets the builder that can be used to configure this check constraint.
+ ///
+ /// If the check constraint has been removed from the model.
+ new IConventionCheckConstraintBuilder Builder { get; }
+
///
/// Gets the entity type on which this check constraint is defined.
///
@@ -18,5 +27,19 @@ public interface IConventionCheckConstraint : IReadOnlyCheckConstraint, IConvent
///
/// The configuration source for this check constraint.
ConfigurationSource GetConfigurationSource();
+
+ ///
+ /// Sets the name of the check constraint in the database.
+ ///
+ /// The name of the check constraint in the database.
+ /// Indicates whether the configuration was specified using a data annotation.
+ /// The configured value.
+ string? SetName(string? name, bool fromDataAnnotation = false);
+
+ ///
+ /// Gets the configuration source for the database name.
+ ///
+ /// The configuration source for the database name.
+ ConfigurationSource? GetNameConfigurationSource();
}
}
diff --git a/src/EFCore.Relational/Metadata/IMutableCheckConstraint.cs b/src/EFCore.Relational/Metadata/IMutableCheckConstraint.cs
index 9e7bbe3f659..b630702178d 100644
--- a/src/EFCore.Relational/Metadata/IMutableCheckConstraint.cs
+++ b/src/EFCore.Relational/Metadata/IMutableCheckConstraint.cs
@@ -4,7 +4,7 @@
namespace Microsoft.EntityFrameworkCore.Metadata
{
///
- /// Represents a check constraint in the .
+ /// Represents a check constraint on the entity type.
///
public interface IMutableCheckConstraint : IReadOnlyCheckConstraint, IMutableAnnotatable
{
@@ -12,5 +12,10 @@ public interface IMutableCheckConstraint : IReadOnlyCheckConstraint, IMutableAnn
/// Gets the entity type on which this check constraint is defined.
///
new IMutableEntityType EntityType { get; }
+
+ ///
+ /// Gets or sets the name of the check constraint in the database.
+ ///
+ new string Name { get; set; }
}
}
diff --git a/src/EFCore.Relational/Metadata/IReadOnlyCheckConstraint.cs b/src/EFCore.Relational/Metadata/IReadOnlyCheckConstraint.cs
index 269587dd487..533247aae48 100644
--- a/src/EFCore.Relational/Metadata/IReadOnlyCheckConstraint.cs
+++ b/src/EFCore.Relational/Metadata/IReadOnlyCheckConstraint.cs
@@ -6,15 +6,52 @@
namespace Microsoft.EntityFrameworkCore.Metadata
{
///
- /// Represents a check constraint in the .
+ /// Represents a check constraint on the entity type.
///
public interface IReadOnlyCheckConstraint : IReadOnlyAnnotatable
{
///
- /// Gets the name of the check constraint in the database.
+ /// Gets the name of the check constraint in the model.
+ ///
+ string ModelName { get; }
+
+ ///
+ /// Gets the database name of the check constraint.
///
string Name { get; }
+ ///
+ /// Returns the default database name that would be used for this check constraint.
+ ///
+ /// The default name that would be used for this check constraint.
+ string? GetDefaultName()
+ {
+ var table = StoreObjectIdentifier.Create(EntityType, StoreObjectType.Table);
+ return !table.HasValue ? null : GetDefaultName(table.Value);
+ }
+
+ ///
+ /// Gets the database name of the check constraint.
+ ///
+ /// The identifier of the store object.
+ /// The database name of the check constraint for the given store object.
+ string GetName(in StoreObjectIdentifier storeObject);
+
+ ///
+ /// Returns the default database name that would be used for this check constraint.
+ ///
+ /// The identifier of the store object.
+ /// The default name that would be used for this check constraint.
+ string GetDefaultName(in StoreObjectIdentifier storeObject)
+ {
+ var prefix = $"CK_{storeObject.Name}_";
+ return Uniquifier.Truncate(
+ ModelName.StartsWith(prefix, System.StringComparison.Ordinal)
+ ? ModelName
+ : prefix + ModelName,
+ EntityType.Model.GetMaxIdentifierLength());
+ }
+
///
/// Gets the entity type on which this check constraint is defined.
///
diff --git a/src/EFCore.Relational/Metadata/ITable.cs b/src/EFCore.Relational/Metadata/ITable.cs
index 91115a416a6..d836f5eda89 100644
--- a/src/EFCore.Relational/Metadata/ITable.cs
+++ b/src/EFCore.Relational/Metadata/ITable.cs
@@ -54,7 +54,7 @@ public interface ITable : ITableBase
/// Gets the check constraints for this table.
///
IEnumerable CheckConstraints
- => EntityTypeMappings.SelectMany(m => CheckConstraint.GetCheckConstraints(m.EntityType))
+ => EntityTypeMappings.SelectMany(m => m.EntityType.GetDeclaredCheckConstraints())
.Distinct((x, y) => x!.Name == y!.Name);
///
diff --git a/src/EFCore.Relational/Metadata/Internal/CheckConstraint.cs b/src/EFCore.Relational/Metadata/Internal/CheckConstraint.cs
index dbcff4117dd..38e1fb297d6 100644
--- a/src/EFCore.Relational/Metadata/Internal/CheckConstraint.cs
+++ b/src/EFCore.Relational/Metadata/Internal/CheckConstraint.cs
@@ -7,6 +7,7 @@
using System.Linq;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore.Utilities;
namespace Microsoft.EntityFrameworkCore.Metadata.Internal
@@ -19,7 +20,11 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal
///
public class CheckConstraint : ConventionAnnotatable, IMutableCheckConstraint, IConventionCheckConstraint, ICheckConstraint
{
+ private string? _name;
+ private InternalCheckConstraintBuilder? _builder;
+
private ConfigurationSource _configurationSource;
+ private ConfigurationSource? _nameConfigurationSource;
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -38,7 +43,7 @@ public CheckConstraint(
Check.NotEmpty(sql, nameof(sql));
EntityType = entityType;
- Name = name;
+ ModelName = name;
Sql = sql;
_configurationSource = configurationSource;
@@ -49,14 +54,34 @@ public CheckConstraint(
((IMutableEntityType)EntityType).SetOrRemoveAnnotation(RelationalAnnotationNames.CheckConstraints, constraints);
}
- if (constraints.ContainsKey(Name))
+ if (constraints.ContainsKey(name))
+ {
+ throw new InvalidOperationException(RelationalStrings.DuplicateCheckConstraint(
+ name, EntityType.DisplayName(), EntityType.DisplayName()));
+ }
+
+ var baseCheckConstraint = entityType.BaseType?.FindCheckConstraint(name);
+ if (baseCheckConstraint != null)
{
- throw new InvalidOperationException(RelationalStrings.DuplicateCheckConstraint(Name, EntityType.DisplayName()));
+ throw new InvalidOperationException(RelationalStrings.DuplicateCheckConstraint(
+ name, EntityType.DisplayName(), baseCheckConstraint.EntityType.DisplayName()));
+ }
+
+ foreach (var derivedType in entityType.GetDerivedTypes())
+ {
+ var derivedCheckConstraint = FindCheckConstraint(derivedType, name);
+ if (derivedCheckConstraint != null)
+ {
+ throw new InvalidOperationException(RelationalStrings.DuplicateCheckConstraint(
+ name, EntityType.DisplayName(), derivedCheckConstraint.EntityType.DisplayName()));
+ }
}
EnsureMutable();
constraints.Add(name, this);
+
+ _builder = new InternalCheckConstraintBuilder(this, ((IConventionModel)entityType.Model).Builder);
}
///
@@ -65,9 +90,13 @@ public CheckConstraint(
/// 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 IEnumerable GetCheckConstraints(IReadOnlyEntityType entityType)
+ public static IEnumerable GetDeclaredCheckConstraints(IReadOnlyEntityType entityType)
{
Check.NotNull(entityType, nameof(entityType));
+ if (entityType is RuntimeEntityType)
+ {
+ throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData);
+ }
return GetConstraintsDictionary(entityType)?.Values ?? Enumerable.Empty();
}
@@ -78,12 +107,32 @@ public static IEnumerable GetCheckConstraints(IReadOnlyEntityT
/// 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 ICheckConstraint? FindCheckConstraint(
+ public static IEnumerable GetCheckConstraints(IReadOnlyEntityType entityType)
+ => entityType.BaseType != null
+ ? GetCheckConstraints(entityType.BaseType).Concat(GetDeclaredCheckConstraints(entityType))
+ : GetDeclaredCheckConstraints(entityType);
+
+ ///
+ /// 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 IReadOnlyCheckConstraint? FindCheckConstraint(
IReadOnlyEntityType entityType,
string name)
+ => entityType.BaseType?.FindCheckConstraint(name)
+ ?? FindDeclaredCheckConstraint(entityType, name);
+
+ ///
+ /// 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 IReadOnlyCheckConstraint? FindDeclaredCheckConstraint(IReadOnlyEntityType entityType, string name)
{
var dataDictionary = GetConstraintsDictionary(entityType);
-
return dataDictionary == null
? null
: dataDictionary.TryGetValue(name, out var checkConstraint)
@@ -97,7 +146,7 @@ public static IEnumerable GetCheckConstraints(IReadOnlyEntityT
/// 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 CheckConstraint? RemoveCheckConstraint(
+ public static IMutableCheckConstraint? RemoveCheckConstraint(
IMutableEntityType entityType,
string name)
{
@@ -108,6 +157,7 @@ public static IEnumerable GetCheckConstraints(IReadOnlyEntityT
{
var checkConstraint = (CheckConstraint)constraint;
checkConstraint.EnsureMutable();
+ checkConstraint.SetRemovedFromModel();
dataDictionary.Remove(name);
return checkConstraint;
@@ -116,6 +166,83 @@ public static IEnumerable GetCheckConstraints(IReadOnlyEntityT
return null;
}
+ ///
+ /// 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 Attach(IConventionCheckConstraint detachedCheckConstraint, IConventionCheckConstraint existingCheckConstraint)
+ {
+ var nameConfigurationSource = detachedCheckConstraint.GetNameConfigurationSource();
+ if (nameConfigurationSource != null)
+ {
+ ((InternalCheckConstraintBuilder)existingCheckConstraint.Builder).HasName(
+ detachedCheckConstraint.Name, nameConfigurationSource.Value);
+ }
+ }
+
+ ///
+ /// 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 bool AreCompatible(
+ IReadOnlyCheckConstraint checkConstraint,
+ IReadOnlyCheckConstraint duplicateCheckConstraint,
+ in StoreObjectIdentifier storeObject,
+ bool shouldThrow)
+ {
+ if (checkConstraint.Sql != duplicateCheckConstraint.Sql)
+ {
+ if (shouldThrow)
+ {
+ throw new InvalidOperationException(
+ RelationalStrings.DuplicateCheckConstraintSqlMismatch(
+ checkConstraint.ModelName,
+ checkConstraint.EntityType.DisplayName(),
+ duplicateCheckConstraint.ModelName,
+ duplicateCheckConstraint.EntityType.DisplayName(),
+ checkConstraint.GetName(storeObject)));
+ }
+
+ return false;
+ }
+
+ return true;
+ }
+
+ ///
+ /// 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 virtual InternalCheckConstraintBuilder Builder
+ {
+ [DebuggerStepThrough]
+ get => _builder ?? throw new InvalidOperationException(CoreStrings.ObjectRemovedFromModel);
+ }
+
+ ///
+ /// 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 virtual bool IsInModel
+ => _builder is not null;
+
+ ///
+ /// 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 virtual void SetRemovedFromModel()
+ => _builder = null;
+
///
/// 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
@@ -135,7 +262,51 @@ public static IEnumerable GetCheckConstraints(IReadOnlyEntityT
/// 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 virtual string Name { get; }
+ public virtual string ModelName { get; }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// 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 virtual string Name
+ {
+ get => _name ?? ((IReadOnlyCheckConstraint)this).GetDefaultName() ?? ModelName;
+ set => SetName(value, ConfigurationSource.Explicit);
+ }
+
+ ///
+ public virtual string GetName(in StoreObjectIdentifier storeObject)
+ => _name ?? ((IReadOnlyCheckConstraint)this).GetDefaultName(storeObject) ?? ModelName;
+
+ ///
+ /// 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 virtual string? SetName(string? name, ConfigurationSource configurationSource)
+ {
+ Check.NullButNotEmpty(name, nameof(name));
+
+ EnsureMutable();
+
+ _name = name;
+
+ _nameConfigurationSource = configurationSource.Max(_nameConfigurationSource);
+
+ return name;
+ }
+
+ ///
+ /// 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 virtual ConfigurationSource? GetNameConfigurationSource()
+ => _nameConfigurationSource;
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -212,5 +383,27 @@ IEntityType ICheckConstraint.EntityType
[DebuggerStepThrough]
get => (IEntityType)EntityType;
}
+
+ ///
+ /// 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.
+ ///
+ IConventionCheckConstraintBuilder IConventionCheckConstraint.Builder
+ {
+ [DebuggerStepThrough]
+ get => Builder;
+ }
+
+ ///
+ /// 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.
+ ///
+ [DebuggerStepThrough]
+ string? IConventionCheckConstraint.SetName(string? name, bool fromDataAnnotation)
+ => SetName(name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
}
}
diff --git a/src/EFCore.Relational/Metadata/Internal/DbFunction.cs b/src/EFCore.Relational/Metadata/Internal/DbFunction.cs
index 482db686a45..ea478a9bdb0 100644
--- a/src/EFCore.Relational/Metadata/Internal/DbFunction.cs
+++ b/src/EFCore.Relational/Metadata/Internal/DbFunction.cs
@@ -12,7 +12,7 @@
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
-using Microsoft.EntityFrameworkCore.Metadata.Builders.Internal;
+using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Utilities;
diff --git a/src/EFCore.Relational/Metadata/Internal/DbFunctionParameter.cs b/src/EFCore.Relational/Metadata/Internal/DbFunctionParameter.cs
index aa0cf60b435..e3d03600109 100644
--- a/src/EFCore.Relational/Metadata/Internal/DbFunctionParameter.cs
+++ b/src/EFCore.Relational/Metadata/Internal/DbFunctionParameter.cs
@@ -7,7 +7,6 @@
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
-using Microsoft.EntityFrameworkCore.Metadata.Builders.Internal;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Utilities;
diff --git a/src/EFCore.Relational/Metadata/Internal/InternalCheckConstraintBuilder.cs b/src/EFCore.Relational/Metadata/Internal/InternalCheckConstraintBuilder.cs
new file mode 100644
index 00000000000..c87ade75408
--- /dev/null
+++ b/src/EFCore.Relational/Metadata/Internal/InternalCheckConstraintBuilder.cs
@@ -0,0 +1,202 @@
+// 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.Collections.Generic;
+using System.Diagnostics;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+using Microsoft.EntityFrameworkCore.Utilities;
+
+namespace Microsoft.EntityFrameworkCore.Metadata.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 class InternalCheckConstraintBuilder :
+ AnnotatableBuilder,
+ IConventionCheckConstraintBuilder
+ {
+ ///
+ /// 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 InternalCheckConstraintBuilder(CheckConstraint checkConstraint, IConventionModelBuilder modelBuilder)
+ : base(checkConstraint, modelBuilder)
+ {
+ }
+
+ ///
+ /// 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 virtual IConventionCheckConstraintBuilder? HasName(string? name, ConfigurationSource configurationSource)
+ {
+ if (CanSetName(name, configurationSource))
+ {
+ Metadata.SetName(name, configurationSource);
+ return this;
+ }
+
+ return null;
+ }
+
+ ///
+ /// 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 virtual bool CanSetName(string? name, ConfigurationSource configurationSource)
+ => configurationSource.Overrides(Metadata.GetNameConfigurationSource())
+ || Metadata.Name == name;
+
+ ///
+ /// 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 IConventionCheckConstraint? HasCheckConstraint(
+ IConventionEntityType entityType,
+ string name,
+ string? sql,
+ ConfigurationSource configurationSource)
+ {
+ Check.NotEmpty(name, nameof(name));
+ Check.NullButNotEmpty(sql, nameof(sql));
+
+ List? checkConstraintsToBeDetached = null;
+ var constraint = entityType.FindCheckConstraint(name);
+ if (constraint != null)
+ {
+ if (constraint.Sql == sql)
+ {
+ ((CheckConstraint)constraint).UpdateConfigurationSource(configurationSource);
+ return constraint;
+ }
+
+ if (!configurationSource.Overrides(constraint.GetConfigurationSource()))
+ {
+ return null;
+ }
+
+ entityType.RemoveCheckConstraint(name);
+ constraint = null;
+ }
+ else
+ {
+ foreach (var derivedType in entityType.GetDerivedTypes())
+ {
+ var derivedCheckConstraint = (IConventionCheckConstraint?)CheckConstraint.FindDeclaredCheckConstraint(derivedType, name);
+ if (derivedCheckConstraint == null)
+ {
+ continue;
+ }
+
+ if (derivedCheckConstraint.Sql != sql
+ && !configurationSource.Overrides(derivedCheckConstraint.GetConfigurationSource()))
+ {
+ return null;
+ }
+
+ if (checkConstraintsToBeDetached == null)
+ {
+ checkConstraintsToBeDetached = new List();
+ }
+
+ checkConstraintsToBeDetached.Add(derivedCheckConstraint);
+ }
+ }
+
+ List? detachedCheckConstraints = null;
+ if (checkConstraintsToBeDetached != null)
+ {
+ detachedCheckConstraints = new List();
+ foreach (var checkConstraintToBeDetached in checkConstraintsToBeDetached)
+ {
+ detachedCheckConstraints.Add(
+ checkConstraintToBeDetached.EntityType.RemoveCheckConstraint(checkConstraintToBeDetached.ModelName)!);
+ }
+ }
+
+ if (sql != null)
+ {
+ constraint = new CheckConstraint((IMutableEntityType)entityType, name, sql, configurationSource);
+
+ if (detachedCheckConstraints != null)
+ {
+ foreach (var detachedCheckConstraint in detachedCheckConstraints)
+ {
+ CheckConstraint.Attach(detachedCheckConstraint, constraint);
+ }
+ }
+ }
+
+ return constraint;
+ }
+
+ ///
+ /// 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 bool CanHaveCheckConstraint(
+ IConventionEntityType entityType,
+ string name,
+ string? sql,
+ ConfigurationSource configurationSource)
+ {
+ Check.NotEmpty(name, nameof(name));
+ Check.NullButNotEmpty(sql, nameof(sql));
+
+ var constraint = entityType.FindCheckConstraint(name);
+ if (constraint != null)
+ {
+ return constraint.Sql == sql
+ || configurationSource.Overrides(constraint.GetConfigurationSource());
+ }
+
+ foreach (var derivedType in entityType.GetDerivedTypes())
+ {
+ var derivedCheckConstraint = (IConventionCheckConstraint?)CheckConstraint.FindDeclaredCheckConstraint(derivedType, name);
+ if (derivedCheckConstraint == null)
+ {
+ continue;
+ }
+
+ if (derivedCheckConstraint.Sql != sql
+ && !configurationSource.Overrides(derivedCheckConstraint.GetConfigurationSource()))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ IConventionCheckConstraint IConventionCheckConstraintBuilder.Metadata
+ {
+ [DebuggerStepThrough]
+ get => Metadata;
+ }
+
+ ///
+ [DebuggerStepThrough]
+ IConventionCheckConstraintBuilder? IConventionCheckConstraintBuilder.HasName(string? name, bool fromDataAnnotation)
+ => HasName(name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+
+ ///
+ [DebuggerStepThrough]
+ bool IConventionCheckConstraintBuilder.CanSetName(string? name, bool fromDataAnnotation)
+ => CanSetName(name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+ }
+}
diff --git a/src/EFCore.Relational/Metadata/Internal/InternalDbFunctionBuilder.cs b/src/EFCore.Relational/Metadata/Internal/InternalDbFunctionBuilder.cs
index cd9eecbd5f0..c69876f85ae 100644
--- a/src/EFCore.Relational/Metadata/Internal/InternalDbFunctionBuilder.cs
+++ b/src/EFCore.Relational/Metadata/Internal/InternalDbFunctionBuilder.cs
@@ -7,11 +7,12 @@
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Internal;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
using Microsoft.EntityFrameworkCore.Storage;
-namespace Microsoft.EntityFrameworkCore.Metadata.Builders.Internal
+namespace Microsoft.EntityFrameworkCore.Metadata.Internal
{
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
diff --git a/src/EFCore.Relational/Metadata/Internal/InternalDbFunctionParameterBuilder.cs b/src/EFCore.Relational/Metadata/Internal/InternalDbFunctionParameterBuilder.cs
index 7307a696573..80f171f952d 100644
--- a/src/EFCore.Relational/Metadata/Internal/InternalDbFunctionParameterBuilder.cs
+++ b/src/EFCore.Relational/Metadata/Internal/InternalDbFunctionParameterBuilder.cs
@@ -3,10 +3,10 @@
using System.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
-using Microsoft.EntityFrameworkCore.Metadata.Internal;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore.Storage;
-namespace Microsoft.EntityFrameworkCore.Metadata.Builders.Internal
+namespace Microsoft.EntityFrameworkCore.Metadata.Internal
{
///
///
diff --git a/src/EFCore.Relational/Metadata/Internal/InternalSequenceBuilder.cs b/src/EFCore.Relational/Metadata/Internal/InternalSequenceBuilder.cs
index 68461572268..2cddee782ae 100644
--- a/src/EFCore.Relational/Metadata/Internal/InternalSequenceBuilder.cs
+++ b/src/EFCore.Relational/Metadata/Internal/InternalSequenceBuilder.cs
@@ -5,9 +5,9 @@
using System.Diagnostics;
using System.Linq;
using Microsoft.EntityFrameworkCore.Infrastructure;
-using Microsoft.EntityFrameworkCore.Metadata.Internal;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
-namespace Microsoft.EntityFrameworkCore.Metadata.Builders.Internal
+namespace Microsoft.EntityFrameworkCore.Metadata.Internal
{
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
diff --git a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs
index 960d97c54c8..891a0d3d8da 100644
--- a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs
+++ b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs
@@ -182,9 +182,12 @@ public static IRelationalModel Create(
constraint.AddAnnotations(relationalAnnotationProvider.For(constraint, designTime));
}
- foreach (var checkConstraint in ((ITable)table).CheckConstraints)
+ if (designTime)
{
- ((AnnotatableBase)checkConstraint).AddAnnotations(relationalAnnotationProvider.For(checkConstraint, designTime));
+ foreach (var checkConstraint in ((ITable)table).CheckConstraints)
+ {
+ ((AnnotatableBase)checkConstraint).AddAnnotations(relationalAnnotationProvider.For(checkConstraint, designTime));
+ }
}
table.AddAnnotations(relationalAnnotationProvider.For(table, designTime));
diff --git a/src/EFCore.Relational/Metadata/Internal/Sequence.cs b/src/EFCore.Relational/Metadata/Internal/Sequence.cs
index a9133fefabc..460a4be9658 100644
--- a/src/EFCore.Relational/Metadata/Internal/Sequence.cs
+++ b/src/EFCore.Relational/Metadata/Internal/Sequence.cs
@@ -10,7 +10,6 @@
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
-using Microsoft.EntityFrameworkCore.Metadata.Builders.Internal;
using Microsoft.EntityFrameworkCore.Utilities;
namespace Microsoft.EntityFrameworkCore.Metadata.Internal
diff --git a/src/EFCore.Relational/Metadata/RuntimeCheckConstraint.cs b/src/EFCore.Relational/Metadata/RuntimeCheckConstraint.cs
deleted file mode 100644
index 29ef96876e4..00000000000
--- a/src/EFCore.Relational/Metadata/RuntimeCheckConstraint.cs
+++ /dev/null
@@ -1,82 +0,0 @@
-// 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.Diagnostics;
-using Microsoft.EntityFrameworkCore.Infrastructure;
-
-namespace Microsoft.EntityFrameworkCore.Metadata
-{
- ///
- /// Represents a check constraint in the .
- ///
- public class RuntimeCheckConstraint : AnnotatableBase, ICheckConstraint
- {
- ///
- /// Initializes a new instance of the class.
- ///
- /// The constraint name.
- /// The affected entity type.
- /// The SQL string.
- public RuntimeCheckConstraint(
- string name,
- RuntimeEntityType entityType,
- string sql)
- {
- EntityType = entityType;
- Name = name;
- Sql = sql;
- }
-
- ///
- /// Gets the entity type on which this check constraint is defined.
- ///
- public virtual RuntimeEntityType EntityType { get; }
-
- ///
- /// Gets the name of the check constraint in the database.
- ///
- public virtual string Name { get; }
-
- private string Sql { get; }
-
- ///
- /// Returns a string that represents the current object.
- ///
- /// A string that represents the current object.
- public override string ToString()
- => ((ICheckConstraint)this).ToDebugString(MetadataDebugStringOptions.SingleLineDefault);
-
- ///
- /// 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.
- ///
- [EntityFrameworkInternal]
- public virtual DebugView DebugView
- => new(
- () => ((ICheckConstraint)this).ToDebugString(MetadataDebugStringOptions.ShortDefault),
- () => ((ICheckConstraint)this).ToDebugString(MetadataDebugStringOptions.LongDefault));
-
- ///
- IReadOnlyEntityType IReadOnlyCheckConstraint.EntityType
- {
- [DebuggerStepThrough]
- get => EntityType;
- }
-
- ///
- IEntityType ICheckConstraint.EntityType
- {
- [DebuggerStepThrough]
- get => EntityType;
- }
-
- ///
- string IReadOnlyCheckConstraint.Sql
- {
- [DebuggerStepThrough]
- get => Sql;
- }
- }
-}
diff --git a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs
index 7c27804de4c..eb95cd991a4 100644
--- a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs
+++ b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs
@@ -310,12 +310,20 @@ public static string DistinctOnCollectionNotSupported
=> GetString("DistinctOnCollectionNotSupported");
///
- /// The check constraint '{checkConstraint}' cannot be added to the entity type '{entityType}' because another check constraint with the same name already exists.
+ /// The check constraint '{checkConstraint}' cannot be added to the entity type '{entityType}' because another check constraint with the same name already exists on entity type '{conflictingEntityType}'.
///
- public static string DuplicateCheckConstraint(object? checkConstraint, object? entityType)
+ public static string DuplicateCheckConstraint(object? checkConstraint, object? entityType, object? conflictingEntityType)
=> string.Format(
- GetString("DuplicateCheckConstraint", nameof(checkConstraint), nameof(entityType)),
- checkConstraint, entityType);
+ GetString("DuplicateCheckConstraint", nameof(checkConstraint), nameof(entityType), nameof(conflictingEntityType)),
+ checkConstraint, entityType, conflictingEntityType);
+
+ ///
+ /// The check constraints '{checkConstraint1}' on '{entityType1}' and '{checkConstraint2}' on '{entityType2}' are both mapped to '{checkConstraintName}', but with different defining SQL.
+ ///
+ public static string DuplicateCheckConstraintSqlMismatch(object? checkConstraint1, object? entityType1, object? checkConstraint2, object? entityType2, object? checkConstraintName)
+ => string.Format(
+ GetString("DuplicateCheckConstraintSqlMismatch", nameof(checkConstraint1), nameof(entityType1), nameof(checkConstraint2), nameof(entityType2), nameof(checkConstraintName)),
+ checkConstraint1, entityType1, checkConstraint2, entityType2, checkConstraintName);
///
/// '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}', but are configured to use different collations ('{collation1}' and '{collation2}').
diff --git a/src/EFCore.Relational/Properties/RelationalStrings.resx b/src/EFCore.Relational/Properties/RelationalStrings.resx
index ac1def7f471..d465a7c314f 100644
--- a/src/EFCore.Relational/Properties/RelationalStrings.resx
+++ b/src/EFCore.Relational/Properties/RelationalStrings.resx
@@ -232,7 +232,10 @@
Using 'Distinct' operation on a projection containing a collection is not supported.
- The check constraint '{checkConstraint}' cannot be added to the entity type '{entityType}' because another check constraint with the same name already exists.
+ The check constraint '{checkConstraint}' cannot be added to the entity type '{entityType}' because another check constraint with the same name already exists on entity type '{conflictingEntityType}'.
+
+
+ The check constraints '{checkConstraint1}' on '{entityType1}' and '{checkConstraint2}' on '{entityType2}' are both mapped to '{checkConstraintName}', but with different defining SQL.
'{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}', but are configured to use different collations ('{collation1}' and '{collation2}').
diff --git a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs
index c0c667a6180..7b5b72695b4 100644
--- a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs
+++ b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs
@@ -631,7 +631,7 @@ public virtual void CheckConstraint_is_stored_in_snapshot_as_fluent_api()
builder =>
{
builder.Entity()
- .HasCheckConstraint("CK_Customer_AlternateId", "AlternateId > Id");
+ .HasCheckConstraint("CK_Customer_AlternateId", "AlternateId > Id", ck => ck.HasName("CK_Customer_AlternateId"));
builder.Ignore();
},
AddBoilerPlate(
@@ -651,11 +651,12 @@ public virtual void CheckConstraint_is_stored_in_snapshot_as_fluent_api()
b.ToTable(""EntityWithTwoProperties"");
- b.HasCheckConstraint(""CK_Customer_AlternateId"", ""AlternateId > Id"");
+ b.HasCheckConstraint(""CK_Customer_AlternateId"", ""AlternateId > Id"", c => c.HasName(""CK_Customer_AlternateId""));
});"),
o =>
{
- Assert.Equal(2, o.GetAnnotations().Count());
+ var constraint = o.GetEntityTypes().Single().GetCheckConstraints().Single();
+ Assert.Equal("CK_Customer_AlternateId", constraint.Name);
});
}
@@ -666,7 +667,7 @@ public virtual void CheckConstraint_is_only_stored_in_snapshot_once_for_TPH()
builder =>
{
builder.Entity()
- .HasCheckConstraint("CK_Customer_AlternateId", "AlternateId > Id");
+ .HasCheckConstraint("CK_BaseEntity_AlternateId", "AlternateId > Id");
builder.Entity();
},
AddBoilerPlate(
@@ -699,11 +700,12 @@ public virtual void CheckConstraint_is_only_stored_in_snapshot_once_for_TPH()
b.HasDiscriminator().HasValue(""DerivedEntity"");
- b.HasCheckConstraint(""CK_Customer_AlternateId"", ""AlternateId > Id"");
+ b.HasCheckConstraint(""CK_BaseEntity_AlternateId"", ""AlternateId > Id"");
});"),
o =>
{
- Assert.Equal(2, o.GetAnnotations().Count());
+ var constraint = o.FindEntityType(typeof(DerivedEntity)).GetDeclaredCheckConstraints().Single();
+ Assert.Equal("CK_BaseEntity_AlternateId", constraint.Name);
});
}
diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs
index fa1c072dab4..8a42fc3a8d9 100644
--- a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs
+++ b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs
@@ -2600,7 +2600,6 @@ partial void Initialize()
c => AssertFileContents("DataEntityType.cs",
@"//
using System;
-using System.Collections.Generic;
using System.Reflection;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Scaffolding.Internal;
@@ -2643,22 +2642,6 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas
public static void CreateAnnotations(RuntimeEntityType runtimeEntityType)
{
- var constraints = new SortedDictionary();
- var anotherConstraint = new RuntimeCheckConstraint(
- ""anotherConstraint"",
- runtimeEntityType,
- ""Id <> -1"");
-
- constraints[""anotherConstraint""] = anotherConstraint;
-
- var idConstraint = new RuntimeCheckConstraint(
- ""idConstraint"",
- runtimeEntityType,
- ""Id <> 0"");
-
- constraints[""idConstraint""] = idConstraint;
-
- runtimeEntityType.AddAnnotation(""Relational:CheckConstraints"", constraints);
runtimeEntityType.AddAnnotation(""Relational:FunctionName"", null);
runtimeEntityType.AddAnnotation(""Relational:Schema"", null);
runtimeEntityType.AddAnnotation(""Relational:SqlQuery"", null);
@@ -2676,21 +2659,11 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType)
c)),
model =>
{
- Assert.Single(model.GetEntityTypes());
- var dataEntity = model.FindEntityType(typeof(Data));
+ var dataEntity = model.GetEntityTypes().Single();
- Assert.Equal(2, dataEntity.GetCheckConstraints().Count());
- var anotherConstraint = dataEntity.GetCheckConstraints().First();
- Assert.Same(dataEntity, anotherConstraint.EntityType);
- Assert.Equal("anotherConstraint", anotherConstraint.Name);
- Assert.Equal("Id <> -1", anotherConstraint.Sql);
- Assert.NotNull(anotherConstraint.ToString());
-
- var idConstraint = dataEntity.GetCheckConstraints().Last();
- Assert.Same(dataEntity, ((IReadOnlyCheckConstraint)idConstraint).EntityType);
- Assert.Equal("idConstraint", idConstraint.Name);
- Assert.Equal("Id <> 0", idConstraint.Sql);
- Assert.NotNull(idConstraint.ToString());
+ Assert.Equal(
+ CoreStrings.RuntimeModelMissingData,
+ Assert.Throws(() => dataEntity.GetCheckConstraints()).Message);
});
}
diff --git a/test/EFCore.Relational.Specification.Tests/Migrations/MigrationsTestBase.cs b/test/EFCore.Relational.Specification.Tests/Migrations/MigrationsTestBase.cs
index c7420cfeb02..1ee02252375 100644
--- a/test/EFCore.Relational.Specification.Tests/Migrations/MigrationsTestBase.cs
+++ b/test/EFCore.Relational.Specification.Tests/Migrations/MigrationsTestBase.cs
@@ -89,7 +89,7 @@ await Test(
e.HasKey("CustomId");
e.HasAlternateKey("SSN");
- e.HasCheckConstraint("CK_EmployerId", $"{DelimitIdentifier("EmployerId")} > 0");
+ e.HasCheckConstraint("EmployerId", $"{DelimitIdentifier("EmployerId")} > 0");
e.HasOne("Employers").WithMany("People").HasForeignKey("EmployerId");
e.HasComment("Table comment");
@@ -639,7 +639,7 @@ public virtual Task Add_column_with_check_constraint()
"People", e =>
{
e.Property("DriverLicense");
- e.HasCheckConstraint("CK_Foo", $"{DelimitIdentifier("DriverLicense")} > 0");
+ e.HasCheckConstraint("Foo", $"{DelimitIdentifier("DriverLicense")} > 0");
}),
model =>
{
@@ -1250,7 +1250,7 @@ public virtual Task Add_check_constraint_with_name()
e.Property("DriverLicense");
}),
builder => { },
- builder => builder.Entity("People").HasCheckConstraint("CK_Foo", $"{DelimitIdentifier("DriverLicense")} > 0"),
+ builder => builder.Entity("People").HasCheckConstraint("Foo", $"{DelimitIdentifier("DriverLicense")} > 0"),
model =>
{
// TODO: no scaffolding support for check constraints, https://github.com/aspnet/EntityFrameworkCore/issues/15408
@@ -1265,8 +1265,8 @@ public virtual Task Alter_check_constraint()
e.Property("Id");
e.Property("DriverLicense");
}),
- builder => builder.Entity("People").HasCheckConstraint("CK_Foo", $"{DelimitIdentifier("DriverLicense")} > 0"),
- builder => builder.Entity("People").HasCheckConstraint("CK_Foo", $"{DelimitIdentifier("DriverLicense")} > 1"),
+ builder => builder.Entity("People").HasCheckConstraint("Foo", $"{DelimitIdentifier("DriverLicense")} > 0"),
+ builder => builder.Entity("People").HasCheckConstraint("Foo", $"{DelimitIdentifier("DriverLicense")} > 1"),
model =>
{
// TODO: no scaffolding support for check constraints, https://github.com/aspnet/EntityFrameworkCore/issues/15408
@@ -1281,7 +1281,7 @@ public virtual Task Drop_check_constraint()
e.Property("Id");
e.Property("DriverLicense");
}),
- builder => builder.Entity("People").HasCheckConstraint("CK_Foo", $"{DelimitIdentifier("DriverLicense")} > 0"),
+ builder => builder.Entity("People").HasCheckConstraint("Foo", $"{DelimitIdentifier("DriverLicense")} > 0"),
builder => { },
model =>
{
diff --git a/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs b/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs
index 632b8741840..9894d169f4b 100644
--- a/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs
+++ b/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs
@@ -341,7 +341,7 @@ public virtual void Detects_incompatible_primary_keys_with_shared_table()
{
var modelBuilder = CreateConventionalModelBuilder();
- modelBuilder.Entity().HasOne().WithOne().HasForeignKey(a => a.Id).HasPrincipalKey(b => b.Id).IsRequired();
+ modelBuilder.Entity().HasOne().WithOne(b => b.A).HasForeignKey(a => a.Id).HasPrincipalKey(b => b.Id).IsRequired();
modelBuilder.Entity().HasKey(a => a.Id).HasName("Key");
modelBuilder.Entity().ToTable("Table");
modelBuilder.Entity().ToTable("Table");
@@ -357,7 +357,7 @@ public virtual void Detects_incompatible_comments_with_shared_table()
{
var modelBuilder = CreateConventionalModelBuilder();
- modelBuilder.Entity().HasOne().WithOne().HasPrincipalKey(a => a.Id).HasForeignKey(b => b.Id).IsRequired();
+ modelBuilder.Entity().HasOne().WithOne(b => b.A).HasPrincipalKey(a => a.Id).HasForeignKey(b => b.Id).IsRequired();
modelBuilder.Entity().ToTable("Table").HasComment("My comment");
modelBuilder.Entity().ToTable("Table").HasComment("my comment");
@@ -372,7 +372,7 @@ public virtual void Passes_on_null_comments()
{
var modelBuilder = CreateConventionalModelBuilder();
- modelBuilder.Entity().HasOne().WithOne().HasPrincipalKey(a => a.Id).HasForeignKey(b => b.Id).IsRequired();
+ modelBuilder.Entity().HasOne().WithOne(b => b.A).HasPrincipalKey(a => a.Id).HasForeignKey(b => b.Id).IsRequired();
modelBuilder.Entity().ToTable("Table").HasComment("My comment");
modelBuilder.Entity().ToTable("Table");
@@ -384,7 +384,7 @@ public virtual void Detects_incompatible_primary_key_columns_with_shared_table()
{
var modelBuilder = CreateConventionalModelBuilder();
- modelBuilder.Entity().HasOne().WithOne().HasForeignKey(a => a.Id).HasPrincipalKey(b => b.Id).IsRequired();
+ modelBuilder.Entity().HasOne().WithOne(b => b.A).HasForeignKey(a => a.Id).HasPrincipalKey(b => b.Id).IsRequired();
modelBuilder.Entity().Property(a => a.Id).ValueGeneratedNever().HasColumnName("Key");
modelBuilder.Entity().ToTable("Table");
modelBuilder.Entity().Property(a => a.Id).ValueGeneratedNever().HasColumnName(nameof(B.Id));
@@ -400,7 +400,7 @@ public virtual void Passes_on_not_configured_shared_columns_with_shared_table()
{
var modelBuilder = CreateConventionalModelBuilder();
- modelBuilder.Entity().HasOne().WithOne().HasForeignKey(a => a.Id).HasPrincipalKey(b => b.Id).IsRequired();
+ modelBuilder.Entity().HasOne().WithOne(b => b.A).HasForeignKey(a => a.Id).HasPrincipalKey(b => b.Id).IsRequired();
modelBuilder.Entity().Property(a => a.P0).HasColumnName(nameof(A.P0));
modelBuilder.Entity().Property(a => a.P1).IsRequired();
modelBuilder.Entity().ToTable("Table");
@@ -440,7 +440,7 @@ public virtual void Detects_incompatible_shared_columns_with_shared_table()
{
var modelBuilder = CreateConventionalModelBuilder();
- modelBuilder.Entity().HasOne().WithOne().HasForeignKey(a => a.Id).HasPrincipalKey(b => b.Id).IsRequired();
+ modelBuilder.Entity().HasOne().WithOne(b => b.A).HasForeignKey(a => a.Id).HasPrincipalKey(b => b.Id).IsRequired();
modelBuilder.Entity().Property(a => a.P0).HasColumnName(nameof(A.P0)).HasColumnType("someInt");
modelBuilder.Entity().ToTable("Table");
modelBuilder.Entity().Property(b => b.P0).HasColumnName(nameof(A.P0)).HasColumnType("default_int_mapping");
@@ -452,6 +452,57 @@ public virtual void Detects_incompatible_shared_columns_with_shared_table()
modelBuilder);
}
+ [ConditionalFact]
+ public virtual void Detects_incompatible_shared_check_constraints_with_shared_table()
+ {
+ var modelBuilder = CreateConventionalModelBuilder();
+
+ modelBuilder.Entity().HasOne().WithOne(b => b.A).HasForeignKey(a => a.Id).HasPrincipalKey(b => b.Id).IsRequired();
+ modelBuilder.Entity().HasCheckConstraint("SomeCK", "Id > 0", c => c.HasName("CK_Table_SomeCK"));
+ modelBuilder.Entity().ToTable("Table");
+ modelBuilder.Entity().HasCheckConstraint("SomeOtherCK", "Id > 10", c => c.HasName("CK_Table_SomeCK"));
+ modelBuilder.Entity().ToTable("Table");
+
+ VerifyError(
+ RelationalStrings.DuplicateCheckConstraintSqlMismatch(
+ "SomeOtherCK", nameof(B), "SomeCK", nameof(A), "CK_Table_SomeCK"),
+ modelBuilder);
+ }
+
+ [ConditionalFact]
+ public virtual void Passes_for_incompatible_uniquified_check_constraints_with_shared_table()
+ {
+ var modelBuilder = CreateConventionalModelBuilder();
+
+ modelBuilder.Entity().HasOne().WithOne(b => b.A).HasForeignKey(a => a.Id).HasPrincipalKey(b => b.Id).IsRequired();
+ modelBuilder.Entity().HasCheckConstraint("SomeCK", "Id > 0");
+ modelBuilder.Entity().ToTable("Table");
+ modelBuilder.Entity().HasCheckConstraint("SomeCK", "Id > 10");
+ modelBuilder.Entity().ToTable("Table");
+
+ var model = Validate(modelBuilder);
+
+ Assert.Equal("CK_Table_SomeCK1", model.FindEntityType(typeof(A)).GetCheckConstraints().Single().Name);
+ Assert.Equal("CK_Table_SomeCK", model.FindEntityType(typeof(B)).GetCheckConstraints().Single().Name);
+ }
+
+ [ConditionalFact]
+ public virtual void Passes_for_compatible_shared_check_constraints_with_shared_table()
+ {
+ var modelBuilder = CreateConventionalModelBuilder();
+
+ modelBuilder.Entity().HasOne().WithOne(b => b.A).HasForeignKey(a => a.Id).HasPrincipalKey(b => b.Id).IsRequired();
+ modelBuilder.Entity().HasCheckConstraint("SomeCK", "Id > 0");
+ modelBuilder.Entity().ToTable("Table");
+ modelBuilder.Entity().HasCheckConstraint("SomeCK", "Id > 0");
+ modelBuilder.Entity().ToTable("Table");
+
+ var model = Validate(modelBuilder);
+
+ Assert.Equal("CK_Table_SomeCK", model.FindEntityType(typeof(A)).GetCheckConstraints().Single().Name);
+ Assert.Equal("CK_Table_SomeCK", model.FindEntityType(typeof(B)).GetCheckConstraints().Single().Name);
+ }
+
[ConditionalFact]
public virtual void Detects_multiple_shared_table_roots()
{
diff --git a/test/EFCore.Relational.Tests/Metadata/RelationalBuilderExtensionsTest.cs b/test/EFCore.Relational.Tests/Metadata/RelationalBuilderExtensionsTest.cs
index 1b98a2cbef5..b659e760638 100644
--- a/test/EFCore.Relational.Tests/Metadata/RelationalBuilderExtensionsTest.cs
+++ b/test/EFCore.Relational.Tests/Metadata/RelationalBuilderExtensionsTest.cs
@@ -1,11 +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.
-using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
-using Microsoft.EntityFrameworkCore.Diagnostics;
+using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.TestUtilities;
@@ -39,23 +38,6 @@ public void Can_set_fixed_length()
Assert.False(property.IsFixedLength());
}
- [ConditionalFact]
- public void Can_write_index_builder_extension_with_where_clauses()
- {
- var builder = CreateConventionModelBuilder();
-
- var returnedBuilder = builder
- .Entity()
- .HasIndex(e => e.Id)
- .HasFilter("[Id] % 2 = 0");
-
- Assert.IsType>(returnedBuilder);
-
- var model = builder.Model;
- var index = model.FindEntityType(typeof(Customer)).GetIndexes().Single();
- Assert.Equal("[Id] % 2 = 0", index.GetFilter());
- }
-
[ConditionalFact]
public void Can_set_column_name()
{
@@ -251,18 +233,21 @@ public void Default_alternate_key_name_is_based_on_key_column_names()
}
[ConditionalFact]
- public void Can_set_key_name()
+ public void Can_access_key()
{
- var modelBuilder = CreateConventionModelBuilder();
+ var modelBuilder = CreateBuilder();
+ var entityTypeBuilder = modelBuilder.Entity(typeof(Splot), ConfigurationSource.Convention);
+ var idProperty = entityTypeBuilder.Property(typeof(int), "Id", ConfigurationSource.Convention).Metadata;
+ var keyBuilder = entityTypeBuilder.HasKey(new[] { idProperty.Name }, ConfigurationSource.Convention);
- modelBuilder
- .Entity()
- .HasKey(e => e.Id)
- .HasName("KeyLimePie");
+ Assert.NotNull(keyBuilder.HasName("Splew"));
+ Assert.Equal("Splew", keyBuilder.Metadata.GetName());
- var key = modelBuilder.Model.FindEntityType(typeof(Customer)).FindPrimaryKey();
+ Assert.NotNull(keyBuilder.HasName("Splow", fromDataAnnotation: true));
+ Assert.Equal("Splow", keyBuilder.Metadata.GetName());
- Assert.Equal("KeyLimePie", key.GetName());
+ Assert.Null(keyBuilder.HasName("Splod"));
+ Assert.Equal("Splow", keyBuilder.Metadata.GetName());
}
[ConditionalFact]
@@ -394,6 +379,47 @@ public void Can_set_foreign_key_name_for_one_to_one_with_FK_specified()
Assert.Equal("LemonSupreme", foreignKey.GetConstraintName());
}
+ [ConditionalFact]
+ public void Can_access_index()
+ {
+ var modelBuilder = CreateBuilder();
+ var entityTypeBuilder = modelBuilder.Entity(typeof(Splot), ConfigurationSource.Convention);
+ entityTypeBuilder.Property(typeof(int), "Id", ConfigurationSource.Convention);
+ var indexBuilder = entityTypeBuilder.HasIndex(new[] { "Id" }, ConfigurationSource.Convention);
+
+#pragma warning disable CS0618 // Type or member is obsolete
+ Assert.NotNull(indexBuilder.HasName("Splew"));
+ Assert.Equal("Splew", indexBuilder.Metadata.GetName());
+
+ Assert.NotNull(indexBuilder.HasName("Splow", fromDataAnnotation: true));
+ Assert.Equal("Splow", indexBuilder.Metadata.GetName());
+
+ Assert.Null(indexBuilder.HasName("Splod"));
+ Assert.Equal("Splow", indexBuilder.Metadata.GetName());
+
+ Assert.NotNull(indexBuilder.HasName(null, fromDataAnnotation: true));
+ Assert.Equal("IX_Splot_Id", indexBuilder.Metadata.GetName());
+
+ Assert.NotNull(indexBuilder.HasName("Splod"));
+ Assert.Equal("Splod", indexBuilder.Metadata.GetName());
+#pragma warning restore CS0618 // Type or member is obsolete
+
+ Assert.NotNull(indexBuilder.HasFilter("Splew"));
+ Assert.Equal("Splew", indexBuilder.Metadata.GetFilter());
+
+ Assert.NotNull(indexBuilder.HasFilter("Splow", fromDataAnnotation: true));
+ Assert.Equal("Splow", indexBuilder.Metadata.GetFilter());
+
+ Assert.Null(indexBuilder.HasFilter("Splod"));
+ Assert.Equal("Splow", indexBuilder.Metadata.GetFilter());
+
+ Assert.NotNull(indexBuilder.HasFilter(null, fromDataAnnotation: true));
+ Assert.Null(indexBuilder.Metadata.GetFilter());
+
+ Assert.Null(indexBuilder.HasFilter("Splod"));
+ Assert.Null(indexBuilder.Metadata.GetFilter());
+ }
+
[ConditionalFact]
public void Default_index_database_name_is_based_on_index_column_names()
{
@@ -431,67 +457,76 @@ public void Can_set_index_database_name()
}
[ConditionalFact]
- public void Can_set_table_name()
+ public void Can_write_index_filter_with_where_clauses()
{
- var modelBuilder = CreateConventionModelBuilder();
+ var builder = CreateConventionModelBuilder();
- modelBuilder
+ var returnedBuilder = builder
.Entity()
- .ToTable("Customizer");
+ .HasIndex(e => e.Id)
+ .HasFilter("[Id] % 2 = 0");
- var entityType = modelBuilder.Model.FindEntityType(typeof(Customer));
+ Assert.IsType>(returnedBuilder);
- Assert.Equal("Customer", entityType.DisplayName());
- Assert.Equal("Customizer", entityType.GetTableName());
+ var model = builder.Model;
+ var index = model.FindEntityType(typeof(Customer)).GetIndexes().Single();
+ Assert.Equal("[Id] % 2 = 0", index.GetFilter());
}
[ConditionalFact]
- public void Can_set_table_name_non_generic()
+ public void Can_set_table_name()
{
- var modelBuilder = CreateConventionModelBuilder();
+ var typeBuilder = CreateBuilder().Entity(typeof(Splot), ConfigurationSource.Convention);
- modelBuilder
- .Entity(typeof(Customer))
- .ToTable("Customizer");
+ Assert.NotNull(typeBuilder.ToTable("Splew"));
+ Assert.Equal("Splew", typeBuilder.Metadata.GetTableName());
- var entityType = modelBuilder.Model.FindEntityType(typeof(Customer));
+ Assert.NotNull(typeBuilder.ToTable("Splow", fromDataAnnotation: true));
+ Assert.Equal("Splow", typeBuilder.Metadata.GetTableName());
- Assert.Equal("Customer", entityType.DisplayName());
- Assert.Equal("Customizer", entityType.GetTableName());
+ Assert.Null(typeBuilder.ToTable("Splod"));
+ Assert.Equal("Splow", typeBuilder.Metadata.GetTableName());
}
[ConditionalFact]
- public void Can_set_table_and_schema_name()
+ public void Can_set_table_name_and_schema()
{
- var modelBuilder = CreateConventionModelBuilder();
+ var typeBuilder = CreateBuilder().Entity(typeof(Splot), ConfigurationSource.Convention);
- modelBuilder
- .Entity()
- .ToTable("Customizer", "db0");
+ Assert.NotNull(typeBuilder.ToTable("Splew", "1"));
+ Assert.Equal("Splew", typeBuilder.Metadata.GetTableName());
+ Assert.Equal("1", typeBuilder.Metadata.GetSchema());
- var entityType = modelBuilder.Model.FindEntityType(typeof(Customer));
+ Assert.NotNull(typeBuilder.ToTable("Splow", "2", fromDataAnnotation: true));
+ Assert.Equal("Splow", typeBuilder.Metadata.GetTableName());
+ Assert.Equal("2", typeBuilder.Metadata.GetSchema());
- Assert.Equal("Customer", entityType.DisplayName());
- Assert.Equal("Customizer", entityType.GetTableName());
- Assert.Equal("db0", entityType.GetSchema());
+ Assert.Null(typeBuilder.ToTable("Splod", "3"));
+ Assert.Equal("Splow", typeBuilder.Metadata.GetTableName());
+ Assert.Equal("2", typeBuilder.Metadata.GetSchema());
}
[ConditionalFact]
- public void Can_set_table_and_schema_name_non_generic()
+ public void Can_override_existing_schema()
{
- var modelBuilder = CreateConventionModelBuilder();
+ var typeBuilder = CreateBuilder().Entity(typeof(Splot), ConfigurationSource.Convention);
- modelBuilder
- .Entity(typeof(Customer))
- .ToTable("Customizer", "db0");
+ typeBuilder.Metadata.SetSchema("Explicit");
- var entityType = modelBuilder.Model.FindEntityType(typeof(Customer));
+ Assert.Null(typeBuilder.ToTable("Splod", "2", fromDataAnnotation: true));
+ Assert.Equal("Splot", typeBuilder.Metadata.GetTableName());
+ Assert.Equal("Explicit", typeBuilder.Metadata.GetSchema());
- Assert.Equal("Customer", entityType.DisplayName());
- Assert.Equal("Customizer", entityType.GetTableName());
- Assert.Equal("db0", entityType.GetSchema());
+ Assert.NotNull(typeBuilder.ToTable("Splod", "Explicit", fromDataAnnotation: true));
+ Assert.Equal("Splod", typeBuilder.Metadata.GetTableName());
+ Assert.Equal("Explicit", typeBuilder.Metadata.GetSchema());
+
+ Assert.NotNull(new EntityTypeBuilder(typeBuilder.Metadata).ToTable("Splew", "1"));
+ Assert.Equal("Splew", typeBuilder.Metadata.GetTableName());
+ Assert.Equal("1", typeBuilder.Metadata.GetSchema());
}
+
[ConditionalFact]
public void Can_create_check_constraint()
{
@@ -533,38 +568,106 @@ public void Can_create_check_constraint_with_duplicate_name_replaces_existing()
}
[ConditionalFact]
- public void AddCheckConstraint_with_duplicate_names_throws_exception()
+ public void Can_access_check_constraint()
{
- var entityTypeBuilder = CreateConventionModelBuilder().Entity();
- var entityType = entityTypeBuilder.Metadata;
+ var typeBuilder = CreateBuilder().Entity(typeof(Splot), ConfigurationSource.Convention);
+ IReadOnlyEntityType entityType = typeBuilder.Metadata;
+
+ Assert.NotNull(typeBuilder.HasCheckConstraint("Splew", "s > p"));
+ Assert.Equal("Splew", entityType.GetCheckConstraints().Single().ModelName);
+ Assert.Equal("s > p", entityType.GetCheckConstraints().Single().Sql);
- entityType.AddCheckConstraint("CK_Customer_AlternateId", "AlternateId > Id");
+ Assert.NotNull(typeBuilder.HasCheckConstraint("Splew", "s < p", fromDataAnnotation: true));
+ Assert.Equal("Splew", entityType.GetCheckConstraints().Single().ModelName);
+ Assert.Equal("s < p", entityType.GetCheckConstraints().Single().Sql);
- Assert.Equal(
- RelationalStrings.DuplicateCheckConstraint("CK_Customer_AlternateId", entityType.DisplayName()),
- Assert.Throws(
- () =>
- entityType.AddCheckConstraint("CK_Customer_AlternateId", "AlternateId < Id")).Message);
+ Assert.Null(typeBuilder.HasCheckConstraint("Splew", "s > p"));
+ Assert.Equal("Splew", entityType.GetCheckConstraints().Single().ModelName);
+ Assert.Equal("s < p", entityType.GetCheckConstraints().Single().Sql);
}
[ConditionalFact]
- public void RemoveCheckConstraint_returns_constraint_when_constraint_exists()
+ public void Base_check_constraint_overrides_derived_one()
{
- var entityTypeBuilder = CreateConventionModelBuilder().Entity();
- var entityType = entityTypeBuilder.Metadata;
+ var modelBuilder = CreateBuilder();
+
+ var derivedBuilder = modelBuilder.Entity(typeof(Splow), ConfigurationSource.Convention);
+ IReadOnlyEntityType derivedEntityType = derivedBuilder.Metadata;
+
+ Assert.NotNull(derivedBuilder.HasCheckConstraint("Splew", "s < p", fromDataAnnotation: true)
+ .HasName("CK_Splow", fromDataAnnotation: true));
+ Assert.Equal("Splew", derivedEntityType.GetCheckConstraints().Single().ModelName);
+ Assert.Equal("s < p", derivedEntityType.GetCheckConstraints().Single().Sql);
+ Assert.Equal("CK_Splow", derivedEntityType.GetCheckConstraints().Single().Name);
+
+ Assert.True(derivedBuilder.CanHaveCheckConstraint("Splew", "s < p"));
+ Assert.True(derivedBuilder.CanHaveCheckConstraint("Splew", "s > p", fromDataAnnotation: true));
+ Assert.False(derivedBuilder.CanHaveCheckConstraint("Splew", "s > p"));
+ Assert.True(derivedBuilder.CanHaveCheckConstraint("Splot", "s > p"));
+
+ var baseBuilder = modelBuilder.Entity(typeof(Splot), ConfigurationSource.DataAnnotation);
+ IReadOnlyEntityType baseEntityType = baseBuilder.Metadata;
+
+ Assert.True(baseBuilder.CanHaveCheckConstraint("Splew", "s < p"));
+ Assert.True(baseBuilder.CanHaveCheckConstraint("Splew", "s > p", fromDataAnnotation: true));
+ Assert.False(baseBuilder.CanHaveCheckConstraint("Splew", "s > p"));
+ Assert.True(baseBuilder.CanHaveCheckConstraint("Splot", "s > p"));
- var constraint = entityType.AddCheckConstraint("CK_Customer_AlternateId", "AlternateId > Id");
+ Assert.Null(baseBuilder.HasCheckConstraint("Splew", "s > p"));
+ Assert.Empty(baseEntityType.GetCheckConstraints());
+ Assert.Equal("s < p", derivedEntityType.GetCheckConstraints().Single().Sql);
- Assert.Same(constraint, entityType.RemoveCheckConstraint("CK_Customer_AlternateId"));
+ Assert.NotNull(baseBuilder.HasCheckConstraint("Splew", "s < p", fromDataAnnotation: true)
+ .HasName("CK_Splot", fromDataAnnotation: true));
+ Assert.Equal("Splew", baseEntityType.GetCheckConstraints().Single().ModelName);
+ Assert.Equal("s < p", baseEntityType.GetCheckConstraints().Single().Sql);
+ Assert.Equal("CK_Splot", baseEntityType.GetCheckConstraints().Single().Name);
+
+ derivedBuilder.HasBaseType((EntityType)baseEntityType, ConfigurationSource.Convention);
+
+ Assert.Null(baseBuilder.HasCheckConstraint("Splew", "s < p", fromDataAnnotation: true)
+ .HasName("CK_Splew"));
+ Assert.Equal("Splew", baseEntityType.GetCheckConstraints().Single().ModelName);
+ Assert.Equal("s < p", baseEntityType.GetCheckConstraints().Single().Sql);
+ Assert.Equal("CK_Splot", baseEntityType.GetCheckConstraints().Single().Name);
+ Assert.Empty(derivedEntityType.GetDeclaredCheckConstraints());
+ Assert.Same(baseEntityType.GetCheckConstraints().Single(), derivedEntityType.GetCheckConstraints().Single());
}
[ConditionalFact]
- public void RemoveCheckConstraint_returns_null_when_constraint_is_missing()
+ public void Base_check_constraint_overrides_derived_one_after_base_is_set()
{
- var entityTypeBuilder = CreateConventionModelBuilder().Entity();
- var entityType = entityTypeBuilder.Metadata;
+ var modelBuilder = CreateBuilder();
+
+ var derivedBuilder = modelBuilder.Entity(typeof(Splow), ConfigurationSource.Convention);
+ Assert.NotNull(derivedBuilder.HasBaseType((string)null, ConfigurationSource.DataAnnotation));
+ IReadOnlyEntityType derivedEntityType = derivedBuilder.Metadata;
+
+ Assert.NotNull(derivedBuilder.HasCheckConstraint("Splew", "s < p", fromDataAnnotation: true)
+ .HasName("CK_Splow", fromDataAnnotation: true));
+ Assert.Equal("Splew", derivedEntityType.GetCheckConstraints().Single().ModelName);
+ Assert.Equal("s < p", derivedEntityType.GetCheckConstraints().Single().Sql);
+ Assert.Equal("CK_Splow", derivedEntityType.GetCheckConstraints().Single().Name);
+
+ var baseBuilder = modelBuilder.Entity(typeof(Splot), ConfigurationSource.Convention);
+ IReadOnlyEntityType baseEntityType = baseBuilder.Metadata;
+ Assert.Null(derivedEntityType.BaseType);
+
+ Assert.NotNull(baseBuilder.HasCheckConstraint("Splew", "s < p", fromDataAnnotation: true)
+ .HasName("CK_Splot", fromDataAnnotation: true));
+ Assert.Equal("Splew", baseEntityType.GetCheckConstraints().Single().ModelName);
+ Assert.Equal("s < p", baseEntityType.GetCheckConstraints().Single().Sql);
+ Assert.Equal("CK_Splot", baseEntityType.GetCheckConstraints().Single().Name);
+
+ Assert.NotNull(derivedBuilder.HasBaseType((EntityType)baseEntityType, ConfigurationSource.DataAnnotation));
- Assert.Null(entityType.RemoveCheckConstraint("CK_Customer_AlternateId"));
+ Assert.Null(baseBuilder.HasCheckConstraint("Splew", "s < p", fromDataAnnotation: true)
+ .HasName("CK_Splew"));
+ Assert.Equal("Splew", baseEntityType.GetCheckConstraints().Single().ModelName);
+ Assert.Equal("s < p", baseEntityType.GetCheckConstraints().Single().Sql);
+ Assert.Equal("CK_Splot", baseEntityType.GetCheckConstraints().Single().Name);
+ Assert.Empty(derivedEntityType.GetDeclaredCheckConstraints());
+ Assert.Same(baseEntityType.GetCheckConstraints().Single(), derivedEntityType.GetCheckConstraints().Single());
}
[ConditionalFact]
@@ -1020,6 +1123,22 @@ public void Can_create_schema_named_sequence_with_specific_facets_using_nested_c
ValidateSchemaNamedSpecificSequence(sequence);
}
+ [ConditionalFact]
+ public void Can_access_comment()
+ {
+ var typeBuilder = CreateBuilder().Entity(typeof(Splot), ConfigurationSource.Convention);
+ var entityType = typeBuilder.Metadata;
+
+ Assert.NotNull(typeBuilder.HasComment("My Comment"));
+ Assert.Equal("My Comment", entityType.GetComment());
+
+ Assert.NotNull(typeBuilder.HasComment("My Comment 2", fromDataAnnotation: true));
+ Assert.Equal("My Comment 2", entityType.GetComment());
+
+ Assert.Null(typeBuilder.HasComment("My Comment"));
+ Assert.Equal("My Comment 2", entityType.GetComment());
+ }
+
[ConditionalFact]
public void Can_create_dbFunction()
{
@@ -1156,6 +1275,48 @@ public void Relational_property_methods_have_non_generic_overloads()
.HasDefaultValue("Neil");
}
+ [ConditionalFact]
+ public void Can_access_property()
+ {
+ var propertyBuilder = CreateBuilder()
+ .Entity(typeof(Splot), ConfigurationSource.Convention)
+ .Property(typeof(int), "Id", ConfigurationSource.Convention);
+
+ Assert.NotNull(propertyBuilder.IsFixedLength(true));
+ Assert.True(propertyBuilder.Metadata.IsFixedLength());
+ Assert.NotNull(propertyBuilder.HasColumnName("Splew"));
+ Assert.Equal("Splew", propertyBuilder.Metadata.GetColumnBaseName());
+ Assert.NotNull(propertyBuilder.HasColumnType("int"));
+ Assert.Equal("int", propertyBuilder.Metadata.GetColumnType());
+ Assert.NotNull(propertyBuilder.HasDefaultValue(1));
+ Assert.Equal(1, propertyBuilder.Metadata.GetDefaultValue());
+ Assert.NotNull(propertyBuilder.HasDefaultValueSql("2"));
+ Assert.Equal("2", propertyBuilder.Metadata.GetDefaultValueSql());
+ Assert.Equal(0, propertyBuilder.Metadata.GetDefaultValue());
+ Assert.NotNull(propertyBuilder.HasComputedColumnSql("3"));
+ Assert.Equal("3", propertyBuilder.Metadata.GetComputedColumnSql());
+ Assert.Null(propertyBuilder.Metadata.GetDefaultValueSql());
+
+ Assert.NotNull(propertyBuilder.IsFixedLength(false, fromDataAnnotation: true));
+ Assert.Null(propertyBuilder.IsFixedLength(true));
+ Assert.False(propertyBuilder.Metadata.IsFixedLength());
+ Assert.NotNull(propertyBuilder.HasColumnName("Splow", fromDataAnnotation: true));
+ Assert.Null(propertyBuilder.HasColumnName("Splod"));
+ Assert.Equal("Splow", propertyBuilder.Metadata.GetColumnBaseName());
+ Assert.NotNull(propertyBuilder.HasColumnType("varchar", fromDataAnnotation: true));
+ Assert.Null(propertyBuilder.HasColumnType("int"));
+ Assert.Equal("varchar", propertyBuilder.Metadata.GetColumnType());
+ Assert.NotNull(propertyBuilder.HasDefaultValue(0, fromDataAnnotation: true));
+ Assert.Null(propertyBuilder.HasDefaultValue(1));
+ Assert.Equal(0, propertyBuilder.Metadata.GetDefaultValue());
+ Assert.NotNull(propertyBuilder.HasDefaultValueSql("NULL", fromDataAnnotation: true));
+ Assert.Null(propertyBuilder.HasDefaultValueSql("2"));
+ Assert.Equal("NULL", propertyBuilder.Metadata.GetDefaultValueSql());
+ Assert.NotNull(propertyBuilder.HasComputedColumnSql("runthis()", fromDataAnnotation: true));
+ Assert.Null(propertyBuilder.HasComputedColumnSql("3"));
+ Assert.Equal("runthis()", propertyBuilder.Metadata.GetComputedColumnSql());
+ }
+
[ConditionalFact]
public void Relational_relationship_methods_dont_break_out_of_the_generics()
{
@@ -1205,6 +1366,24 @@ public void Relational_relationship_methods_have_non_generic_overloads()
.HasConstraintName("Simon");
}
+ [ConditionalFact]
+ public void Can_access_relationship()
+ {
+ var modelBuilder = CreateBuilder();
+ var entityTypeBuilder = modelBuilder.Entity(typeof(Splot), ConfigurationSource.Convention);
+ entityTypeBuilder.Property(typeof(int), "Id", ConfigurationSource.Convention);
+ var relationshipBuilder = entityTypeBuilder.HasRelationship("Splot", new[] { "Id" }, ConfigurationSource.Convention);
+
+ Assert.NotNull(relationshipBuilder.HasConstraintName("Splew"));
+ Assert.Equal("Splew", relationshipBuilder.Metadata.GetConstraintName());
+
+ Assert.NotNull(relationshipBuilder.HasConstraintName("Splow", fromDataAnnotation: true));
+ Assert.Equal("Splow", relationshipBuilder.Metadata.GetConstraintName());
+
+ Assert.Null(relationshipBuilder.HasConstraintName("Splod"));
+ Assert.Equal("Splow", relationshipBuilder.Metadata.GetConstraintName());
+ }
+
private void AssertIsGeneric(EntityTypeBuilder _)
{
}
@@ -1224,6 +1403,9 @@ private void AssertIsGeneric(ReferenceReferenceBuilder _)
protected virtual ModelBuilder CreateConventionModelBuilder()
=> RelationalTestHelpers.Instance.CreateConventionBuilder();
+ private InternalModelBuilder CreateBuilder()
+ => (InternalModelBuilder)CreateConventionModelBuilder().GetInfrastructure();
+
private static void ValidateSchemaNamedSpecificSequence(IReadOnlySequence sequence)
{
Assert.Equal("Snook", sequence.Name);
@@ -1273,5 +1455,20 @@ private class OrderDetails
public int OrderId { get; set; }
public Order Order { get; set; }
}
+
+ private class Splot
+ {
+ public static readonly PropertyInfo SplowedProperty = typeof(Splot).GetProperty("Splowed");
+
+ public int? Splowed { get; set; }
+ }
+
+ private class Splow : Splot
+ {
+ }
+
+ private class Splod : Splow
+ {
+ }
}
}
diff --git a/test/EFCore.Relational.Tests/Metadata/RelationalMetadataBuilderExtensionsTest.cs b/test/EFCore.Relational.Tests/Metadata/RelationalMetadataBuilderExtensionsTest.cs
deleted file mode 100644
index 5e22dfd01ea..00000000000
--- a/test/EFCore.Relational.Tests/Metadata/RelationalMetadataBuilderExtensionsTest.cs
+++ /dev/null
@@ -1,251 +0,0 @@
-// 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.Linq;
-using System.Reflection;
-using Microsoft.EntityFrameworkCore.Metadata.Builders;
-using Microsoft.EntityFrameworkCore.Metadata.Internal;
-using Xunit;
-
-// ReSharper disable UnusedMember.Local
-// ReSharper disable InconsistentNaming
-namespace Microsoft.EntityFrameworkCore.Metadata
-{
- public class RelationalMetadataBuilderExtensionsTest
- {
- private InternalModelBuilder CreateBuilder()
- => new(new Model());
-
- [ConditionalFact]
- public void Can_access_model()
- {
- var builder = CreateBuilder();
-
- ((IMutableModel)builder.Metadata).AddSequence("Mine").IncrementBy = 77;
-
- Assert.Equal(77, ((IMutableModel)builder.Metadata).FindSequence("Mine").IncrementBy);
- }
-
- [ConditionalFact]
- public void Can_set_table_name()
- {
- var typeBuilder = CreateBuilder().Entity(typeof(Splot), ConfigurationSource.Convention);
-
- Assert.NotNull(typeBuilder.ToTable("Splew"));
- Assert.Equal("Splew", typeBuilder.Metadata.GetTableName());
-
- Assert.NotNull(typeBuilder.ToTable("Splow", fromDataAnnotation: true));
- Assert.Equal("Splow", typeBuilder.Metadata.GetTableName());
-
- Assert.Null(typeBuilder.ToTable("Splod"));
- Assert.Equal("Splow", typeBuilder.Metadata.GetTableName());
- }
-
- [ConditionalFact]
- public void Can_set_table_name_and_schema()
- {
- var typeBuilder = CreateBuilder().Entity(typeof(Splot), ConfigurationSource.Convention);
-
- Assert.NotNull(typeBuilder.ToTable("Splew", "1"));
- Assert.Equal("Splew", typeBuilder.Metadata.GetTableName());
- Assert.Equal("1", typeBuilder.Metadata.GetSchema());
-
- Assert.NotNull(typeBuilder.ToTable("Splow", "2", fromDataAnnotation: true));
- Assert.Equal("Splow", typeBuilder.Metadata.GetTableName());
- Assert.Equal("2", typeBuilder.Metadata.GetSchema());
-
- Assert.Null(typeBuilder.ToTable("Splod", "3"));
- Assert.Equal("Splow", typeBuilder.Metadata.GetTableName());
- Assert.Equal("2", typeBuilder.Metadata.GetSchema());
- }
-
- [ConditionalFact]
- public void Can_override_existing_schema()
- {
- var typeBuilder = CreateBuilder().Entity(typeof(Splot), ConfigurationSource.Convention);
-
- typeBuilder.Metadata.SetSchema("Explicit");
-
- Assert.Null(typeBuilder.ToTable("Splod", "2", fromDataAnnotation: true));
- Assert.Equal("Splot", typeBuilder.Metadata.GetTableName());
- Assert.Equal("Explicit", typeBuilder.Metadata.GetSchema());
-
- Assert.NotNull(typeBuilder.ToTable("Splod", "Explicit", fromDataAnnotation: true));
- Assert.Equal("Splod", typeBuilder.Metadata.GetTableName());
- Assert.Equal("Explicit", typeBuilder.Metadata.GetSchema());
-
- Assert.NotNull(new EntityTypeBuilder(typeBuilder.Metadata).ToTable("Splew", "1"));
- Assert.Equal("Splew", typeBuilder.Metadata.GetTableName());
- Assert.Equal("1", typeBuilder.Metadata.GetSchema());
- }
-
- [ConditionalFact]
- public void Can_access_property()
- {
- var propertyBuilder = CreateBuilder()
- .Entity(typeof(Splot), ConfigurationSource.Convention)
- .Property(typeof(int), "Id", ConfigurationSource.Convention);
-
- Assert.NotNull(propertyBuilder.IsFixedLength(true));
- Assert.True(propertyBuilder.Metadata.IsFixedLength());
- Assert.NotNull(propertyBuilder.HasColumnName("Splew"));
- Assert.Equal("Splew", propertyBuilder.Metadata.GetColumnBaseName());
- Assert.NotNull(propertyBuilder.HasColumnType("int"));
- Assert.Equal("int", propertyBuilder.Metadata.GetColumnType());
- Assert.NotNull(propertyBuilder.HasDefaultValue(1));
- Assert.Equal(1, propertyBuilder.Metadata.GetDefaultValue());
- Assert.NotNull(propertyBuilder.HasDefaultValueSql("2"));
- Assert.Equal("2", propertyBuilder.Metadata.GetDefaultValueSql());
- Assert.Equal(1, propertyBuilder.Metadata.GetDefaultValue());
- Assert.NotNull(propertyBuilder.HasComputedColumnSql("3"));
- Assert.Equal("3", propertyBuilder.Metadata.GetComputedColumnSql());
- Assert.Equal("2", propertyBuilder.Metadata.GetDefaultValueSql());
-
- Assert.NotNull(propertyBuilder.IsFixedLength(false, fromDataAnnotation: true));
- Assert.Null(propertyBuilder.IsFixedLength(true));
- Assert.False(propertyBuilder.Metadata.IsFixedLength());
- Assert.NotNull(propertyBuilder.HasColumnName("Splow", fromDataAnnotation: true));
- Assert.Null(propertyBuilder.HasColumnName("Splod"));
- Assert.Equal("Splow", propertyBuilder.Metadata.GetColumnBaseName());
- Assert.NotNull(propertyBuilder.HasColumnType("varchar", fromDataAnnotation: true));
- Assert.Null(propertyBuilder.HasColumnType("int"));
- Assert.Equal("varchar", propertyBuilder.Metadata.GetColumnType());
- Assert.NotNull(propertyBuilder.HasDefaultValue(0, fromDataAnnotation: true));
- Assert.Null(propertyBuilder.HasDefaultValue(1));
- Assert.Equal(0, propertyBuilder.Metadata.GetDefaultValue());
- Assert.NotNull(propertyBuilder.HasDefaultValueSql("NULL", fromDataAnnotation: true));
- Assert.Null(propertyBuilder.HasDefaultValueSql("2"));
- Assert.Equal("NULL", propertyBuilder.Metadata.GetDefaultValueSql());
- Assert.NotNull(propertyBuilder.HasComputedColumnSql("runthis()", fromDataAnnotation: true));
- Assert.Null(propertyBuilder.HasComputedColumnSql("3"));
- Assert.Equal("runthis()", propertyBuilder.Metadata.GetComputedColumnSql());
- }
-
- [ConditionalFact]
- public void Can_access_key()
- {
- var modelBuilder = CreateBuilder();
- var entityTypeBuilder = modelBuilder.Entity(typeof(Splot), ConfigurationSource.Convention);
- var idProperty = entityTypeBuilder.Property(typeof(int), "Id", ConfigurationSource.Convention).Metadata;
- var keyBuilder = entityTypeBuilder.HasKey(new[] { idProperty.Name }, ConfigurationSource.Convention);
-
- Assert.NotNull(keyBuilder.HasName("Splew"));
- Assert.Equal("Splew", keyBuilder.Metadata.GetName());
-
- Assert.NotNull(keyBuilder.HasName("Splow", fromDataAnnotation: true));
- Assert.Equal("Splow", keyBuilder.Metadata.GetName());
-
- Assert.Null(keyBuilder.HasName("Splod"));
- Assert.Equal("Splow", keyBuilder.Metadata.GetName());
- }
-
- [ConditionalFact]
- public void Can_access_index()
- {
- var modelBuilder = CreateBuilder();
- var entityTypeBuilder = modelBuilder.Entity(typeof(Splot), ConfigurationSource.Convention);
- entityTypeBuilder.Property(typeof(int), "Id", ConfigurationSource.Convention);
- var indexBuilder = entityTypeBuilder.HasIndex(new[] { "Id" }, ConfigurationSource.Convention);
-
-#pragma warning disable CS0618 // Type or member is obsolete
- Assert.NotNull(indexBuilder.HasName("Splew"));
- Assert.Equal("Splew", indexBuilder.Metadata.GetName());
-
- Assert.NotNull(indexBuilder.HasName("Splow", fromDataAnnotation: true));
- Assert.Equal("Splow", indexBuilder.Metadata.GetName());
-
- Assert.Null(indexBuilder.HasName("Splod"));
- Assert.Equal("Splow", indexBuilder.Metadata.GetName());
-
- Assert.NotNull(indexBuilder.HasName(null, fromDataAnnotation: true));
- Assert.Equal("IX_Splot_Id", indexBuilder.Metadata.GetName());
-
- Assert.NotNull(indexBuilder.HasName("Splod"));
- Assert.Equal("Splod", indexBuilder.Metadata.GetName());
-#pragma warning restore CS0618 // Type or member is obsolete
-
- Assert.NotNull(indexBuilder.HasFilter("Splew"));
- Assert.Equal("Splew", indexBuilder.Metadata.GetFilter());
-
- Assert.NotNull(indexBuilder.HasFilter("Splow", fromDataAnnotation: true));
- Assert.Equal("Splow", indexBuilder.Metadata.GetFilter());
-
- Assert.Null(indexBuilder.HasFilter("Splod"));
- Assert.Equal("Splow", indexBuilder.Metadata.GetFilter());
-
- Assert.NotNull(indexBuilder.HasFilter(null, fromDataAnnotation: true));
- Assert.Null(indexBuilder.Metadata.GetFilter());
-
- Assert.Null(indexBuilder.HasFilter("Splod"));
- Assert.Null(indexBuilder.Metadata.GetFilter());
- }
-
- [ConditionalFact]
- public void Can_access_relationship()
- {
- var modelBuilder = CreateBuilder();
- var entityTypeBuilder = modelBuilder.Entity(typeof(Splot), ConfigurationSource.Convention);
- entityTypeBuilder.Property(typeof(int), "Id", ConfigurationSource.Convention);
- var relationshipBuilder = entityTypeBuilder.HasRelationship("Splot", new[] { "Id" }, ConfigurationSource.Convention);
-
- Assert.NotNull(relationshipBuilder.HasConstraintName("Splew"));
- Assert.Equal("Splew", relationshipBuilder.Metadata.GetConstraintName());
-
- Assert.NotNull(relationshipBuilder.HasConstraintName("Splow", fromDataAnnotation: true));
- Assert.Equal("Splow", relationshipBuilder.Metadata.GetConstraintName());
-
- Assert.Null(relationshipBuilder.HasConstraintName("Splod"));
- Assert.Equal("Splow", relationshipBuilder.Metadata.GetConstraintName());
- }
-
- [ConditionalFact]
- public void Can_access_check_constraint()
- {
- var typeBuilder = CreateBuilder().Entity(typeof(Splot), ConfigurationSource.Convention);
- IReadOnlyEntityType entityType = typeBuilder.Metadata;
-
- Assert.NotNull(typeBuilder.HasCheckConstraint("Splew", "s > p"));
- Assert.Equal("Splew", entityType.GetCheckConstraints().Single().Name);
- Assert.Equal("s > p", entityType.GetCheckConstraints().Single().Sql);
-
- Assert.NotNull(typeBuilder.HasCheckConstraint("Splew", "s < p", fromDataAnnotation: true));
- Assert.Equal("Splew", entityType.GetCheckConstraints().Single().Name);
- Assert.Equal("s < p", entityType.GetCheckConstraints().Single().Sql);
-
- Assert.Null(typeBuilder.HasCheckConstraint("Splew", "s > p"));
- Assert.Equal("Splew", entityType.GetCheckConstraints().Single().Name);
- Assert.Equal("s < p", entityType.GetCheckConstraints().Single().Sql);
- }
-
- [ConditionalFact]
- public void Can_access_comment()
- {
- var typeBuilder = CreateBuilder().Entity(typeof(Splot), ConfigurationSource.Convention);
- var entityType = typeBuilder.Metadata;
-
- Assert.NotNull(typeBuilder.HasComment("My Comment"));
- Assert.Equal("My Comment", entityType.GetComment());
-
- Assert.NotNull(typeBuilder.HasComment("My Comment 2", fromDataAnnotation: true));
- Assert.Equal("My Comment 2", entityType.GetComment());
-
- Assert.Null(typeBuilder.HasComment("My Comment"));
- Assert.Equal("My Comment 2", entityType.GetComment());
- }
-
- private class Splot
- {
- public static readonly PropertyInfo SplowedProperty = typeof(Splot).GetProperty("Splowed");
-
- public int? Splowed { get; set; }
- }
-
- private class Splow : Splot
- {
- }
-
- private class Splod : Splow
- {
- }
- }
-}
diff --git a/test/EFCore.Relational.Tests/Metadata/RelationalMetadataExtensionsTest.cs b/test/EFCore.Relational.Tests/Metadata/RelationalMetadataExtensionsTest.cs
index 09f91467235..e66d2aac2ba 100644
--- a/test/EFCore.Relational.Tests/Metadata/RelationalMetadataExtensionsTest.cs
+++ b/test/EFCore.Relational.Tests/Metadata/RelationalMetadataExtensionsTest.cs
@@ -5,6 +5,7 @@
using System.Linq;
using System.Reflection;
using Microsoft.EntityFrameworkCore.Diagnostics;
+using Microsoft.EntityFrameworkCore.TestUtilities;
using Xunit;
namespace Microsoft.EntityFrameworkCore.Metadata
@@ -589,6 +590,43 @@ public void Can_get_multiple_sequences()
Assert.Contains(sequences, s => s.Name == "Golomb");
}
+ [ConditionalFact]
+ public void AddCheckConstraint_with_duplicate_names_throws_exception()
+ {
+ var entityTypeBuilder = CreateConventionModelBuilder().Entity();
+ var entityType = entityTypeBuilder.Metadata;
+
+ entityType.AddCheckConstraint("CK_Customer_AlternateId", "AlternateId > Id");
+
+ Assert.Equal(
+ RelationalStrings.DuplicateCheckConstraint("CK_Customer_AlternateId", entityType.DisplayName(), entityType.DisplayName()),
+ Assert.Throws(
+ () => entityType.AddCheckConstraint("CK_Customer_AlternateId", "AlternateId < Id")).Message);
+ }
+
+ [ConditionalFact]
+ public void RemoveCheckConstraint_returns_constraint_when_constraint_exists()
+ {
+ var entityTypeBuilder = CreateConventionModelBuilder().Entity();
+ var entityType = entityTypeBuilder.Metadata;
+
+ var constraint = entityType.AddCheckConstraint("CK_Customer_AlternateId", "AlternateId > Id");
+
+ Assert.Same(constraint, entityType.RemoveCheckConstraint("CK_Customer_AlternateId"));
+ }
+
+ [ConditionalFact]
+ public void RemoveCheckConstraint_returns_null_when_constraint_is_missing()
+ {
+ var entityTypeBuilder = CreateConventionModelBuilder().Entity();
+ var entityType = entityTypeBuilder.Metadata;
+
+ Assert.Null(entityType.RemoveCheckConstraint("CK_Customer_AlternateId"));
+ }
+
+ protected virtual ModelBuilder CreateConventionModelBuilder()
+ => RelationalTestHelpers.Instance.CreateConventionBuilder();
+
private enum MyEnum : byte
{
Son,
diff --git a/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs b/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs
index 604edd98d10..a4b2f5c8907 100644
--- a/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs
+++ b/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs
@@ -377,7 +377,7 @@ public void Create_table()
Assert.NotNull(createTableOperation.PrimaryKey);
Assert.Single(createTableOperation.UniqueConstraints);
var checkConstraint = createTableOperation.CheckConstraints.Single();
- Assert.Equal("SomeCheckConstraint", checkConstraint.Name);
+ Assert.Equal("CK_Node_SomeCheckConstraint", checkConstraint.Name);
Assert.Equal("[Id] > 10", checkConstraint.Sql);
Assert.Single(createTableOperation.ForeignKeys);
@@ -2857,7 +2857,7 @@ public void Drop_check_constraint()
x.ToTable("Penguin", "dbo");
x.Property("Id");
x.Property("AlternateId");
- x.HasCheckConstraint("CK_Flamingo_AlternateId", "AlternateId > Id");
+ x.HasCheckConstraint("CK_Penguin_AlternateId", "AlternateId > Id");
}),
target => target.Entity(
"Penguin",
@@ -2874,7 +2874,7 @@ public void Drop_check_constraint()
var operation = Assert.IsType(operations[0]);
Assert.Equal("dbo", operation.Schema);
Assert.Equal("Penguin", operation.Table);
- Assert.Equal("CK_Flamingo_AlternateId", operation.Name);
+ Assert.Equal("CK_Penguin_AlternateId", operation.Name);
});
}
@@ -2889,7 +2889,7 @@ public void Rename_check_constraint()
x.ToTable("Pelican", "dbo");
x.Property("Id");
x.Property("AlternateId");
- x.HasCheckConstraint("CK_Flamingo_AlternateId", "AlternateId > Id");
+ x.HasCheckConstraint("CK_Pelican_AlternateId", "AlternateId > Id");
}),
target => target.Entity(
"Pelican",
@@ -2898,7 +2898,7 @@ public void Rename_check_constraint()
x.ToTable("Pelican", "dbo");
x.Property("Id");
x.Property("AlternateId");
- x.HasCheckConstraint("CK_Flamingo", "AlternateId > Id");
+ x.HasCheckConstraint("CK_Pelican_AlternateId", "AlternateId > Id", c => c.HasName("CK_Flamingo"));
}),
operations =>
{
@@ -2907,7 +2907,7 @@ public void Rename_check_constraint()
var dropOperation = Assert.IsType(operations[0]);
Assert.Equal("dbo", dropOperation.Schema);
Assert.Equal("Pelican", dropOperation.Table);
- Assert.Equal("CK_Flamingo_AlternateId", dropOperation.Name);
+ Assert.Equal("CK_Pelican_AlternateId", dropOperation.Name);
var createOperation = Assert.IsType(operations[1]);
Assert.Equal("dbo", createOperation.Schema);
@@ -2928,7 +2928,7 @@ public void Alter_check_constraint_expression()
x.ToTable("Rook", "dbo");
x.Property("Id");
x.Property("AlternateId");
- x.HasCheckConstraint("CK_Flamingo_AlternateId", "AlternateId > Id");
+ x.HasCheckConstraint("CK_Rook_AlternateId", "AlternateId > Id");
}),
target => target.Entity(
"Rook",
@@ -2937,7 +2937,7 @@ public void Alter_check_constraint_expression()
x.ToTable("Rook", "dbo");
x.Property("Id");
x.Property("AlternateId");
- x.HasCheckConstraint("CK_Flamingo_AlternateId", "AlternateId < Id");
+ x.HasCheckConstraint("CK_Rook_AlternateId", "AlternateId < Id");
}),
operations =>
{
@@ -2946,12 +2946,12 @@ public void Alter_check_constraint_expression()
var dropOperation = Assert.IsType(operations[0]);
Assert.Equal("dbo", dropOperation.Schema);
Assert.Equal("Rook", dropOperation.Table);
- Assert.Equal("CK_Flamingo_AlternateId", dropOperation.Name);
+ Assert.Equal("CK_Rook_AlternateId", dropOperation.Name);
var createOperation = Assert.IsType(operations[1]);
Assert.Equal("dbo", createOperation.Schema);
Assert.Equal("Rook", createOperation.Table);
- Assert.Equal("CK_Flamingo_AlternateId", createOperation.Name);
+ Assert.Equal("CK_Rook_AlternateId", createOperation.Name);
Assert.Equal("AlternateId < Id", createOperation.Sql);
});
}
diff --git a/test/EFCore.Relational.Tests/ModelBuilding/RelationalModelBuilderGenericTestBase.cs b/test/EFCore.Relational.Tests/ModelBuilding/RelationalModelBuilderGenericTestBase.cs
index e497018cfa6..4df8ecafb38 100644
--- a/test/EFCore.Relational.Tests/ModelBuilding/RelationalModelBuilderGenericTestBase.cs
+++ b/test/EFCore.Relational.Tests/ModelBuilding/RelationalModelBuilderGenericTestBase.cs
@@ -4,6 +4,8 @@
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
+#nullable enable
+
// ReSharper disable InconsistentNaming
namespace Microsoft.EntityFrameworkCore.ModelBuilding
{
@@ -52,5 +54,28 @@ protected virtual TestTableBuilder Wrap(TableBuilder tableBuilder)
public override TestTableBuilder ExcludeFromMigrations(bool excluded = true)
=> Wrap(TableBuilder.ExcludeFromMigrations(excluded));
}
+
+ public abstract class TestCheckConstraintBuilder
+ {
+ public abstract TestCheckConstraintBuilder HasName(string name);
+ }
+
+ public class NonGenericTestCheckConstraintBuilder : TestCheckConstraintBuilder, IInfrastructure
+ {
+ public NonGenericTestCheckConstraintBuilder(CheckConstraintBuilder checkConstraintBuilder)
+ {
+ CheckConstraintBuilder = checkConstraintBuilder;
+ }
+
+ protected CheckConstraintBuilder CheckConstraintBuilder { get; }
+
+ public CheckConstraintBuilder Instance => CheckConstraintBuilder;
+
+ protected virtual TestCheckConstraintBuilder Wrap(CheckConstraintBuilder checkConstraintBuilder)
+ => new NonGenericTestCheckConstraintBuilder(checkConstraintBuilder);
+
+ public override TestCheckConstraintBuilder HasName(string name)
+ => Wrap(CheckConstraintBuilder.HasName(name));
+ }
}
}
diff --git a/test/EFCore.Relational.Tests/ModelBuilding/RelationalTestModelBuilderExtensions.cs b/test/EFCore.Relational.Tests/ModelBuilding/RelationalTestModelBuilderExtensions.cs
index fcfb50bd886..cc4e9689a19 100644
--- a/test/EFCore.Relational.Tests/ModelBuilding/RelationalTestModelBuilderExtensions.cs
+++ b/test/EFCore.Relational.Tests/ModelBuilding/RelationalTestModelBuilderExtensions.cs
@@ -5,6 +5,8 @@
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
+#nullable enable
+
namespace Microsoft.EntityFrameworkCore.ModelBuilding
{
public static class RelationalTestModelBuilderExtensions
@@ -271,6 +273,90 @@ public static ModelBuilderTest.TestOwnedNavigationBuilder HasCheckConstraint(
+ this ModelBuilderTest.TestEntityTypeBuilder builder,
+ string name,
+ string? sql)
+ where TEntity : class
+ {
+ switch (builder)
+ {
+ case IInfrastructure> genericBuilder:
+ genericBuilder.Instance.HasCheckConstraint(name, sql);
+ break;
+ case IInfrastructure nongenericBuilder:
+ nongenericBuilder.Instance.HasCheckConstraint(name, sql);
+ break;
+ }
+
+ return builder;
+ }
+
+ public static ModelBuilderTest.TestEntityTypeBuilder HasCheckConstraint(
+ this ModelBuilderTest.TestEntityTypeBuilder builder,
+ string name,
+ string sql,
+ Action buildAction)
+ where TEntity : class
+ {
+ switch (builder)
+ {
+ case IInfrastructure> genericBuilder:
+ genericBuilder.Instance.HasCheckConstraint(name, sql,
+ b => buildAction(new RelationalModelBuilderTest.NonGenericTestCheckConstraintBuilder(b)));
+ break;
+ case IInfrastructure nongenericBuilder:
+ nongenericBuilder.Instance.HasCheckConstraint(name, sql,
+ b => buildAction(new RelationalModelBuilderTest.NonGenericTestCheckConstraintBuilder(b)));
+ break;
+ }
+
+ return builder;
+ }
+
+ public static ModelBuilderTest.TestOwnedNavigationBuilder HasCheckConstraint(
+ this ModelBuilderTest.TestOwnedNavigationBuilder builder,
+ string name,
+ string? sql)
+ where TEntity : class
+ where TRelatedEntity : class
+ {
+ switch (builder)
+ {
+ case IInfrastructure> genericBuilder:
+ genericBuilder.Instance.HasCheckConstraint(name, sql);
+ break;
+ case IInfrastructure nongenericBuilder:
+ nongenericBuilder.Instance.HasCheckConstraint(name, sql);
+ break;
+ }
+
+ return builder;
+ }
+
+ public static ModelBuilderTest.TestOwnedNavigationBuilder HasCheckConstraint(
+ this ModelBuilderTest.TestOwnedNavigationBuilder builder,
+ string name,
+ string sql,
+ Action buildAction)
+ where TEntity : class
+ where TRelatedEntity : class
+ {
+ switch (builder)
+ {
+ case IInfrastructure> genericBuilder:
+ genericBuilder.Instance.HasCheckConstraint(name, sql,
+ b => buildAction(new RelationalModelBuilderTest.NonGenericTestCheckConstraintBuilder(b)));
+ break;
+ case IInfrastructure nongenericBuilder:
+ nongenericBuilder.Instance.HasCheckConstraint(name, sql,
+ b => buildAction(new RelationalModelBuilderTest.NonGenericTestCheckConstraintBuilder(b)));
+ break;
+ }
+
+ return builder;
+ }
+
public static ModelBuilderTest.TestOwnershipBuilder HasConstraintName(
this ModelBuilderTest.TestOwnershipBuilder builder,
string name)
diff --git a/test/EFCore.SqlServer.FunctionalTests/Migrations/MigrationsSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Migrations/MigrationsSqlServerTest.cs
index 26495b51fb0..d0cc5754aa7 100644
--- a/test/EFCore.SqlServer.FunctionalTests/Migrations/MigrationsSqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/Migrations/MigrationsSqlServerTest.cs
@@ -57,7 +57,7 @@ public override async Task Create_table_all_settings()
[SSN] nvarchar(11) COLLATE German_PhoneBook_CI_AS NOT NULL,
CONSTRAINT [PK_People] PRIMARY KEY ([CustomId]),
CONSTRAINT [AK_People_SSN] UNIQUE ([SSN]),
- CONSTRAINT [CK_EmployerId] CHECK ([EmployerId] > 0),
+ CONSTRAINT [CK_People_EmployerId] CHECK ([EmployerId] > 0),
CONSTRAINT [FK_People_Employers_EmployerId] FOREIGN KEY ([EmployerId]) REFERENCES [Employers] ([Id]) ON DELETE NO ACTION
);
DECLARE @description AS sql_variant;
@@ -455,7 +455,7 @@ public override async Task Add_column_with_check_constraint()
AssertSql(
@"ALTER TABLE [People] ADD [DriverLicense] int NOT NULL DEFAULT 0;",
//
- @"ALTER TABLE [People] ADD CONSTRAINT [CK_Foo] CHECK ([DriverLicense] > 0);");
+ @"ALTER TABLE [People] ADD CONSTRAINT [CK_People_Foo] CHECK ([DriverLicense] > 0);");
}
[ConditionalFact]
@@ -1679,7 +1679,7 @@ public override async Task Add_check_constraint_with_name()
await base.Add_check_constraint_with_name();
AssertSql(
- @"ALTER TABLE [People] ADD CONSTRAINT [CK_Foo] CHECK ([DriverLicense] > 0);");
+ @"ALTER TABLE [People] ADD CONSTRAINT [CK_People_Foo] CHECK ([DriverLicense] > 0);");
}
public override async Task Alter_check_constraint()
@@ -1687,9 +1687,9 @@ public override async Task Alter_check_constraint()
await base.Alter_check_constraint();
AssertSql(
- @"ALTER TABLE [People] DROP CONSTRAINT [CK_Foo];",
+ @"ALTER TABLE [People] DROP CONSTRAINT [CK_People_Foo];",
//
- @"ALTER TABLE [People] ADD CONSTRAINT [CK_Foo] CHECK ([DriverLicense] > 1);");
+ @"ALTER TABLE [People] ADD CONSTRAINT [CK_People_Foo] CHECK ([DriverLicense] > 1);");
}
public override async Task Drop_check_constraint()
@@ -1697,7 +1697,7 @@ public override async Task Drop_check_constraint()
await base.Drop_check_constraint();
AssertSql(
- @"ALTER TABLE [People] DROP CONSTRAINT [CK_Foo];");
+ @"ALTER TABLE [People] DROP CONSTRAINT [CK_People_Foo];");
}
public override async Task Create_sequence()
diff --git a/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderGenericTest.cs b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderGenericTest.cs
index 328a3eae132..619dac13446 100644
--- a/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderGenericTest.cs
+++ b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderGenericTest.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
@@ -326,6 +327,69 @@ public virtual void TPT_index_can_use_inherited_properties()
Assert.All(bunType.GetIndexes(), i => Assert.Null(i.GetFilter()));
}
+ [ConditionalFact]
+ public void Can_add_check_constraints()
+ {
+ var modelBuilder = CreateModelBuilder();
+ modelBuilder.Entity()
+ .HasBaseType(null)
+ .HasCheckConstraint("LargeId", "Id > 1000", c => c.HasName("CK_LargeId"));
+ modelBuilder.Entity()
+ .HasCheckConstraint("PositiveId", "Id > 0")
+ .HasCheckConstraint("LargeId", "Id > 1000");
+ modelBuilder.Entity()
+ .HasBaseType();
+ modelBuilder.Entity();
+
+ var model = modelBuilder.FinalizeModel();
+
+ var @base = model.FindEntityType(typeof(ChildBase));
+ Assert.Equal(2, @base.GetCheckConstraints().Count());
+
+ var firstCheckConstraint = @base.FindCheckConstraint("PositiveId");
+ Assert.Equal("PositiveId", firstCheckConstraint.ModelName);
+ Assert.Equal("Id > 0", firstCheckConstraint.Sql);
+ Assert.Equal("CK_ChildBase_PositiveId", firstCheckConstraint.Name);
+
+ var secondCheckConstraint = @base.FindCheckConstraint("LargeId");
+ Assert.Equal("LargeId", secondCheckConstraint.ModelName);
+ Assert.Equal("Id > 1000", secondCheckConstraint.Sql);
+ Assert.Equal("CK_LargeId", secondCheckConstraint.Name);
+
+ var child = model.FindEntityType(typeof(Child));
+ Assert.Equal(@base.GetCheckConstraints(), child.GetCheckConstraints());
+ Assert.Empty(child.GetDeclaredCheckConstraints());
+ }
+
+ [ConditionalFact]
+ public void Adding_conflicting_check_constraint_to_derived_type_throws()
+ {
+ var modelBuilder = CreateModelBuilder();
+ modelBuilder.Entity()
+ .HasCheckConstraint("LargeId", "Id > 100", c => c.HasName("CK_LargeId"));
+
+ Assert.Equal(
+ RelationalStrings.DuplicateCheckConstraint("LargeId", nameof(Child), nameof(ChildBase)),
+ Assert.Throws(
+ () => modelBuilder.Entity().HasCheckConstraint("LargeId", "Id > 1000")).Message);
+ }
+
+ [ConditionalFact]
+ public void Adding_conflicting_check_constraint_to_derived_type_before_base_throws()
+ {
+ var modelBuilder = CreateModelBuilder();
+ modelBuilder.Entity()
+ .HasBaseType(null)
+ .HasCheckConstraint("LargeId", "Id > 1000");
+ modelBuilder.Entity()
+ .HasCheckConstraint("LargeId", "Id > 100", c => c.HasName("CK_LargeId"));
+
+ Assert.Equal(
+ RelationalStrings.DuplicateCheckConstraint("LargeId", nameof(Child), nameof(ChildBase)),
+ Assert.Throws(
+ () => modelBuilder.Entity().HasBaseType()).Message);
+ }
+
public class Parent
{
public int Id { get; set; }
@@ -743,16 +807,16 @@ public virtual void Owned_type_collections_can_be_mapped_to_different_tables()
public override void Can_configure_owned_type()
{
var modelBuilder = CreateModelBuilder();
- var model = modelBuilder.Model;
- var entityBuilder = modelBuilder.Entity().OwnsOne(c => c.Details)
- .ToTable("CustomerDetails");
- entityBuilder.Property(d => d.CustomerId);
- entityBuilder.HasIndex(d => d.CustomerId);
- entityBuilder.WithOwner(d => d.Customer)
+ var ownedBuilder = modelBuilder.Entity().OwnsOne(c => c.Details)
+ .ToTable("CustomerDetails")
+ .HasCheckConstraint("CK_CustomerDetails_T", "AlternateKey <> 0", c => c.HasName("CK_Guid"));
+ ownedBuilder.Property(d => d.CustomerId);
+ ownedBuilder.HasIndex(d => d.CustomerId);
+ ownedBuilder.WithOwner(d => d.Customer)
.HasPrincipalKey(c => c.AlternateKey);
- modelBuilder.FinalizeModel();
+ var model = modelBuilder.FinalizeModel();
var owner = model.FindEntityType(typeof(Customer));
Assert.Equal(typeof(Customer).FullName, owner.Name);
@@ -762,7 +826,12 @@ public override void Can_configure_owned_type()
Assert.Equal("CustomerAlternateKey", ownership.Properties.Single().Name);
Assert.Equal(nameof(Customer.AlternateKey), ownership.PrincipalKey.Properties.Single().Name);
var owned = ownership.DeclaringEntityType;
- Assert.Same(entityBuilder.OwnedEntityType, owned);
+ Assert.Same(ownedBuilder.OwnedEntityType, owned);
+ Assert.Equal("CustomerDetails", owned.GetTableName());
+ var checkConstraint = owned.GetCheckConstraints().Single();
+ Assert.Equal("CK_CustomerDetails_T", checkConstraint.ModelName);
+ Assert.Equal("AlternateKey <> 0", checkConstraint.Sql);
+ Assert.Equal("CK_Guid", checkConstraint.Name);
Assert.Single(owned.GetForeignKeys());
Assert.Equal(nameof(CustomerDetails.CustomerId), owned.GetIndexes().Single().Properties.Single().Name);
Assert.Equal(
diff --git a/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerTestModelBuilderExtensions.cs b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerTestModelBuilderExtensions.cs
index d12e8da9843..17fa9c9e9f6 100644
--- a/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerTestModelBuilderExtensions.cs
+++ b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerTestModelBuilderExtensions.cs
@@ -5,6 +5,8 @@
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
+#nullable enable
+
namespace Microsoft.EntityFrameworkCore.ModelBuilding
{
public static class SqlServerTestModelBuilderExtensions
diff --git a/test/EFCore.Sqlite.FunctionalTests/Migrations/MigrationsSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Migrations/MigrationsSqliteTest.cs
index 0cfb966ed40..e142a3877a1 100644
--- a/test/EFCore.Sqlite.FunctionalTests/Migrations/MigrationsSqliteTest.cs
+++ b/test/EFCore.Sqlite.FunctionalTests/Migrations/MigrationsSqliteTest.cs
@@ -53,7 +53,7 @@ public override async Task Create_table_all_settings()
""SSN"" TEXT COLLATE NOCASE NOT NULL,
CONSTRAINT ""AK_People_SSN"" UNIQUE (""SSN""),
- CONSTRAINT ""CK_EmployerId"" CHECK (""EmployerId"" > 0),
+ CONSTRAINT ""CK_People_EmployerId"" CHECK (""EmployerId"" > 0),
CONSTRAINT ""FK_People_Employers_EmployerId"" FOREIGN KEY (""EmployerId"") REFERENCES ""Employers"" (""Id"") ON DELETE RESTRICT
);");
}
@@ -321,7 +321,7 @@ public override async Task Add_column_with_check_constraint()
@"CREATE TABLE ""ef_temp_People"" (
""DriverLicense"" INTEGER NOT NULL,
""Id"" INTEGER NOT NULL,
- CONSTRAINT ""CK_Foo"" CHECK (""DriverLicense"" > 0)
+ CONSTRAINT ""CK_People_Foo"" CHECK (""DriverLicense"" > 0)
);",
@"INSERT INTO ""ef_temp_People"" (""DriverLicense"", ""Id"")
SELECT ""DriverLicense"", ""Id""
@@ -797,7 +797,7 @@ public override async Task Add_check_constraint_with_name()
@"CREATE TABLE ""ef_temp_People"" (
""DriverLicense"" INTEGER NOT NULL,
""Id"" INTEGER NOT NULL,
- CONSTRAINT ""CK_Foo"" CHECK (""DriverLicense"" > 0)
+ CONSTRAINT ""CK_People_Foo"" CHECK (""DriverLicense"" > 0)
);",
@"INSERT INTO ""ef_temp_People"" (""DriverLicense"", ""Id"")
SELECT ""DriverLicense"", ""Id""
@@ -816,7 +816,7 @@ public override async Task Alter_check_constraint()
@"CREATE TABLE ""ef_temp_People"" (
""DriverLicense"" INTEGER NOT NULL,
""Id"" INTEGER NOT NULL,
- CONSTRAINT ""CK_Foo"" CHECK (""DriverLicense"" > 1)
+ CONSTRAINT ""CK_People_Foo"" CHECK (""DriverLicense"" > 1)
);",
@"INSERT INTO ""ef_temp_People"" (""DriverLicense"", ""Id"")
SELECT ""DriverLicense"", ""Id""