diff --git a/src/EFCore.Design/Migrations/Design/CSharpMigrationOperationGenerator.cs b/src/EFCore.Design/Migrations/Design/CSharpMigrationOperationGenerator.cs
index 1c9f452e9cb..f41ded5dd1e 100644
--- a/src/EFCore.Design/Migrations/Design/CSharpMigrationOperationGenerator.cs
+++ b/src/EFCore.Design/Migrations/Design/CSharpMigrationOperationGenerator.cs
@@ -384,6 +384,45 @@ protected virtual void Generate([NotNull] AddUniqueConstraintOperation operation
}
}
+ ///
+ /// Generates code for an .
+ ///
+ /// The operation.
+ /// The builder code is added to.
+ protected virtual void Generate([NotNull] CreateCheckConstraintOperation operation, [NotNull] IndentedStringBuilder builder)
+ {
+ Check.NotNull(operation, nameof(operation));
+ Check.NotNull(builder, nameof(builder));
+
+ builder.AppendLine(".CreateCheckConstraint(");
+
+ using (builder.Indent())
+ {
+ builder
+ .Append("name: ")
+ .Append(Code.Literal(operation.Name));
+
+ if (operation.Schema != null)
+ {
+ builder
+ .AppendLine(",")
+ .Append("schema: ")
+ .Append(Code.Literal(operation.Schema));
+ }
+
+ builder
+ .AppendLine(",")
+ .Append("table: ")
+ .Append(Code.Literal(operation.Table))
+ .AppendLine(",")
+ .Append("constraintSql: ")
+ .Append(Code.Literal(operation.ConstraintSql))
+ .Append(")");
+
+ Annotations(operation.GetAnnotations(), builder);
+ }
+ }
+
///
/// Generates code for an .
///
@@ -1039,6 +1078,23 @@ protected virtual void Generate([NotNull] CreateTableOperation operation, [NotNu
builder.AppendLine(";");
}
+ foreach (var checkConstraints in operation.CheckConstraints)
+ {
+ builder
+ .Append("table.CheckConstraint(")
+ .Append(Code.Literal(checkConstraints.Name))
+ .Append(", ")
+ .Append(Code.Literal(checkConstraints.ConstraintSql))
+ .Append(")");
+
+ using (builder.Indent())
+ {
+ Annotations(checkConstraints.GetAnnotations(), builder);
+ }
+
+ builder.AppendLine(";");
+ }
+
foreach (var foreignKey in operation.ForeignKeys)
{
builder.AppendLine("table.ForeignKey(");
@@ -1380,6 +1436,42 @@ protected virtual void Generate([NotNull] DropUniqueConstraintOperation operatio
}
}
+ ///
+ /// Generates code for a .
+ ///
+ /// The operation.
+ /// The builder code is added to.
+ protected virtual void Generate([NotNull] DropCheckConstraintOperation operation, [NotNull] IndentedStringBuilder builder)
+ {
+ Check.NotNull(operation, nameof(operation));
+ Check.NotNull(builder, nameof(builder));
+
+ builder.AppendLine(".DropCheckConstraint(");
+
+ using (builder.Indent())
+ {
+ builder
+ .Append("name: ")
+ .Append(Code.Literal(operation.Name));
+
+ if (operation.Schema != null)
+ {
+ builder
+ .AppendLine(",")
+ .Append("schema: ")
+ .Append(Code.Literal(operation.Schema));
+ }
+
+ builder
+ .AppendLine(",")
+ .Append("table: ")
+ .Append(Code.Literal(operation.Table))
+ .Append(")");
+
+ Annotations(operation.GetAnnotations(), builder);
+ }
+ }
+
///
/// Generates code for a .
///
diff --git a/src/EFCore.Relational/Metadata/ICheckConstraint.cs b/src/EFCore.Relational/Metadata/ICheckConstraint.cs
new file mode 100644
index 00000000000..d236bb4906f
--- /dev/null
+++ b/src/EFCore.Relational/Metadata/ICheckConstraint.cs
@@ -0,0 +1,36 @@
+// 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
+{
+ ///
+ /// Represents a check constraint in the .
+ ///
+ public interface ICheckConstraint
+ {
+ ///
+ /// Gets the name of the check constraint in the database.
+ ///
+ string Name { get; }
+
+ ///
+ /// The database table that contains the check constraint.
+ ///
+ string Table { get; }
+
+ ///
+ /// The database table schema that contains the check constraint.
+ ///
+ string Schema { get; }
+
+ ///
+ /// The in which this check constraint is defined.
+ ///
+ IModel Model { get; }
+
+ ///
+ /// Gets the constraint sql used in a check constraint in the database.
+ ///
+ string ConstraintSql { get; }
+ }
+}
diff --git a/src/EFCore.Relational/Metadata/IRelationalModelAnnotations.cs b/src/EFCore.Relational/Metadata/IRelationalModelAnnotations.cs
index d79a14f8929..dfe400bd909 100644
--- a/src/EFCore.Relational/Metadata/IRelationalModelAnnotations.cs
+++ b/src/EFCore.Relational/Metadata/IRelationalModelAnnotations.cs
@@ -24,6 +24,18 @@ public interface IRelationalModelAnnotations
///
ISequence FindSequence([NotNull] string name, [CanBeNull] string schema = null);
+ ///
+ /// Finds an with the given name.
+ ///
+ /// The check constraint name.
+ /// The table that contains the check constraint.
+ /// The table schema that contains the check constraint.
+ ///
+ /// The or null if no check constraint with the given name in
+ /// the given schema was found.
+ ///
+ ICheckConstraint FindCheckConstraint([NotNull] string name, [NotNull] string table, [CanBeNull] string schema = null);
+
///
/// Finds a that is mapped to the method represented by the given .
///
@@ -36,6 +48,11 @@ public interface IRelationalModelAnnotations
///
IReadOnlyList Sequences { get; }
+ ///
+ /// All s contained in the model.
+ ///
+ IReadOnlyList CheckConstraints { get; }
+
///
/// All s contained in the model.
///
diff --git a/src/EFCore.Relational/Metadata/Internal/CheckConstraint.cs b/src/EFCore.Relational/Metadata/Internal/CheckConstraint.cs
new file mode 100644
index 00000000000..88fe0300f13
--- /dev/null
+++ b/src/EFCore.Relational/Metadata/Internal/CheckConstraint.cs
@@ -0,0 +1,235 @@
+// 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.Text;
+using JetBrains.Annotations;
+using Microsoft.EntityFrameworkCore.Diagnostics;
+using Microsoft.EntityFrameworkCore.Utilities;
+
+namespace Microsoft.EntityFrameworkCore.Metadata.Internal
+{
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public class CheckConstraint : ICheckConstraint
+ {
+ private readonly IModel _model;
+ private readonly string _annotationName;
+
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public CheckConstraint(
+ [NotNull] IMutableModel model,
+ [NotNull] string name,
+ [NotNull] string constraintSql,
+ [NotNull] string table,
+ [CanBeNull] string schema = null)
+ : this(model, GetAnnotationKey(name, table, schema))
+ {
+ Check.NotEmpty(name, nameof(name));
+ Check.NotEmpty(constraintSql, nameof(constraintSql));
+ Check.NotEmpty(table, nameof(table));
+ Check.NullButNotEmpty(schema, nameof(schema));
+
+ SetData(
+ new CheckContraintData
+ {
+ Name = name,
+ ConstraintSql = constraintSql,
+ Table = table,
+ Schema = schema
+ });
+ }
+
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public CheckConstraint([NotNull] IModel model, [NotNull] string annotationName)
+ {
+ Check.NotNull(model, nameof(model));
+
+ _model = model;
+ _annotationName = annotationName;
+ }
+
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public static IEnumerable GetCheckConstraints([NotNull] IModel model)
+ {
+ Check.NotNull(model, nameof(model));
+
+ return GetAnnotationsDictionary(model)?
+ .Select(a => new CheckConstraint(model, a.Key)) ?? Enumerable.Empty();
+ }
+
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public static ICheckConstraint FindCheckConstraint([NotNull] IModel model, [NotNull] string name, [NotNull] string table, [CanBeNull] string schema = null)
+ {
+ Check.NotNull(model, nameof(model));
+ Check.NotEmpty(name, nameof(name));
+ Check.NotEmpty(table, nameof(table));
+ Check.NullButNotEmpty(schema, nameof(schema));
+
+ var dataDictionary = GetAnnotationsDictionary(model);
+ var annotationKey = GetAnnotationKey(name, table, schema);
+
+ return dataDictionary?.ContainsKey(annotationKey) == true ? new CheckConstraint(model, annotationKey) : null;
+ }
+
+ private static string GetAnnotationKey(string name, string table, string schema = null)
+ => $"{schema}.{table}:{name}";
+
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public virtual Model Model => (Model)_model;
+
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public virtual string Name => GetData().Name;
+
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public virtual string Table => GetData().Table;
+
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public virtual string Schema => GetData().Schema ?? Model.Relational().DefaultSchema;
+
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public virtual string ConstraintSql
+ {
+ get => GetData().ConstraintSql;
+ set
+ {
+ var data = GetData();
+ data.ConstraintSql = value;
+ SetData(data);
+ }
+ }
+
+ private CheckContraintData GetData()
+ {
+ var dataDictionary = GetAnnotationsDictionary(Model);
+ return CheckContraintData.Deserialize(dataDictionary?[_annotationName]);
+ }
+
+ private void SetData(CheckContraintData data)
+ {
+ var dataDictionary = GetAnnotationsDictionary(Model);
+ if (dataDictionary == null)
+ {
+ dataDictionary = new Dictionary();
+ Model[RelationalAnnotationNames.CheckConstraints] = dataDictionary;
+ }
+ dataDictionary[_annotationName] = data.Serialize();
+ }
+
+ internal static Dictionary GetAnnotationsDictionary(IModel model) =>
+ (Dictionary)model[RelationalAnnotationNames.CheckConstraints];
+
+ IModel ICheckConstraint.Model => _model;
+
+ private class CheckContraintData
+ {
+ public string Name { get; set; }
+
+ public string Table { get; set; }
+
+ public string Schema { get; set; }
+
+ public string ConstraintSql { get; set; }
+
+ public string Serialize()
+ {
+ var builder = new StringBuilder();
+
+ EscapeAndQuote(builder, Name);
+ builder.Append(", ");
+ EscapeAndQuote(builder, Table);
+ builder.Append(", ");
+ EscapeAndQuote(builder, Schema);
+ builder.Append(", ");
+ EscapeAndQuote(builder, ConstraintSql);
+
+ return builder.ToString();
+ }
+
+ public static CheckContraintData Deserialize([NotNull] string value)
+ {
+ Check.NotEmpty(value, nameof(value));
+
+ try
+ {
+ var data = new CheckContraintData();
+
+ // ReSharper disable PossibleInvalidOperationException
+ var position = 0;
+ data.Name = ExtractValue(value, ref position);
+ data.Table = ExtractValue(value, ref position);
+ data.Schema = ExtractValue(value, ref position);
+ data.ConstraintSql = ExtractValue(value, ref position);
+ // ReSharper restore PossibleInvalidOperationException
+
+ return data;
+ }
+ catch (Exception ex)
+ {
+ throw new ArgumentException(RelationalStrings.BadCheckConstraintString, ex);
+ }
+ }
+
+ private static string ExtractValue(string value, ref int position)
+ {
+ position = value.IndexOf('\'', position) + 1;
+
+ var end = value.IndexOf('\'', position);
+
+ while (end + 1 < value.Length
+ && value[end + 1] == '\'')
+ {
+ end = value.IndexOf('\'', end + 2);
+ }
+
+ var extracted = value.Substring(position, end - position).Replace("''", "'");
+ position = end + 1;
+
+ return extracted.Length == 0 ? null : extracted;
+ }
+
+ private static void EscapeAndQuote(StringBuilder builder, object value)
+ {
+ builder.Append("'");
+
+ if (value != null)
+ {
+ builder.Append(value.ToString().Replace("'", "''"));
+ }
+
+ builder.Append("'");
+ }
+ }
+ }
+}
diff --git a/src/EFCore.Relational/Metadata/RelationalAnnotationNames.cs b/src/EFCore.Relational/Metadata/RelationalAnnotationNames.cs
index 91b582cffe4..c3d16f6e2f2 100644
--- a/src/EFCore.Relational/Metadata/RelationalAnnotationNames.cs
+++ b/src/EFCore.Relational/Metadata/RelationalAnnotationNames.cs
@@ -65,6 +65,11 @@ public static class RelationalAnnotationNames
///
public const string SequencePrefix = Prefix + "Sequence:";
+ ///
+ /// The prefix for serialized check constraint annotations.
+ ///
+ public const string CheckConstraints = Prefix + "CheckConstraints";
+
///
/// The name for discriminator property annotations.
///
diff --git a/src/EFCore.Relational/Metadata/RelationalModelAnnotations.cs b/src/EFCore.Relational/Metadata/RelationalModelAnnotations.cs
index 9a49e126d1b..5bc240d6f6d 100644
--- a/src/EFCore.Relational/Metadata/RelationalModelAnnotations.cs
+++ b/src/EFCore.Relational/Metadata/RelationalModelAnnotations.cs
@@ -65,7 +65,7 @@ public virtual IMutableSequence FindSequence([NotNull] string name, [CanBeNull]
Check.NotEmpty(name, nameof(name));
Check.NullButNotEmpty(schema, nameof(schema));
- var annotationName = BuildAnnotationName(RelationalAnnotationNames.SequencePrefix, name, schema);
+ var annotationName = BuildSequenceAnnotationName(RelationalAnnotationNames.SequencePrefix, name, schema);
return Model[annotationName] == null ? null : new Sequence(Model, annotationName);
}
@@ -79,9 +79,9 @@ public virtual IMutableSequence FindSequence([NotNull] string name, [CanBeNull]
/// The sequence.
public virtual IMutableSequence GetOrAddSequence([NotNull] string name, [CanBeNull] string schema = null)
=> FindSequence(name, schema)
- ?? new Sequence((IMutableModel)Model, BuildAnnotationName(RelationalAnnotationNames.SequencePrefix, name, schema), name, schema);
+ ?? new Sequence((IMutableModel)Model, BuildSequenceAnnotationName(RelationalAnnotationNames.SequencePrefix, name, schema), name, schema);
- private static string BuildAnnotationName(string annotationPrefix, string name, string schema)
+ private static string BuildSequenceAnnotationName(string annotationPrefix, string name, string schema)
=> annotationPrefix + schema + "." + name;
///
@@ -166,5 +166,65 @@ protected virtual bool SetMaxIdentifierLength(int? value)
/// All s contained in the model.
///
IReadOnlyList IRelationalModelAnnotations.Sequences => Sequences;
+
+ ///
+ /// All s contained in the model.
+ ///
+ public virtual IReadOnlyList CheckConstraints
+ => CheckConstraint.GetCheckConstraints(Model).ToList();
+
+ ///
+ /// Finds an with the given name.
+ ///
+ /// The table schema that contains the check constraint.
+ /// The table that contains the check constraint.
+ /// The check constraint name.
+ ///
+ /// The or null if no check constraint with the given name in
+ /// the given schema was found.
+ ///
+ public virtual ICheckConstraint FindCheckConstraint([NotNull] string name, [NotNull] string table, [CanBeNull] string schema = null)
+ {
+ Check.NotEmpty(name, nameof(name));
+ Check.NotEmpty(table, nameof(table));
+ Check.NullButNotEmpty(schema, nameof(schema));
+
+ return CheckConstraint.FindCheckConstraint(Model, name, table, schema);
+ }
+
+ ///
+ /// Either returns the existing with the given name in the given schema
+ /// or creates a new check constraint with the given name and schema.
+ ///
+ /// The table schema that contains the check constraint.
+ /// The table that contains the check constraint.
+ /// The check constraint name.
+ /// The logical constraint sql used in the check constraint.
+ /// The check constraint.
+ public virtual ICheckConstraint GetOrAddCheckConstraint(
+ [NotNull] string name,
+ [NotNull] string constraintSql,
+ [NotNull] string table,
+ [CanBeNull] string schema = null)
+ => FindCheckConstraint(name, table, schema)
+ ?? new CheckConstraint((IMutableModel)Model, name, constraintSql, table, schema);
+
+ ///
+ /// Finds an with the given name.
+ ///
+ /// The table schema that contains the check constraint.
+ /// The table that contains the check constraint.
+ /// The check constraint name.
+ ///
+ /// The or null if no check constraint with the given name in
+ /// the given schema was found.
+ ///
+ ICheckConstraint IRelationalModelAnnotations.FindCheckConstraint([NotNull] string name, [NotNull] string table, [CanBeNull] string schema)
+ => FindCheckConstraint(name, table, schema);
+
+ ///
+ /// All s contained in the model.
+ ///
+ IReadOnlyList IRelationalModelAnnotations.CheckConstraints => CheckConstraints;
}
}
diff --git a/src/EFCore.Relational/Migrations/IMigrationsAnnotationProvider.cs b/src/EFCore.Relational/Migrations/IMigrationsAnnotationProvider.cs
index dd1b20d4cb4..9977668efa0 100644
--- a/src/EFCore.Relational/Migrations/IMigrationsAnnotationProvider.cs
+++ b/src/EFCore.Relational/Migrations/IMigrationsAnnotationProvider.cs
@@ -71,6 +71,13 @@ public interface IMigrationsAnnotationProvider
/// The annotations.
IEnumerable For([NotNull] ISequence sequence);
+ ///
+ /// Gets provider-specific Migrations annotations for the given .
+ ///
+ /// The check constraint.
+ /// The annotations.
+ IEnumerable For([NotNull] ICheckConstraint checkConstraint);
+
///
/// Gets provider-specific Migrations annotations for the given
/// when it is being removed/altered.
@@ -126,5 +133,13 @@ public interface IMigrationsAnnotationProvider
/// The sequence.
/// The annotations.
IEnumerable ForRemove([NotNull] ISequence sequence);
+
+ ///
+ /// Gets provider-specific Migrations annotations for the given
+ /// when it is being removed/altered.
+ ///
+ /// The check constraint.
+ /// The annotations.
+ IEnumerable ForRemove([NotNull] ICheckConstraint sequence);
}
}
diff --git a/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs b/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs
index 856e571d560..20820b593ec 100644
--- a/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs
+++ b/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs
@@ -39,7 +39,7 @@ namespace Microsoft.EntityFrameworkCore.Migrations.Internal
///
public class MigrationsModelDiffer : IMigrationsModelDiffer
{
- private static readonly Type[] _dropOperationTypes = { typeof(DropIndexOperation), typeof(DropPrimaryKeyOperation), typeof(DropSequenceOperation), typeof(DropUniqueConstraintOperation) };
+ private static readonly Type[] _dropOperationTypes = { typeof(DropIndexOperation), typeof(DropPrimaryKeyOperation), typeof(DropSequenceOperation), typeof(DropUniqueConstraintOperation), typeof(DropCheckConstraintOperation) };
private static readonly Type[] _alterOperationTypes = { typeof(AddPrimaryKeyOperation), typeof(AddUniqueConstraintOperation), typeof(AlterSequenceOperation) };
@@ -156,6 +156,7 @@ protected virtual IReadOnlyList Sort(
var dropTableOperations = new List();
var ensureSchemaOperations = new List();
var createSequenceOperations = new List();
+ var createCheckConstraintOperations = new List();
var createTableOperations = new List();
var alterDatabaseOperations = new List();
var alterTableOperations = new List();
@@ -197,6 +198,10 @@ protected virtual IReadOnlyList Sort(
{
createSequenceOperations.Add(operation);
}
+ else if (type == typeof(CreateCheckConstraintOperation))
+ {
+ createCheckConstraintOperations.Add(operation);
+ }
else if (type == typeof(CreateTableOperation))
{
createTableOperations.Add((CreateTableOperation)operation);
@@ -326,6 +331,7 @@ protected virtual IReadOnlyList Sort(
.Concat(renameOperations)
.Concat(alterDatabaseOperations)
.Concat(createSequenceOperations)
+ .Concat(createCheckConstraintOperations)
.Concat(alterTableOperations)
.Concat(columnOperations)
.Concat(computedColumnOperations)
@@ -358,6 +364,7 @@ protected virtual IEnumerable Diff(
.Concat(Diff(GetSchemas(source), GetSchemas(target), diffContext))
.Concat(Diff(diffContext.GetSourceTables(), diffContext.GetTargetTables(), diffContext))
.Concat(Diff(source.Relational().Sequences, target.Relational().Sequences, diffContext))
+ .Concat(Diff(source.Relational().CheckConstraints, target.Relational().CheckConstraints, diffContext))
.Concat(
Diff(
diffContext.GetSourceTables().SelectMany(s => s.GetForeignKeys()),
@@ -424,6 +431,7 @@ protected virtual IEnumerable Add([NotNull] IModel target, [
.Concat(GetSchemas(target).SelectMany(t => Add(t, diffContext)))
.Concat(diffContext.GetTargetTables().SelectMany(t => Add(t, diffContext)))
.Concat(target.Relational().Sequences.SelectMany(t => Add(t, diffContext)))
+ .Concat(target.Relational().CheckConstraints.SelectMany(t => Add(t, diffContext)))
.Concat(diffContext.GetTargetTables().SelectMany(t => t.GetForeignKeys()).SelectMany(k => Add(k, diffContext)));
///
@@ -435,7 +443,8 @@ protected virtual IEnumerable Add([NotNull] IModel target, [
protected virtual IEnumerable Remove([NotNull] IModel source, [NotNull] DiffContext diffContext)
=> DiffAnnotations(source, null)
.Concat(diffContext.GetSourceTables().SelectMany(t => Remove(t, diffContext)))
- .Concat(source.Relational().Sequences.SelectMany(t => Remove(t, diffContext)));
+ .Concat(source.Relational().Sequences.SelectMany(t => Remove(t, diffContext)))
+ .Concat(source.Relational().CheckConstraints.SelectMany(t => Remove(t, diffContext)));
#endregion
@@ -1369,6 +1378,75 @@ protected virtual IEnumerable Remove([NotNull] IIndex source
#endregion
+ #region ICheckConstraint
+
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ protected virtual IEnumerable Diff(
+ [NotNull] IEnumerable source,
+ [NotNull] IEnumerable target,
+ [NotNull] DiffContext diffContext)
+ => DiffCollection(
+ source,
+ target,
+ diffContext,
+ Diff,
+ Add,
+ Remove,
+ (s, t, c) => string.Equals(s.Schema, t.Schema, StringComparison.OrdinalIgnoreCase)
+ && string.Equals(s.Name, t.Name, StringComparison.OrdinalIgnoreCase)
+ && string.Equals(s.Table, t.Table, StringComparison.OrdinalIgnoreCase)
+ && string.Equals(s.ConstraintSql, t.ConstraintSql, StringComparison.OrdinalIgnoreCase));
+
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ protected virtual IEnumerable Diff(
+ [NotNull] ICheckConstraint source, [NotNull] ICheckConstraint target, [NotNull] DiffContext diffContext)
+ => Enumerable.Empty();
+
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ protected virtual IEnumerable Add([NotNull] ICheckConstraint target, [NotNull] DiffContext diffContext)
+ {
+ var operation = new CreateCheckConstraintOperation
+ {
+ Schema = target.Schema,
+ Name = target.Name,
+ Table = target.Table,
+ ConstraintSql = target.ConstraintSql
+ };
+
+ operation.ConstraintSql = target.ConstraintSql;
+ operation.AddAnnotations(MigrationsAnnotations.For(target));
+
+ yield return operation;
+ }
+
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ protected virtual IEnumerable Remove([NotNull] ICheckConstraint source, [NotNull] DiffContext diffContext)
+ {
+ var operation = new DropCheckConstraintOperation
+ {
+ Schema = source.Schema,
+ Name = source.Name,
+ Table = source.Table
+ };
+ operation.AddAnnotations(MigrationsAnnotations.ForRemove(source));
+
+ yield return operation;
+ }
+
+ #endregion
+
#region ISequence
///
diff --git a/src/EFCore.Relational/Migrations/MigrationBuilder.cs b/src/EFCore.Relational/Migrations/MigrationBuilder.cs
index ca1e737a631..55e18d50617 100644
--- a/src/EFCore.Relational/Migrations/MigrationBuilder.cs
+++ b/src/EFCore.Relational/Migrations/MigrationBuilder.cs
@@ -627,6 +627,34 @@ public virtual OperationBuilder CreateSequence(
return new OperationBuilder(operation);
}
+ ///
+ /// Builds an to create a new check constraint.
+ ///
+ /// The check constraint name.
+ /// The name of the table for the check constraint.
+ /// The constraint sql for the check constraint.
+ /// The schema that contains the check constraint, or null to use the default schema.
+ /// A builder to allow annotations to be added to the operation.
+ public virtual OperationBuilder CreateCheckConstraint(
+ [NotNull] string name,
+ [NotNull] string table,
+ [NotNull] string constraintSql,
+ [CanBeNull] string schema = null)
+ {
+ Check.NotEmpty(name, nameof(name));
+
+ var operation = new CreateCheckConstraintOperation
+ {
+ Schema = schema,
+ Name = name,
+ Table = table,
+ ConstraintSql = constraintSql
+ };
+ Operations.Add(operation);
+
+ return new OperationBuilder(operation);
+ }
+
///
/// Builds an to create a new table.
///
@@ -821,6 +849,31 @@ public virtual OperationBuilder DropSequence(
return new OperationBuilder(operation);
}
+ ///
+ /// Builds an to drop an existing check constraint.
+ ///
+ /// The name of the check constraint to drop.
+ /// The name of the table for the check constraint to drop.
+ /// The schema that contains the check constraint, or null to use the default schema.
+ /// A builder to allow annotations to be added to the operation.
+ public virtual OperationBuilder DropCheckConstraint(
+ [NotNull] string name,
+ [NotNull] string table,
+ [CanBeNull] string schema = null)
+ {
+ Check.NotEmpty(name, nameof(name));
+
+ var operation = new DropCheckConstraintOperation
+ {
+ Name = name,
+ Table = table,
+ Schema = schema
+ };
+ Operations.Add(operation);
+
+ return new OperationBuilder(operation);
+ }
+
///
/// Builds an to drop an existing table.
///
diff --git a/src/EFCore.Relational/Migrations/MigrationsAnnotationProvider.cs b/src/EFCore.Relational/Migrations/MigrationsAnnotationProvider.cs
index 7419a106066..2f4ea41f4e7 100644
--- a/src/EFCore.Relational/Migrations/MigrationsAnnotationProvider.cs
+++ b/src/EFCore.Relational/Migrations/MigrationsAnnotationProvider.cs
@@ -117,6 +117,18 @@ public MigrationsAnnotationProvider([NotNull] MigrationsAnnotationProviderDepend
/// The annotations.
public virtual IEnumerable For(ISequence sequence) => Enumerable.Empty();
+ ///
+ ///
+ /// Gets provider-specific Migrations annotations for the given .
+ ///
+ ///
+ /// The default implementation returns an empty collection.
+ ///
+ ///
+ /// The check constraint.
+ /// The annotations.
+ public virtual IEnumerable For(ICheckConstraint checkConstraint) => Enumerable.Empty();
+
///
///
/// Gets provider-specific Migrations annotations for the given
@@ -207,5 +219,18 @@ public MigrationsAnnotationProvider([NotNull] MigrationsAnnotationProviderDepend
/// The sequence.
/// The annotations.
public virtual IEnumerable ForRemove(ISequence sequence) => Enumerable.Empty();
+
+ ///
+ ///
+ /// Gets provider-specific Migrations annotations for the given
+ /// when it is being removed/altered.
+ ///
+ ///
+ /// The default implementation returns an empty collection.
+ ///
+ ///
+ /// The check constraint.
+ /// The annotations.
+ public virtual IEnumerable ForRemove(ICheckConstraint checkConstraint) => Enumerable.Empty();
}
}
diff --git a/src/EFCore.Relational/Migrations/MigrationsSqlGenerator.cs b/src/EFCore.Relational/Migrations/MigrationsSqlGenerator.cs
index c9355258ffb..4b02a862eb1 100644
--- a/src/EFCore.Relational/Migrations/MigrationsSqlGenerator.cs
+++ b/src/EFCore.Relational/Migrations/MigrationsSqlGenerator.cs
@@ -47,6 +47,7 @@ public class MigrationsSqlGenerator : IMigrationsSqlGenerator
{ typeof(AlterDatabaseOperation), (g, o, m, b) => g.Generate((AlterDatabaseOperation)o, m, b) },
{ typeof(AlterSequenceOperation), (g, o, m, b) => g.Generate((AlterSequenceOperation)o, m, b) },
{ typeof(AlterTableOperation), (g, o, m, b) => g.Generate((AlterTableOperation)o, m, b) },
+ { typeof(CreateCheckConstraintOperation), (g, o, m, b) => g.Generate((CreateCheckConstraintOperation)o, m, b) },
{ typeof(CreateIndexOperation), (g, o, m, b) => g.Generate((CreateIndexOperation)o, m, b) },
{ typeof(CreateSequenceOperation), (g, o, m, b) => g.Generate((CreateSequenceOperation)o, m, b) },
{ typeof(CreateTableOperation), (g, o, m, b) => g.Generate((CreateTableOperation)o, m, b) },
@@ -58,6 +59,7 @@ public class MigrationsSqlGenerator : IMigrationsSqlGenerator
{ typeof(DropSequenceOperation), (g, o, m, b) => g.Generate((DropSequenceOperation)o, m, b) },
{ typeof(DropTableOperation), (g, o, m, b) => g.Generate((DropTableOperation)o, m, b) },
{ typeof(DropUniqueConstraintOperation), (g, o, m, b) => g.Generate((DropUniqueConstraintOperation)o, m, b) },
+ { typeof(DropCheckConstraintOperation), (g, o, m, b) => g.Generate((DropCheckConstraintOperation)o, m, b) },
{ typeof(EnsureSchemaOperation), (g, o, m, b) => g.Generate((EnsureSchemaOperation)o, m, b) },
{ typeof(RenameColumnOperation), (g, o, m, b) => g.Generate((RenameColumnOperation)o, m, b) },
{ typeof(RenameIndexOperation), (g, o, m, b) => g.Generate((RenameIndexOperation)o, m, b) },
@@ -309,6 +311,30 @@ protected virtual void Generate(
EndStatement(builder);
}
+ ///
+ /// Builds commands for the given by making calls on the given
+ /// , and then terminates the final command.
+ ///
+ /// The operation.
+ /// The target model which may be null if the operations exist without a model.
+ /// The command builder to use to build the commands.
+ protected virtual void Generate(
+ [NotNull] CreateCheckConstraintOperation operation,
+ [CanBeNull] IModel model,
+ [NotNull] MigrationCommandListBuilder builder)
+ {
+ Check.NotNull(operation, nameof(operation));
+ Check.NotNull(builder, nameof(builder));
+
+ builder
+ .Append("ALTER TABLE ")
+ .Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(operation.Table, operation.Schema))
+ .Append(" ADD ");
+ CheckConstraint(operation, model, builder);
+ builder.AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator);
+ EndStatement(builder);
+ }
+
///
///
/// Can be overridden by database providers to build commands for the given
@@ -893,6 +919,31 @@ protected virtual void Generate(
EndStatement(builder);
}
+ ///
+ /// Builds commands for the given by making calls on the given
+ /// , and then terminates the final command.
+ ///
+ /// The operation.
+ /// The target model which may be null if the operations exist without a model.
+ /// The command builder to use to build the commands.
+ protected virtual void Generate(
+ [NotNull] DropCheckConstraintOperation operation,
+ [CanBeNull] IModel model,
+ [NotNull] MigrationCommandListBuilder builder)
+ {
+ Check.NotNull(operation, nameof(operation));
+ Check.NotNull(builder, nameof(builder));
+
+ builder
+ .Append("ALTER TABLE ")
+ .Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(operation.Table, operation.Schema))
+ .Append(" DROP CONSTRAINT ")
+ .Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(operation.Name))
+ .AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator);
+
+ EndStatement(builder);
+ }
+
///
///
/// Can be overridden by database providers to build commands for the given
@@ -1365,6 +1416,7 @@ protected virtual void CreateTableConstraints(
CreateTablePrimaryKeyContstraint(operation, model, builder);
CreateTableUniqueConstraints(operation, model, builder);
+ CreateTableCheckConstraints(operation, model, builder);
CreateTableForeignKeys(operation, model, builder);
}
@@ -1544,6 +1596,57 @@ protected virtual void UniqueConstraint(
.Append(")");
}
+ ///
+ /// Generates a SQL fragment for the check constraints of a .
+ ///
+ /// The operation.
+ /// The target model which may be null if the operations exist without a model.
+ /// The command builder to use to add the SQL fragment.
+ protected virtual void CreateTableCheckConstraints(
+ [NotNull] CreateTableOperation operation,
+ [CanBeNull] IModel model,
+ [NotNull] MigrationCommandListBuilder builder)
+ {
+ Check.NotNull(operation, nameof(operation));
+ Check.NotNull(builder, nameof(builder));
+
+ foreach (var checkConstraint in operation.CheckConstraints)
+ {
+ builder.AppendLine(",");
+ CheckConstraint(checkConstraint, model, builder);
+ }
+ }
+
+ ///
+ /// Generates a SQL fragment for a check constraint of an .
+ ///
+ /// The operation.
+ /// The target model which may be null if the operations exist without a model.
+ /// The command builder to use to add the SQL fragment.
+ protected virtual void CheckConstraint(
+ [NotNull] CreateCheckConstraintOperation operation,
+ [CanBeNull] IModel model,
+ [NotNull] MigrationCommandListBuilder builder)
+ {
+ Check.NotNull(operation, nameof(operation));
+ Check.NotNull(builder, nameof(builder));
+
+ if (operation.Name != null)
+ {
+ builder
+ .Append("CONSTRAINT ")
+ .Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(operation.Name))
+ .Append(" ");
+ }
+
+ builder
+ .Append("CHECK ");
+
+ builder.Append("(")
+ .Append(operation.ConstraintSql)
+ .Append(")");
+ }
+
///
/// Generates a SQL fragment for traits of an index from a ,
/// , or .
diff --git a/src/EFCore.Relational/Migrations/Operations/Builders/CreateTableBuilder.cs b/src/EFCore.Relational/Migrations/Operations/Builders/CreateTableBuilder.cs
index 0476d15e829..2ea806e22f7 100644
--- a/src/EFCore.Relational/Migrations/Operations/Builders/CreateTableBuilder.cs
+++ b/src/EFCore.Relational/Migrations/Operations/Builders/CreateTableBuilder.cs
@@ -157,6 +157,31 @@ public virtual OperationBuilder UniqueConstraint(
return new OperationBuilder(operation);
}
+ ///
+ /// Configures a check constraint on the table.
+ ///
+ /// The constraint name.
+ /// The sql expression used in the CHECK constraint.
+ /// The same builder so that multiple calls can be chained.
+ public virtual OperationBuilder CheckConstraint(
+ [NotNull] string name,
+ [NotNull] string constraintSql)
+ {
+ Check.NotEmpty(name, nameof(name));
+ Check.NotNull(constraintSql, nameof(constraintSql));
+
+ var operation = new CreateCheckConstraintOperation
+ {
+ Schema = Operation.Schema,
+ Table = Operation.Name,
+ Name = name,
+ ConstraintSql = constraintSql
+ };
+ Operation.CheckConstraints.Add(operation);
+
+ return new OperationBuilder(operation);
+ }
+
///
/// Annotates the operation with the given name/value pair.
///
diff --git a/src/EFCore.Relational/Migrations/Operations/CreateCheckConstraintOperation.cs b/src/EFCore.Relational/Migrations/Operations/CreateCheckConstraintOperation.cs
new file mode 100644
index 00000000000..686297c64d8
--- /dev/null
+++ b/src/EFCore.Relational/Migrations/Operations/CreateCheckConstraintOperation.cs
@@ -0,0 +1,37 @@
+// 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 JetBrains.Annotations;
+
+namespace Microsoft.EntityFrameworkCore.Migrations.Operations
+{
+ ///
+ /// A for creating a new check constraint.
+ ///
+ public class CreateCheckConstraintOperation : MigrationOperation
+ {
+ ///
+ /// The name of the check constraint.
+ ///
+ public virtual string Name { get; [param: NotNull] set; }
+
+ ///
+ /// The table of the check constraint.
+ ///
+ public virtual string Table { get; [param: NotNull] set; }
+
+ ///
+ /// The table schema that contains the check constraint, or null if the default schema should be used.
+ ///
+ public virtual string Schema { get; [param: CanBeNull] set; }
+
+ ///
+ /// The logical sql expression used in a CHECK constraint and returns TRUE or FALSE.
+ /// Sql used with CHECK constraints cannot reference another table
+ /// but can reference other columns in the same table for the same row.
+ /// The expression cannot reference an alias data type.
+ ///
+ public virtual string ConstraintSql { get; [param: NotNull] set; }
+ }
+}
diff --git a/src/EFCore.Relational/Migrations/Operations/CreateTableOperation.cs b/src/EFCore.Relational/Migrations/Operations/CreateTableOperation.cs
index bb1413b1312..0b17f09789a 100644
--- a/src/EFCore.Relational/Migrations/Operations/CreateTableOperation.cs
+++ b/src/EFCore.Relational/Migrations/Operations/CreateTableOperation.cs
@@ -40,5 +40,10 @@ public class CreateTableOperation : MigrationOperation
/// A list of for creating unique constraints in the table.
///
public virtual List UniqueConstraints { get; } = new List();
+
+ ///
+ /// A list of for creating check constraints in the table.
+ ///
+ public virtual List CheckConstraints { get; } = new List();
}
}
diff --git a/src/EFCore.Relational/Migrations/Operations/DropCheckConstraintOperation.cs b/src/EFCore.Relational/Migrations/Operations/DropCheckConstraintOperation.cs
new file mode 100644
index 00000000000..a8da9ade91b
--- /dev/null
+++ b/src/EFCore.Relational/Migrations/Operations/DropCheckConstraintOperation.cs
@@ -0,0 +1,28 @@
+// 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 JetBrains.Annotations;
+
+namespace Microsoft.EntityFrameworkCore.Migrations.Operations
+{
+ ///
+ /// A for dropping an existing check constraint.
+ ///
+ public class DropCheckConstraintOperation : MigrationOperation
+ {
+ ///
+ /// The name of the constraint.
+ ///
+ public virtual string Name { get; [param: NotNull] set; }
+
+ ///
+ /// The schema that contains the table, or null if the default schema should be used.
+ ///
+ public virtual string Schema { get; [param: CanBeNull] set; }
+
+ ///
+ /// The table that contains the constraint.
+ ///
+ public virtual string Table { get; [param: NotNull] set; }
+ }
+}
diff --git a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs
index 955825e8f18..a19b657d637 100644
--- a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs
+++ b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs
@@ -116,6 +116,12 @@ public static string BadSequenceType
public static string BadSequenceString
=> GetString("BadSequenceString");
+ ///
+ /// Unable to deserialize check constraint from model metadata. See inner exception for details.
+ ///
+ public static string BadCheckConstraintString
+ => GetString("BadCheckConstraintString");
+
///
/// The migration '{migrationName}' was not found.
///
diff --git a/src/EFCore.Relational/Properties/RelationalStrings.resx b/src/EFCore.Relational/Properties/RelationalStrings.resx
index cb5f22438c6..4bd97c54adf 100644
--- a/src/EFCore.Relational/Properties/RelationalStrings.resx
+++ b/src/EFCore.Relational/Properties/RelationalStrings.resx
@@ -211,6 +211,9 @@
Unable to deserialize sequence from model metadata. See inner exception for details.
+
+ Unable to deserialize check constraint from model metadata. See inner exception for details.
+
The migration '{migrationName}' was not found.
diff --git a/src/EFCore.Relational/RelationalEntityTypeBuilderExtensions.cs b/src/EFCore.Relational/RelationalEntityTypeBuilderExtensions.cs
index 0f56c1ddd56..eb77ca1e9c7 100644
--- a/src/EFCore.Relational/RelationalEntityTypeBuilderExtensions.cs
+++ b/src/EFCore.Relational/RelationalEntityTypeBuilderExtensions.cs
@@ -244,5 +244,31 @@ public static DiscriminatorBuilder HasDiscriminator()
.Relational(ConfigurationSource.Explicit).HasDiscriminator(propertyExpression.GetPropertyAccess()));
}
+
+ ///
+ /// 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.
+ /// A builder to further configure the check constraint.
+ public static EntityTypeBuilder HasCheckConstraint(
+ [NotNull] this EntityTypeBuilder entityTypeBuilder,
+ [NotNull] string name,
+ [NotNull] string constraintSql)
+ {
+ Check.NotNull(entityTypeBuilder, nameof(entityTypeBuilder));
+ Check.NotEmpty(name, nameof(name));
+
+ var relaionalEntityTypeBuilder = entityTypeBuilder.GetInfrastructure()
+ .Relational(ConfigurationSource.Explicit);
+
+ var tableName = relaionalEntityTypeBuilder.TableName;
+ var schema = relaionalEntityTypeBuilder.Schema;
+
+ entityTypeBuilder.Metadata.Model.Relational().GetOrAddCheckConstraint(name, constraintSql, tableName, schema);
+
+ return entityTypeBuilder;
+ }
}
}
diff --git a/src/EFCore.Sqlite.Core/Migrations/SqliteMigrationsSqlGenerator.cs b/src/EFCore.Sqlite.Core/Migrations/SqliteMigrationsSqlGenerator.cs
index 75f90aac9c6..ad52dea9462 100644
--- a/src/EFCore.Sqlite.Core/Migrations/SqliteMigrationsSqlGenerator.cs
+++ b/src/EFCore.Sqlite.Core/Migrations/SqliteMigrationsSqlGenerator.cs
@@ -424,6 +424,17 @@ protected override void Generate(AddUniqueConstraintOperation operation, IModel
=> throw new NotSupportedException(
SqliteStrings.InvalidMigrationOperation(operation.GetType().ShortDisplayName()));
+ ///
+ /// Throws since this operation requires table rebuilds, which
+ /// are not yet supported.
+ ///
+ /// The operation.
+ /// The target model which may be null if the operations exist without a model.
+ /// The command builder to use to build the commands.
+ protected override void Generate(CreateCheckConstraintOperation operation, IModel model, MigrationCommandListBuilder builder)
+ => throw new NotSupportedException(
+ SqliteStrings.InvalidMigrationOperation(operation.GetType().ShortDisplayName()));
+
///
/// Throws since this operation requires table rebuilds, which
/// are not yet supported.
@@ -468,6 +479,17 @@ protected override void Generate(DropUniqueConstraintOperation operation, IModel
=> throw new NotSupportedException(
SqliteStrings.InvalidMigrationOperation(operation.GetType().ShortDisplayName()));
+ ///
+ /// Throws since this operation requires table rebuilds, which
+ /// are not yet supported.
+ ///
+ /// The operation.
+ /// The target model which may be null if the operations exist without a model.
+ /// The command builder to use to build the commands.
+ protected override void Generate(DropCheckConstraintOperation operation, IModel model, MigrationCommandListBuilder builder)
+ => throw new NotSupportedException(
+ SqliteStrings.InvalidMigrationOperation(operation.GetType().ShortDisplayName()));
+
///
/// Throws since this operation requires table rebuilds, which
/// are not yet supported.
diff --git a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationOperationGeneratorTest.cs b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationOperationGeneratorTest.cs
index 53e238ce47f..e1741662ea7 100644
--- a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationOperationGeneratorTest.cs
+++ b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationOperationGeneratorTest.cs
@@ -403,6 +403,53 @@ public void AddUniqueConstraint_composite()
});
}
+ [Fact]
+ public void CreateCheckConstraint_required_args()
+ {
+ Test(
+ new CreateCheckConstraintOperation
+ {
+ Name = "CK_Post_AltId1_AltId2",
+ Table = "Post",
+ ConstraintSql = "AltId1 > AltId2"
+ },
+ "mb.CreateCheckConstraint(" + _eol +
+ " name: \"CK_Post_AltId1_AltId2\"," + _eol +
+ " table: \"Post\"," + _eol +
+ " constraintSql: \"AltId1 > AltId2\");",
+ o =>
+ {
+ Assert.Equal("CK_Post_AltId1_AltId2", o.Name);
+ Assert.Equal("Post", o.Table);
+ Assert.Equal("AltId1 > AltId2", o.ConstraintSql);
+ });
+ }
+
+ [Fact]
+ public void CreateCheckConstraint_all_args()
+ {
+ Test(
+ new CreateCheckConstraintOperation
+ {
+ Name = "CK_Post_AltId1_AltId2",
+ Schema = "dbo",
+ Table = "Post",
+ ConstraintSql = "AltId1 > AltId2"
+ },
+ "mb.CreateCheckConstraint(" + _eol +
+ " name: \"CK_Post_AltId1_AltId2\"," + _eol +
+ " schema: \"dbo\"," + _eol +
+ " table: \"Post\"," + _eol +
+ " constraintSql: \"AltId1 > AltId2\");",
+ o =>
+ {
+ Assert.Equal("CK_Post_AltId1_AltId2", o.Name);
+ Assert.Equal("dbo", o.Schema);
+ Assert.Equal("Post", o.Table);
+ Assert.Equal("AltId1 > AltId2", o.ConstraintSql);
+ });
+ }
+
[Fact]
public void AlterColumnOperation_required_args()
{
@@ -1541,6 +1588,112 @@ public void CreateTableOperation_UniqueConstraints_composite()
});
}
+ [Fact]
+ public void CreateTableOperation_CheckConstraints_required_args()
+ {
+ Test(
+ new CreateTableOperation
+ {
+ Name = "Post",
+ Columns =
+ {
+ new AddColumnOperation
+ {
+ Name = "AltId1",
+ ClrType = typeof(int)
+ },
+ new AddColumnOperation
+ {
+ Name = "AltId2",
+ ClrType = typeof(int)
+ }
+ },
+ CheckConstraints =
+ {
+ new CreateCheckConstraintOperation
+ {
+ Name = "CK_Post_AltId1_AltId2",
+ Table = "Post",
+ ConstraintSql = "AltId1 > AltId2"
+ }
+ }
+ },
+ "mb.CreateTable(" + _eol +
+ " name: \"Post\"," + _eol +
+ " columns: table => new" + _eol +
+ " {" + _eol +
+ " AltId1 = table.Column(nullable: false)," + _eol +
+ " AltId2 = table.Column(nullable: false)" + _eol +
+ " }," + _eol +
+ " constraints: table =>" + _eol +
+ " {" + _eol +
+ " table.CheckConstraint(\"CK_Post_AltId1_AltId2\", \"AltId1 > AltId2\");" + _eol +
+ " });",
+ o =>
+ {
+ Assert.Equal(1, o.CheckConstraints.Count);
+
+ Assert.Equal("CK_Post_AltId1_AltId2", o.CheckConstraints[0].Name);
+ Assert.Equal("Post", o.CheckConstraints[0].Table);
+ Assert.Equal("AltId1 > AltId2", o.CheckConstraints[0].ConstraintSql);
+ });
+ }
+
+ [Fact]
+ public void CreateTableOperation_ChecksConstraints_all_args()
+ {
+ Test(
+ new CreateTableOperation
+ {
+ Name = "Post",
+ Schema = "dbo",
+ Columns =
+ {
+ new AddColumnOperation
+ {
+ Name = "AltId1",
+ ClrType = typeof(int)
+ },
+ new AddColumnOperation
+ {
+ Name = "AltId2",
+ ClrType = typeof(int)
+ }
+ },
+ CheckConstraints =
+ {
+ new CreateCheckConstraintOperation
+ {
+ Name = "CK_Post_AltId1_AltId2",
+ Schema = "dbo",
+ Table = "Post",
+ ConstraintSql = "AltId1 > AltId2"
+ }
+ }
+ },
+ "mb.CreateTable(" + _eol +
+ " name: \"Post\"," + _eol +
+ " schema: \"dbo\"," + _eol +
+ " columns: table => new" + _eol +
+ " {" + _eol +
+ " AltId1 = table.Column(nullable: false)," + _eol +
+ " AltId2 = table.Column(nullable: false)" + _eol +
+ " }," + _eol +
+ " constraints: table =>" + _eol +
+ " {" + _eol +
+ " table.CheckConstraint(\"CK_Post_AltId1_AltId2\", \"AltId1 > AltId2\");" + _eol +
+ " });",
+ o =>
+ {
+ Assert.Equal(1, o.CheckConstraints.Count);
+
+ Assert.Equal("CK_Post_AltId1_AltId2", o.CheckConstraints[0].Name);
+ Assert.Equal("dbo", o.CheckConstraints[0].Schema);
+ Assert.Equal("Post", o.CheckConstraints[0].Table);
+ Assert.Equal("AltId1 > AltId2", o.CheckConstraints[0].ConstraintSql);
+ });
+ }
+
[Fact]
public void DropColumnOperation_required_args()
{
@@ -1823,6 +1976,47 @@ public void DropUniqueConstraintOperation_all_args()
});
}
+ [Fact]
+ public void DropCheckConstraintOperation_required_args()
+ {
+ Test(
+ new DropCheckConstraintOperation
+ {
+ Name = "CK_Post_AltId1_AltId2",
+ Table = "Post"
+ },
+ "mb.DropCheckConstraint(" + _eol +
+ " name: \"CK_Post_AltId1_AltId2\"," + _eol +
+ " table: \"Post\");",
+ o =>
+ {
+ Assert.Equal("CK_Post_AltId1_AltId2", o.Name);
+ Assert.Equal("Post", o.Table);
+ });
+ }
+
+ [Fact]
+ public void DropCheckConstraintOperation_all_args()
+ {
+ Test(
+ new DropCheckConstraintOperation
+ {
+ Name = "CK_Post_AltId1_AltId2",
+ Schema = "dbo",
+ Table = "Post"
+ },
+ "mb.DropCheckConstraint(" + _eol +
+ " name: \"CK_Post_AltId1_AltId2\"," + _eol +
+ " schema: \"dbo\"," + _eol +
+ " table: \"Post\");",
+ o =>
+ {
+ Assert.Equal("CK_Post_AltId1_AltId2", o.Name);
+ Assert.Equal("dbo", o.Schema);
+ Assert.Equal("Post", o.Table);
+ });
+ }
+
[Fact]
public void RenameColumnOperation_required_args()
{
diff --git a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs
index 5651d13378a..0d9357970e0 100644
--- a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs
+++ b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs
@@ -66,6 +66,7 @@ public void Test_new_annotations_handled_for_entity_types()
RelationalAnnotationNames.DefaultValue,
RelationalAnnotationNames.Name,
RelationalAnnotationNames.SequencePrefix,
+ RelationalAnnotationNames.CheckConstraints,
RelationalAnnotationNames.DefaultSchema,
RelationalAnnotationNames.Filter,
RelationalAnnotationNames.DbFunction,
@@ -128,6 +129,7 @@ public void Test_new_annotations_handled_for_properties()
RelationalAnnotationNames.DefaultSchema,
RelationalAnnotationNames.Name,
RelationalAnnotationNames.SequencePrefix,
+ RelationalAnnotationNames.CheckConstraints,
RelationalAnnotationNames.DiscriminatorProperty,
RelationalAnnotationNames.DiscriminatorValue,
RelationalAnnotationNames.Filter,
diff --git a/test/EFCore.Relational.Specification.Tests/MigrationSqlGeneratorTestBase.cs b/test/EFCore.Relational.Specification.Tests/MigrationSqlGeneratorTestBase.cs
index b0b89200b74..d812c283a2d 100644
--- a/test/EFCore.Relational.Specification.Tests/MigrationSqlGeneratorTestBase.cs
+++ b/test/EFCore.Relational.Specification.Tests/MigrationSqlGeneratorTestBase.cs
@@ -320,6 +320,17 @@ public virtual void AddUniqueConstraintOperation_without_name()
Columns = new[] { "SSN" }
});
+ [Fact]
+ public virtual void CreateCheckConstraintOperation_with_name()
+ => Generate(
+ new CreateCheckConstraintOperation
+ {
+ Table = "People",
+ Schema = "dbo",
+ Name = "CK_People_DriverLicense",
+ ConstraintSql = "DriverLicense_Number > 0"
+ });
+
[Fact]
public virtual void AlterColumnOperation()
=> Generate(
@@ -507,6 +518,13 @@ public virtual void CreateTableOperation()
Columns = new[] { "SSN" }
}
},
+ CheckConstraints =
+ {
+ new CreateCheckConstraintOperation
+ {
+ ConstraintSql = "SSN > 0"
+ }
+ },
ForeignKeys =
{
new AddForeignKeyOperation
@@ -586,6 +604,16 @@ public virtual void DropUniqueConstraintOperation()
Name = "AK_People_SSN"
});
+ [Fact]
+ public virtual void DropCheckConstraintOperation()
+ => Generate(
+ new DropCheckConstraintOperation
+ {
+ Table = "People",
+ Schema = "dbo",
+ Name = "CK_People_SSN"
+ });
+
[Fact]
public virtual void SqlOperation()
=> Generate(
diff --git a/test/EFCore.Relational.Tests/Metadata/RelationalBuilderExtensionsTest.cs b/test/EFCore.Relational.Tests/Metadata/RelationalBuilderExtensionsTest.cs
index 8b2469c8842..c6d969195ad 100644
--- a/test/EFCore.Relational.Tests/Metadata/RelationalBuilderExtensionsTest.cs
+++ b/test/EFCore.Relational.Tests/Metadata/RelationalBuilderExtensionsTest.cs
@@ -545,6 +545,43 @@ public void Can_set_table_and_schema_name_non_generic()
Assert.Equal("db0", entityType.Relational().Schema);
}
+ [Fact]
+ public void Can_create_check_constraint()
+ {
+ var modelBuilder = CreateConventionModelBuilder();
+
+ modelBuilder
+ .Entity()
+ .HasCheckConstraint("CK_Customer_AlternateId", "AlternateId > Id");
+
+ var checkConstraint = modelBuilder.Model.Relational().FindCheckConstraint("CK_Customer_AlternateId", "Customer");
+
+ Assert.NotNull(checkConstraint);
+ Assert.Equal("CK_Customer_AlternateId", checkConstraint.Name);
+ Assert.Equal("AlternateId > Id", checkConstraint.ConstraintSql);
+ Assert.Equal("Customer", checkConstraint.Table);
+ Assert.Equal(null, checkConstraint.Schema);
+ }
+
+ [Fact]
+ public void Can_create_check_constraint_with_schema_and_none_default_table_name()
+ {
+ var modelBuilder = CreateConventionModelBuilder();
+
+ modelBuilder
+ .Entity()
+ .ToTable("Customizer", "db0")
+ .HasCheckConstraint("CK_Customer_AlternateId", "AlternateId > Id");
+
+ var checkConstraint = modelBuilder.Model.Relational().FindCheckConstraint("CK_Customer_AlternateId", "Customizer", "db0");
+
+ Assert.NotNull(checkConstraint);
+ Assert.Equal("CK_Customer_AlternateId", checkConstraint.Name);
+ Assert.Equal("AlternateId > Id", checkConstraint.ConstraintSql);
+ Assert.Equal("Customizer", checkConstraint.Table);
+ Assert.Equal("db0", checkConstraint.Schema);
+ }
+
[Fact]
public void Can_set_discriminator_value_using_property_expression()
{
@@ -926,7 +963,7 @@ public void Can_create_named_sequence_with_specific_facets_using_nested_closure_
ValidateNamedSpecificSequence(sequence);
}
-
+
private static void ValidateNamedSpecificSequence(ISequence sequence)
{
Assert.Equal("Snook", sequence.Name);
diff --git a/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs b/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs
index 3e3990a5d33..5e40aa3607d 100644
--- a/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs
+++ b/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs
@@ -255,6 +255,7 @@ public void Create_table()
Assert.Null(createTableOperation.Columns.First(o => o.Name == "AltId").DefaultValue);
Assert.NotNull(createTableOperation.PrimaryKey);
Assert.Equal(1, createTableOperation.UniqueConstraints.Count);
+ Assert.Equal(0, createTableOperation.CheckConstraints.Count);
Assert.Equal(1, createTableOperation.ForeignKeys.Count);
Assert.IsType(upOps[2]);
@@ -723,6 +724,7 @@ public void Create_shared_table_with_two_types()
Assert.Equal(new[] { "Id", "MouseId", "BoneId" }, createTableOperation.Columns.Select(c => c.Name));
Assert.Equal(0, createTableOperation.ForeignKeys.Count);
Assert.Equal(0, createTableOperation.UniqueConstraints.Count);
+ Assert.Equal(0, createTableOperation.CheckConstraints.Count);
},
downOps =>
{
@@ -2279,6 +2281,149 @@ public void Alter_unique_constraint_columns()
});
}
+ [Fact]
+ public void Add_check_constraint()
+ {
+ Execute(
+ source => source.Entity(
+ "Flamingo",
+ x =>
+ {
+ x.ToTable("Flamingo", "dbo");
+ x.Property("Id");
+ x.Property("AlternateId");
+ }),
+ target => target.Entity(
+ "Flamingo",
+ x =>
+ {
+ x.ToTable("Flamingo", "dbo");
+ x.Property("Id");
+ x.Property("AlternateId");
+ x.HasCheckConstraint("CK_Flamingo_AlternateId", "AlternateId > Id");
+ }),
+ operations =>
+ {
+ Assert.Equal(1, operations.Count);
+
+ var operation = Assert.IsType(operations[0]);
+ Assert.Equal("dbo", operation.Schema);
+ Assert.Equal("Flamingo", operation.Table);
+ Assert.Equal("CK_Flamingo_AlternateId", operation.Name);
+ Assert.Equal("AlternateId > Id", operation.ConstraintSql);
+ });
+ }
+
+ [Fact]
+ public void Drop_check_constraint()
+ {
+ Execute(
+ source => source.Entity(
+ "Penguin",
+ x =>
+ {
+ x.ToTable("Penguin", "dbo");
+ x.Property("Id");
+ x.Property("AlternateId");
+ x.HasCheckConstraint("CK_Flamingo_AlternateId", "AlternateId > Id");
+ }),
+ target => target.Entity(
+ "Penguin",
+ x =>
+ {
+ x.ToTable("Penguin", "dbo");
+ x.Property("Id");
+ x.Property("AlternateId");
+ }),
+ operations =>
+ {
+ Assert.Equal(1, operations.Count);
+
+ var operation = Assert.IsType(operations[0]);
+ Assert.Equal("dbo", operation.Schema);
+ Assert.Equal("Penguin", operation.Table);
+ Assert.Equal("CK_Flamingo_AlternateId", operation.Name);
+ });
+ }
+
+ [Fact]
+ public void Rename_check_constraint()
+ {
+ Execute(
+ source => source.Entity(
+ "Pelican",
+ x =>
+ {
+ x.ToTable("Pelican", "dbo");
+ x.Property("Id");
+ x.Property("AlternateId");
+ x.HasCheckConstraint("CK_Flamingo_AlternateId", "AlternateId > Id");
+ }),
+ target => target.Entity(
+ "Pelican",
+ x =>
+ {
+ x.ToTable("Pelican", "dbo");
+ x.Property("Id");
+ x.Property("AlternateId");
+ x.HasCheckConstraint("CK_Flamingo", "AlternateId > Id");
+ }),
+ operations =>
+ {
+ Assert.Equal(2, operations.Count);
+
+ var dropOperation = Assert.IsType(operations[0]);
+ Assert.Equal("dbo", dropOperation.Schema);
+ Assert.Equal("Pelican", dropOperation.Table);
+ Assert.Equal("CK_Flamingo_AlternateId", dropOperation.Name);
+
+ var createOperation = Assert.IsType(operations[1]);
+ Assert.Equal("dbo", createOperation.Schema);
+ Assert.Equal("Pelican", createOperation.Table);
+ Assert.Equal("CK_Flamingo", createOperation.Name);
+ Assert.Equal("AlternateId > Id", createOperation.ConstraintSql);
+ });
+ }
+
+ [Fact]
+ public void Alter_check_constraint_expression()
+ {
+ Execute(
+ source => source.Entity(
+ "Rook",
+ x =>
+ {
+ x.ToTable("Rook", "dbo");
+ x.Property("Id");
+ x.Property("AlternateId");
+ x.HasCheckConstraint("CK_Flamingo_AlternateId", "AlternateId > Id");
+ }),
+ target => target.Entity(
+ "Rook",
+ x =>
+ {
+ x.ToTable("Rook", "dbo");
+ x.Property("Id");
+ x.Property("AlternateId");
+ x.HasCheckConstraint("CK_Flamingo_AlternateId", "AlternateId < Id");
+ }),
+ operations =>
+ {
+ Assert.Equal(2, operations.Count);
+
+ var dropOperation = Assert.IsType(operations[0]);
+ Assert.Equal("dbo", dropOperation.Schema);
+ Assert.Equal("Rook", dropOperation.Table);
+ Assert.Equal("CK_Flamingo_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("AlternateId < Id", createOperation.ConstraintSql);
+ });
+ }
+
[Fact]
public void Rename_primary_key()
{
diff --git a/test/EFCore.Relational.Tests/Migrations/MigrationSqlGeneratorTest.cs b/test/EFCore.Relational.Tests/Migrations/MigrationSqlGeneratorTest.cs
index e61fdbd476d..878b506f49d 100644
--- a/test/EFCore.Relational.Tests/Migrations/MigrationSqlGeneratorTest.cs
+++ b/test/EFCore.Relational.Tests/Migrations/MigrationSqlGeneratorTest.cs
@@ -128,6 +128,16 @@ public override void AddUniqueConstraintOperation_without_name()
Sql);
}
+ public override void CreateCheckConstraintOperation_with_name()
+ {
+ base.CreateCheckConstraintOperation_with_name();
+
+ Assert.Equal(
+ "ALTER TABLE \"dbo\".\"People\" ADD CONSTRAINT \"CK_People_DriverLicense\" CHECK (DriverLicense_Number > 0);"
+ + EOL,
+ Sql);
+ }
+
public override void AlterSequenceOperation_with_minValue_and_maxValue()
{
base.AlterSequenceOperation_with_minValue_and_maxValue();
@@ -212,6 +222,7 @@ public override void CreateTableOperation()
" \"SSN\" char(11) NULL," + EOL +
" PRIMARY KEY (\"Id\")," + EOL +
" UNIQUE (\"SSN\")," + EOL +
+ " CHECK (SSN > 0)," + EOL +
" FOREIGN KEY (\"EmployerId\") REFERENCES \"Companies\" (\"Id\")" + EOL +
");" + EOL,
Sql);
@@ -271,6 +282,15 @@ public override void DropUniqueConstraintOperation()
Sql);
}
+ public override void DropCheckConstraintOperation()
+ {
+ base.DropCheckConstraintOperation();
+
+ Assert.Equal(
+ "ALTER TABLE \"dbo\".\"People\" DROP CONSTRAINT \"CK_People_SSN\";" + EOL,
+ Sql);
+ }
+
public override void SqlOperation()
{
base.SqlOperation();
diff --git a/test/EFCore.Sqlite.FunctionalTests/SqliteMigrationSqlGeneratorTest.cs b/test/EFCore.Sqlite.FunctionalTests/SqliteMigrationSqlGeneratorTest.cs
index 6f9671e156a..7b6f68055c2 100644
--- a/test/EFCore.Sqlite.FunctionalTests/SqliteMigrationSqlGeneratorTest.cs
+++ b/test/EFCore.Sqlite.FunctionalTests/SqliteMigrationSqlGeneratorTest.cs
@@ -310,6 +310,12 @@ public override void AddUniqueConstraintOperation_without_name()
Assert.Equal(SqliteStrings.InvalidMigrationOperation(nameof(AddUniqueConstraintOperation)), ex.Message);
}
+ public override void CreateCheckConstraintOperation_with_name()
+ {
+ var ex = Assert.Throws(() => base.CreateCheckConstraintOperation_with_name());
+ Assert.Equal(SqliteStrings.InvalidMigrationOperation(nameof(CreateCheckConstraintOperation)), ex.Message);
+ }
+
public override void AlterColumnOperation()
{
var ex = Assert.Throws(() => base.AlterColumnOperation());
@@ -481,6 +487,12 @@ public override void DropUniqueConstraintOperation()
Assert.Equal(SqliteStrings.InvalidMigrationOperation(nameof(DropUniqueConstraintOperation)), ex.Message);
}
+ public override void DropCheckConstraintOperation()
+ {
+ var ex = Assert.Throws(() => base.DropCheckConstraintOperation());
+ Assert.Equal(SqliteStrings.InvalidMigrationOperation(nameof(DropCheckConstraintOperation)), ex.Message);
+ }
+
[Fact]
public virtual void CreateTableOperation_old_autoincrement_annotation()
{