Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Store check constraint annotations on entity type instead of model #15688

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 53 additions & 1 deletion src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,8 @@ protected virtual void GenerateEntityType(

GenerateEntityTypeAnnotations(builderName, entityType, stringBuilder);

GenerateCheckConstraints(builderName, entityType, stringBuilder);

if (ownerNavigation != null)
{
GenerateRelationships(builderName, entityType, stringBuilder);
Expand Down Expand Up @@ -767,7 +769,8 @@ protected virtual void GenerateEntityTypeAnnotations(
CoreAnnotationNames.ChangeTrackingStrategy,
CoreAnnotationNames.ConstructorBinding,
CoreAnnotationNames.DefiningQuery,
CoreAnnotationNames.QueryFilter);
CoreAnnotationNames.QueryFilter,
RelationalAnnotationNames.CheckConstraints);

if (annotations.Count > 0)
{
Expand All @@ -790,6 +793,55 @@ protected virtual void GenerateEntityTypeAnnotations(
}
}

/// <summary>
/// Generates code for <see cref="ICheckConstraint" /> objects.
/// </summary>
/// <param name="builderName"> The name of the builder variable. </param>
/// <param name="entityType"> The entity type. </param>
/// <param name="stringBuilder"> The builder code is added to. </param>
protected virtual void GenerateCheckConstraints(
[NotNull] string builderName,
[NotNull] IEntityType entityType,
[NotNull] IndentedStringBuilder stringBuilder)
{
Check.NotNull(builderName, nameof(builderName));
Check.NotNull(entityType, nameof(entityType));
Check.NotNull(stringBuilder, nameof(stringBuilder));

var constraintsForEntity = entityType.GetCheckConstraints();

foreach (var checkConstraint in constraintsForEntity)
{
stringBuilder.AppendLine();

GenerateCheckConstraint(builderName, checkConstraint, stringBuilder);
}
}

/// <summary>
/// Generates code for an <see cref="ICheckConstraint" />.
/// </summary>
/// <param name="builderName"> The name of the builder variable. </param>
/// <param name="checkConstraint"> The check constraint. </param>
/// <param name="stringBuilder"> The builder code is added to. </param>
protected virtual void GenerateCheckConstraint(
[NotNull] string builderName,
[NotNull] ICheckConstraint checkConstraint,
[NotNull] IndentedStringBuilder stringBuilder)
{
Check.NotNull(builderName, nameof(builderName));
Check.NotNull(checkConstraint, nameof(checkConstraint));
Check.NotNull(stringBuilder, nameof(stringBuilder));

stringBuilder
.Append(builderName)
.Append(".HasCheckConstraint(")
.Append(Code.Literal(checkConstraint.Name))
.Append(", ")
.Append(Code.Literal(checkConstraint.Sql))
.AppendLine(");");
}

/// <summary>
/// Generates code for <see cref="IForeignKey" /> objects.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -353,24 +353,20 @@ public static EntityTypeBuilder HasCheckConstraint(

var entityType = entityTypeBuilder.Metadata;

var tableName = entityType.GetTableName();
var schema = entityType.GetSchema();

var constraint = entityType.Model.FindCheckConstraint(name, tableName, schema);
var constraint = entityType.FindCheckConstraint(name);
if (constraint != null)
{
if (constraint.Sql == sql)
{
((CheckConstraint)constraint).UpdateConfigurationSource(ConfigurationSource.Explicit);
return entityTypeBuilder;
}

entityType.Model.RemoveCheckConstraint(name, tableName, schema);
entityType.RemoveCheckConstraint(name);
}

if (sql != null)
{
entityType.Model.AddCheckConstraint(sql, name, tableName, schema);
entityType.AddCheckConstraint(name, sql);
}

return entityTypeBuilder;
Expand Down Expand Up @@ -414,15 +410,12 @@ public static IConventionEntityTypeBuilder HasCheckConstraint(

var entityType = entityTypeBuilder.Metadata;

var tableName = entityType.GetTableName();
var schema = entityType.GetSchema();

var constraint = entityType.Model.FindCheckConstraint(name, tableName, schema);
var constraint = entityType.FindCheckConstraint(name);
if (constraint != null)
{
if (constraint.Sql == sql)
{
((CheckConstraint)constraint).UpdateConfigurationSource(
((CheckConstraint)constraint).UpdateConfigurationSource(
fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
return entityTypeBuilder;
}
Expand All @@ -433,19 +426,19 @@ public static IConventionEntityTypeBuilder HasCheckConstraint(
return null;
}

entityType.Model.RemoveCheckConstraint(name, tableName, schema);
entityType.RemoveCheckConstraint(name);
}

if (sql != null)
{
entityType.Model.AddCheckConstraint(sql, name, tableName, schema, fromDataAnnotation);
entityType.AddCheckConstraint(name, sql, fromDataAnnotation);
}

return entityTypeBuilder;
}

/// <summary>
/// Returns a value indicating whether the discriminator column can be configured.
/// 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>
Expand All @@ -462,16 +455,12 @@ public static bool CanSetCheckConstraint(
Check.NotEmpty(name, nameof(name));
Check.NullButNotEmpty(sql, nameof(sql));

var entityType = entityTypeBuilder.Metadata;
var tableName = entityType.GetTableName();
var schema = entityType.GetSchema();

var constraint = entityType.Model.FindCheckConstraint(name, tableName, schema);
var constraint = entityTypeBuilder.Metadata.FindCheckConstraint(name);

return constraint == null
|| constraint.Sql == sql
|| (fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention)
.Overrides(entityTypeBuilder.Metadata.GetDiscriminatorPropertyConfigurationSource());
.Overrides(constraint.GetConfigurationSource());
}
}
}
111 changes: 111 additions & 0 deletions src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
// 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.Reflection;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Utilities;

// ReSharper disable once CheckNamespace
Expand Down Expand Up @@ -142,5 +144,114 @@ public static void SetSchema(
public static ConfigurationSource? GetSchemaConfigurationSource([NotNull] this IConventionEntityType entityType)
=> entityType.FindAnnotation(RelationalAnnotationNames.Schema)
?.GetConfigurationSource();

/// <summary>
/// Finds an <see cref="ICheckConstraint" /> with the given name.
/// </summary>
/// <param name="entityType"> The entity type to find the check constraint for. </param>
/// <param name="name"> The check constraint name. </param>
/// <returns>
/// The <see cref="ICheckConstraint" /> or <c>null</c> if no check constraint with the
/// given name in the given entity type was found.
/// </returns>
public static ICheckConstraint FindCheckConstraint(
[NotNull] this IEntityType entityType, [NotNull] string name)
{
Check.NotEmpty(name, nameof(name));

return CheckConstraint.FindCheckConstraint(entityType, name);
}

/// <summary>
/// Finds an <see cref="IConventionCheckConstraint" /> with the given name.
/// </summary>
/// <param name="entityType"> The entity type to find the check constraint for. </param>
/// <param name="name"> The check constraint name. </param>
/// <returns>
/// The <see cref="IConventionCheckConstraint" /> or <c>null</c> if no check constraint with the
/// given name in the given entity type was found.
/// </returns>
public static IConventionCheckConstraint FindCheckConstraint(
[NotNull] this IConventionEntityType entityType, [NotNull] string name)
=> (IConventionCheckConstraint)((IEntityType)entityType).FindCheckConstraint(name);

/// <summary>
/// Creates a new check constraint with the given name on entity type. Throws an exception
/// if a check constraint with the same name exists on the same entity type.
/// </summary>
/// <param name="entityType"> The entity type to add the check constraint to. </param>
/// <param name="name"> The check constraint name. </param>
/// <param name="sql"> The logical constraint sql used in the check constraint. </param>
/// <returns> The new check constraint. </returns>
public static ICheckConstraint AddCheckConstraint(
[NotNull] this IMutableEntityType entityType,
[NotNull] string name,
[NotNull] string sql)
{
Check.NotEmpty(name, nameof(name));
Check.NotEmpty(sql, nameof(sql));

return new CheckConstraint(entityType, name, sql, ConfigurationSource.Explicit);
}

/// <summary>
/// Creates a new check constraint with the given name on entity type. Throws an exception
/// if a check constraint with the same name exists on the same entity type.
/// </summary>
/// <param name="entityType"> The entity type to add the check constraint to. </param>
/// <param name="name"> The check constraint name. </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> The new check constraint. </returns>
public static IConventionCheckConstraint AddCheckConstraint(
[NotNull] this IConventionEntityType entityType,
[NotNull] string name,
[NotNull] string sql,
bool fromDataAnnotation = false)
{
Check.NotEmpty(name, nameof(name));
Check.NotEmpty(sql, nameof(sql));

return new CheckConstraint(
(IMutableEntityType)entityType, name, sql,
fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
}

/// <summary>
/// Removes the <see cref="ICheckConstraint" /> with the given name.
/// </summary>
/// <param name="entityType"> The entity type to remove the check constraint from. </param>
/// <param name="name"> The check constraint name to be removed. </param>
/// <returns>
/// True if the <see cref="ICheckConstraint" /> is successfully found and removed; otherwise, false.
/// </returns>
public static bool RemoveCheckConstraint(
[NotNull] this IMutableEntityType entityType,
[NotNull] string name)
{
Check.NotEmpty(name, nameof(name));

return CheckConstraint.RemoveCheckConstraint(entityType, name);
}

/// <summary>
/// Removes the <see cref="IConventionCheckConstraint" /> with the given name.
/// </summary>
/// <param name="entityType"> The entity type to remove the check constraint from. </param>
/// <param name="name"> The check constraint name. </param>
/// <returns>
/// True if the <see cref="IConventionCheckConstraint" /> is successfully found and removed; otherwise, false.
/// </returns>
public static bool RemoveCheckConstraint(
[NotNull] this IConventionEntityType entityType,
[NotNull] string name)
=> RemoveCheckConstraint((IMutableEntityType)entityType, name);

/// <summary>
/// Returns all <see cref="ICheckConstraint" /> contained in the entity type.
/// </summary>
/// <param name="entityType"> The entity type to get the check constraints for. </param>
public static IEnumerable<ICheckConstraint> GetCheckConstraints([NotNull] this IEntityType entityType)
=> CheckConstraint.GetCheckConstraints(entityType);
}
}
Loading