Skip to content

Commit

Permalink
Uniquify and validate check constraint store name.
Browse files Browse the repository at this point in the history
Allow to specify check constraint store name independently of the model name.
Ensure check constraint model name is unique across the hierarchy.
Remove check constraints from the runtime model.

Fixes #18958
  • Loading branch information
AndriySvyryd authored Jun 14, 2021
1 parent 24e5c6c commit b44b5cc
Show file tree
Hide file tree
Showing 46 changed files with 1,694 additions and 695 deletions.
15 changes: 12 additions & 3 deletions src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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(");");
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -326,22 +326,7 @@ public override void Generate(IEntityType entityType, CSharpRuntimeAnnotationCod
}
else
{
if (annotations.TryGetAndRemove(RelationalAnnotationNames.CheckConstraints,
out SortedDictionary<string, ICheckConstraint> constraints))
{
parameters.Namespaces.Add(typeof(SortedDictionary<string, ICheckConstraint>).Namespace!);
var constraintsVariable = Dependencies.CSharpHelper.Identifier("constraints", parameters.ScopeVariables, capitalize: false);
parameters.MainBuilder
.Append("var ").Append(constraintsVariable).AppendLine(" = new SortedDictionary<string, ICheckConstraint>();");

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);

Expand All @@ -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();
}

/// <summary>
/// Generates code to create the given annotations.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
/// <summary>
/// Configures a database check constraint when targeting a relational database.
/// </summary>
/// <param name="entityTypeBuilder"> The entity type builder. </param>
/// <param name="name"> The name of the check constraint. </param>
/// <param name="sql"> The logical constraint sql used in the check constraint. </param>
/// <param name="buildAction"> An action that performs configuration of the check constraint. </param>
/// <returns> A builder to further configure the entity type. </returns>
public static EntityTypeBuilder HasCheckConstraint(
this EntityTypeBuilder entityTypeBuilder,
string name,
string sql,
Action<CheckConstraintBuilder> 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;
}
Expand All @@ -1012,6 +1023,23 @@ public static EntityTypeBuilder<TEntity> HasCheckConstraint<TEntity>(
where TEntity : class
=> (EntityTypeBuilder<TEntity>)HasCheckConstraint((EntityTypeBuilder)entityTypeBuilder, name, sql);

/// <summary>
/// Configures a database check constraint when targeting a relational database.
/// </summary>
/// <typeparam name="TEntity"> The entity type being configured. </typeparam>
/// <param name="entityTypeBuilder"> The entity type builder. </param>
/// <param name="name"> The name of the check constraint. </param>
/// <param name="sql"> The logical constraint sql used in the check constraint. </param>
/// <param name="buildAction"> An action that performs configuration of the check constraint. </param>
/// <returns> A builder to further configure the entity type. </returns>
public static EntityTypeBuilder<TEntity> HasCheckConstraint<TEntity>(
this EntityTypeBuilder<TEntity> entityTypeBuilder,
string name,
string sql,
Action<CheckConstraintBuilder> buildAction)
where TEntity : class
=> (EntityTypeBuilder<TEntity>)HasCheckConstraint((EntityTypeBuilder)entityTypeBuilder, name, sql, buildAction);

/// <summary>
/// Configures a database check constraint when targeting a relational database.
/// </summary>
Expand All @@ -1023,44 +1051,17 @@ public static EntityTypeBuilder<TEntity> HasCheckConstraint<TEntity>(
/// The same builder instance if the check constraint was configured,
/// <see langword="null" /> otherwise.
/// </returns>
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;

/// <summary>
/// Returns a value indicating whether the check constraint can be configured.
Expand All @@ -1070,23 +1071,32 @@ public static EntityTypeBuilder<TEntity> HasCheckConstraint<TEntity>(
/// <param name="sql"> The logical constraint sql used in the check constraint. </param>
/// <param name="fromDataAnnotation"> Indicates whether the configuration was specified using a data annotation. </param>
/// <returns> <see langword="true" /> if the configuration can be applied. </returns>
[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());
}
/// <summary>
/// Returns a value indicating whether the check constraint can be configured.
/// </summary>
/// <param name="entityTypeBuilder"> The builder for the entity type being configured. </param>
/// <param name="name"> The name of the check constraint. </param>
/// <param name="sql"> The logical constraint sql used in the check constraint. </param>
/// <param name="fromDataAnnotation"> Indicates whether the configuration was specified using a data annotation. </param>
/// <returns> <see langword="true" /> if the configuration can be applied. </returns>
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);

/// <summary>
/// Configures a comment to be applied to the table
Expand Down
Loading

0 comments on commit b44b5cc

Please sign in to comment.