diff --git a/src/EFCore.Design/Migrations/Design/CSharpMigrationOperationGenerator.cs b/src/EFCore.Design/Migrations/Design/CSharpMigrationOperationGenerator.cs
index 8eb1912c7db..e9b0ad2c888 100644
--- a/src/EFCore.Design/Migrations/Design/CSharpMigrationOperationGenerator.cs
+++ b/src/EFCore.Design/Migrations/Design/CSharpMigrationOperationGenerator.cs
@@ -186,6 +186,14 @@ protected virtual void Generate([NotNull] AddColumnOperation operation, [NotNull
.AppendLine(",")
.Append("computedColumnSql: ")
.Append(Code.Literal(operation.ComputedColumnSql));
+
+ if (operation.ComputedColumnIsStored != null)
+ {
+ builder
+ .AppendLine(",")
+ .Append("computedColumnIsStored: ")
+ .Append(Code.Literal(operation.ComputedColumnIsStored));
+ }
}
else if (operation.DefaultValue != null)
{
@@ -552,6 +560,14 @@ protected virtual void Generate([NotNull] AlterColumnOperation operation, [NotNu
.AppendLine(",")
.Append("computedColumnSql: ")
.Append(Code.Literal(operation.ComputedColumnSql));
+
+ if (operation.ComputedColumnIsStored != null)
+ {
+ builder
+ .AppendLine(",")
+ .Append("computedColumnIsStored: ")
+ .Append(Code.Literal(operation.ComputedColumnIsStored));
+ }
}
else if (operation.DefaultValue != null)
{
@@ -653,6 +669,14 @@ protected virtual void Generate([NotNull] AlterColumnOperation operation, [NotNu
.AppendLine(",")
.Append("oldComputedColumnSql: ")
.Append(Code.Literal(operation.OldColumn.ComputedColumnSql));
+
+ if (operation.ComputedColumnIsStored != null)
+ {
+ builder
+ .AppendLine(",")
+ .Append("oldComputedColumnIsStored: ")
+ .Append(Code.Literal(operation.OldColumn.ComputedColumnIsStored));
+ }
}
else if (operation.OldColumn.DefaultValue != null)
{
@@ -1152,6 +1176,13 @@ protected virtual void Generate([NotNull] CreateTableOperation operation, [NotNu
builder
.Append(", computedColumnSql: ")
.Append(Code.Literal(column.ComputedColumnSql));
+
+ if (column.ComputedColumnIsStored != null)
+ {
+ builder
+ .Append(", computedColumnIsStored: ")
+ .Append(Code.Literal(column.ComputedColumnIsStored));
+ }
}
else if (column.DefaultValue != null)
{
diff --git a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs
index 1a357290caf..97f92781596 100644
--- a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs
+++ b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs
@@ -552,11 +552,7 @@ protected virtual void GeneratePropertyAnnotations([NotNull] IProperty property,
nameof(RelationalPropertyBuilderExtensions.HasDefaultValueSql),
stringBuilder);
- GenerateFluentApiForAnnotation(
- ref annotations,
- RelationalAnnotationNames.ComputedColumnSql,
- nameof(RelationalPropertyBuilderExtensions.HasComputedColumnSql),
- stringBuilder);
+ GenerateFluentApiForComputedColumn(ref annotations, stringBuilder);
GenerateFluentApiForAnnotation(
ref annotations,
@@ -582,9 +578,7 @@ protected virtual void GeneratePropertyAnnotations([NotNull] IProperty property,
nameof(PropertyBuilder.HasMaxLength),
stringBuilder);
- GenerateFluentApiForPrecisionAndScale(
- ref annotations,
- stringBuilder);
+ GenerateFluentApiForPrecisionAndScale(ref annotations, stringBuilder);
GenerateFluentApiForAnnotation(
ref annotations,
@@ -1354,6 +1348,47 @@ protected virtual void GenerateFluentApiForPrecisionAndScale(
}
}
+ ///
+ /// Generates a Fluent API call for the computed column annotations.
+ ///
+ /// The list of annotations.
+ /// The builder code is added to.
+ protected virtual void GenerateFluentApiForComputedColumn(
+ [NotNull] ref List annotations,
+ [NotNull] IndentedStringBuilder stringBuilder)
+ {
+ var sql = annotations
+ .FirstOrDefault(a => a.Name == RelationalAnnotationNames.ComputedColumnSql);
+
+ if (sql is null)
+ {
+ return;
+ }
+
+ stringBuilder
+ .AppendLine()
+ .Append(".")
+ .Append(nameof(RelationalPropertyBuilderExtensions.HasComputedColumnSql))
+ .Append("(")
+ .Append(Code.UnknownLiteral(sql.Value));
+
+ var isStored = annotations
+ .FirstOrDefault(a => a.Name == RelationalAnnotationNames.ComputedColumnIsStored);
+
+ if (isStored != null)
+ {
+ stringBuilder
+ .Append(", ")
+ .Append(Code.UnknownLiteral(isStored.Value));
+
+ annotations.Remove(isStored);
+ }
+
+ stringBuilder.Append(")");
+
+ annotations.Remove(sql);
+ }
+
///
/// Generates code for an annotation.
///
diff --git a/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs b/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs
index 6ac3b3343d8..6de287bb1c1 100644
--- a/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs
+++ b/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs
@@ -643,6 +643,7 @@ private void GenerateProperty(IProperty property, bool useDataAnnotations)
RemoveAnnotation(ref annotations, RelationalAnnotationNames.Comment);
RemoveAnnotation(ref annotations, RelationalAnnotationNames.Collation);
RemoveAnnotation(ref annotations, RelationalAnnotationNames.ComputedColumnSql);
+ RemoveAnnotation(ref annotations, RelationalAnnotationNames.ComputedColumnIsStored);
RemoveAnnotation(ref annotations, RelationalAnnotationNames.IsFixedLength);
RemoveAnnotation(ref annotations, RelationalAnnotationNames.TableColumnMappings);
RemoveAnnotation(ref annotations, RelationalAnnotationNames.ViewColumnMappings);
@@ -742,7 +743,10 @@ private void GenerateProperty(IProperty property, bool useDataAnnotations)
{
lines.Add(
$".{nameof(RelationalPropertyBuilderExtensions.HasComputedColumnSql)}" +
- $"({_code.Literal(property.GetComputedColumnSql())})");
+ $"({_code.Literal(property.GetComputedColumnSql())}" +
+ (property.GetComputedColumnIsStored() is bool computedColumnIsStored
+ ? $", stored: {_code.Literal(computedColumnIsStored)})"
+ : ")"));
}
if (property.GetComment() != null)
diff --git a/src/EFCore.Design/Scaffolding/Internal/RelationalScaffoldingModelFactory.cs b/src/EFCore.Design/Scaffolding/Internal/RelationalScaffoldingModelFactory.cs
index 34aeea81d95..8b10e69dead 100644
--- a/src/EFCore.Design/Scaffolding/Internal/RelationalScaffoldingModelFactory.cs
+++ b/src/EFCore.Design/Scaffolding/Internal/RelationalScaffoldingModelFactory.cs
@@ -480,7 +480,7 @@ protected virtual PropertyBuilder VisitColumn([NotNull] EntityTypeBuilder builde
if (column.ComputedColumnSql != null)
{
- property.HasComputedColumnSql(column.ComputedColumnSql);
+ property.HasComputedColumnSql(column.ComputedColumnSql, column.ComputedColumnIsStored);
}
if (column.Comment != null)
diff --git a/src/EFCore.Relational/Extensions/RelationalPropertyBuilderExtensions.cs b/src/EFCore.Relational/Extensions/RelationalPropertyBuilderExtensions.cs
index 162a73e11b9..62112447e4d 100644
--- a/src/EFCore.Relational/Extensions/RelationalPropertyBuilderExtensions.cs
+++ b/src/EFCore.Relational/Extensions/RelationalPropertyBuilderExtensions.cs
@@ -5,6 +5,7 @@
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
+using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Utilities;
// ReSharper disable once CheckNamespace
@@ -360,16 +361,27 @@ public static bool CanSetDefaultValueSql(
///
/// The builder for the property being configured.
/// The SQL expression that computes values for the column.
+ ///
+ /// If true, the computed value is calculated on row modification and stored in the database like a regular column.
+ /// If false, the value is computed when the value is read, and does not occupy any actual storage.
+ /// null selects the database provider default.
+ ///
/// The same builder instance so that multiple calls can be chained.
public static PropertyBuilder HasComputedColumnSql(
[NotNull] this PropertyBuilder propertyBuilder,
- [CanBeNull] string sql)
+ [CanBeNull] string sql,
+ bool? stored = null)
{
Check.NotNull(propertyBuilder, nameof(propertyBuilder));
Check.NullButNotEmpty(sql, nameof(sql));
propertyBuilder.Metadata.SetComputedColumnSql(sql);
+ if (stored != null)
+ {
+ propertyBuilder.Metadata.SetComputedColumnIsStored(stored);
+ }
+
return propertyBuilder;
}
@@ -379,11 +391,17 @@ public static PropertyBuilder HasComputedColumnSql(
/// The type of the property being configured.
/// The builder for the property being configured.
/// The SQL expression that computes values for the column.
+ ///
+ /// If true, the computed value is calculated on row modification and stored in the database like a regular column.
+ /// If false, the value is computed when the value is read, and does not occupy any actual storage.
+ /// null selects the database provider default.
+ ///
/// The same builder instance so that multiple calls can be chained.
public static PropertyBuilder HasComputedColumnSql(
[NotNull] this PropertyBuilder propertyBuilder,
- [CanBeNull] string sql)
- => (PropertyBuilder)HasComputedColumnSql((PropertyBuilder)propertyBuilder, sql);
+ [CanBeNull] string sql,
+ bool? stored = null)
+ => (PropertyBuilder)HasComputedColumnSql((PropertyBuilder)propertyBuilder, sql, stored);
///
/// Configures the property to map to a computed column when targeting a relational database.
@@ -409,6 +427,33 @@ public static IConventionPropertyBuilder HasComputedColumnSql(
return propertyBuilder;
}
+ ///
+ /// Configures the property to map to a computed column of the given type when targeting a relational database.
+ ///
+ /// The builder for the property being configured.
+ ///
+ /// If true, the computed value is calculated on row modification and stored in the database like a regular column.
+ /// If false, the value is computed when the value is read, and does not occupy any actual storage.
+ /// null selects the database provider default.
+ ///
+ /// Indicates whether the configuration was specified using a data annotation.
+ ///
+ /// The same builder instance if the configuration was applied, null otherwise.
+ ///
+ public static IConventionPropertyBuilder IsStoredComputedColumn(
+ [NotNull] this IConventionPropertyBuilder propertyBuilder,
+ bool? stored,
+ bool fromDataAnnotation = false)
+ {
+ if (!propertyBuilder.CanSetIsStoredComputedColumn(stored, fromDataAnnotation))
+ {
+ return null;
+ }
+
+ propertyBuilder.Metadata.SetComputedColumnIsStored(stored, fromDataAnnotation);
+ return propertyBuilder;
+ }
+
///
/// Returns a value indicating whether the given computed value SQL expression can be set for the column.
///
@@ -425,6 +470,26 @@ public static bool CanSetComputedColumnSql(
Check.NullButNotEmpty(sql, nameof(sql)),
fromDataAnnotation);
+ ///
+ /// Returns a value indicating whether the given computed column type can be set for the column.
+ ///
+ /// The builder for the property being configured.
+ ///
+ /// If true, the computed value is calculated on row modification and stored in the database like a regular column.
+ /// If false, the value is computed when the value is read, and does not occupy any actual storage.
+ /// null selects the database provider default.
+ ///
+ /// Indicates whether the configuration was specified using a data annotation.
+ /// true if the given computed column type can be set for the column.
+ public static bool CanSetIsStoredComputedColumn(
+ [NotNull] this IConventionPropertyBuilder propertyBuilder,
+ bool? stored,
+ bool fromDataAnnotation = false)
+ => propertyBuilder.CanSetAnnotation(
+ RelationalAnnotationNames.ComputedColumnIsStored,
+ stored,
+ fromDataAnnotation);
+
///
///
/// Configures the default value for the column that the property maps
diff --git a/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs b/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs
index b4ff89d78a9..4a99b1b8e69 100644
--- a/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs
+++ b/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs
@@ -442,6 +442,67 @@ public static string SetComputedColumnSql(
public static ConfigurationSource? GetComputedColumnSqlConfigurationSource([NotNull] this IConventionProperty property)
=> property.FindAnnotation(RelationalAnnotationNames.ComputedColumnSql)?.GetConfigurationSource();
+ ///
+ /// Gets whether the value of the computed column this property is mapped to is stored in the database, or calculated when
+ /// it is read.
+ ///
+ /// The property.
+ ///
+ /// Whether the value of the computed column this property is mapped to is stored in the database,
+ /// or calculated when it is read.
+ ///
+ public static bool? GetComputedColumnIsStored([NotNull] this IProperty property)
+ {
+ var computedColumnIsStored = (bool?)property[RelationalAnnotationNames.ComputedColumnIsStored];
+ if (computedColumnIsStored != null)
+ {
+ return computedColumnIsStored;
+ }
+
+ var sharedTablePrincipalPrimaryKeyProperty = property.FindSharedRootPrimaryKeyProperty();
+ if (sharedTablePrincipalPrimaryKeyProperty != null)
+ {
+ return GetComputedColumnIsStored(sharedTablePrincipalPrimaryKeyProperty);
+ }
+
+ return null;
+ }
+
+ ///
+ /// Sets whether the value of the computed column this property is mapped to is stored in the database, or calculated when
+ /// it is read.
+ ///
+ /// The property.
+ /// The value to set.
+ public static void SetComputedColumnIsStored([NotNull] this IMutableProperty property, bool? value)
+ => property.SetOrRemoveAnnotation(
+ RelationalAnnotationNames.ComputedColumnIsStored,
+ value);
+
+ ///
+ /// Sets whether the value of the computed column this property is mapped to is stored in the database, or calculated when
+ /// it is read.
+ ///
+ /// The property.
+ /// The value to set.
+ /// Indicates whether the configuration was specified using a data annotation.
+ /// The configured value.
+ public static bool? SetComputedColumnIsStored(
+ [NotNull] this IConventionProperty property, bool? value, bool fromDataAnnotation = false)
+ {
+ property.SetOrRemoveAnnotation(RelationalAnnotationNames.ComputedColumnIsStored, value, fromDataAnnotation);
+
+ return value;
+ }
+
+ ///
+ /// Gets the for the computed value SQL expression.
+ ///
+ /// The property.
+ /// The for the computed value SQL expression.
+ public static ConfigurationSource? GetComputedColumnIsStoredConfigurationSource([NotNull] this IConventionProperty property)
+ => property.FindAnnotation(RelationalAnnotationNames.ComputedColumnIsStored)?.GetConfigurationSource();
+
///
/// Returns the object that is used as the default value for the column this property is mapped to.
///
diff --git a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs
index 7abda702622..549982fee58 100644
--- a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs
+++ b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs
@@ -489,6 +489,22 @@ protected virtual void ValidateCompatible(
currentComputedColumnSql));
}
+ var currentComputedColumnIsStored = property.GetComputedColumnIsStored();
+ var previousComputedColumnIsStored = duplicateProperty.GetComputedColumnIsStored();
+ if (currentComputedColumnIsStored != previousComputedColumnIsStored)
+ {
+ throw new InvalidOperationException(
+ RelationalStrings.DuplicateColumnNameComputedIsStoredMismatch(
+ duplicateProperty.DeclaringEntityType.DisplayName(),
+ duplicateProperty.Name,
+ property.DeclaringEntityType.DisplayName(),
+ property.Name,
+ columnName,
+ tableName,
+ previousComputedColumnIsStored,
+ currentComputedColumnIsStored));
+ }
+
var currentDefaultValue = property.GetDefaultValue();
var previousDefaultValue = duplicateProperty.GetDefaultValue();
if (!Equals(currentDefaultValue, previousDefaultValue))
diff --git a/src/EFCore.Relational/Metadata/IColumn.cs b/src/EFCore.Relational/Metadata/IColumn.cs
index a4b3bd89017..cfb402d8614 100644
--- a/src/EFCore.Relational/Metadata/IColumn.cs
+++ b/src/EFCore.Relational/Metadata/IColumn.cs
@@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.Linq;
+using Microsoft.EntityFrameworkCore.Metadata.Internal;
namespace Microsoft.EntityFrameworkCore.Metadata
{
@@ -83,6 +84,12 @@ public virtual object GetDefaultValue
///
public virtual string ComputedColumnSql => PropertyMappings.First().Property.GetComputedColumnSql();
+ ///
+ /// Returns whether the value of the computed column this property is mapped to is stored in the database, or calculated when
+ /// it is read.
+ ///
+ public virtual bool? ComputedColumnIsStored => PropertyMappings.First().Property.GetComputedColumnIsStored();
+
///
/// Comment for this column
///
diff --git a/src/EFCore.Relational/Metadata/RelationalAnnotationNames.cs b/src/EFCore.Relational/Metadata/RelationalAnnotationNames.cs
index da5038f0045..dcbebb14183 100644
--- a/src/EFCore.Relational/Metadata/RelationalAnnotationNames.cs
+++ b/src/EFCore.Relational/Metadata/RelationalAnnotationNames.cs
@@ -41,6 +41,11 @@ public static class RelationalAnnotationNames
///
public const string ComputedColumnSql = Prefix + "ComputedColumnSql";
+ ///
+ /// The name for computed column type annotations.
+ ///
+ public const string ComputedColumnIsStored = Prefix + "ComputedColumnIsStored";
+
///
/// The name for default value annotations.
///
diff --git a/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs b/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs
index 3cc9c89232a..031c284c126 100644
--- a/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs
+++ b/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs
@@ -899,6 +899,7 @@ private bool PropertyStructureEquals(IProperty source, IProperty target)
&& source.IsFixedLength() == target.IsFixedLength()
&& source.GetConfiguredColumnType() == target.GetConfiguredColumnType()
&& source.GetComputedColumnSql() == target.GetComputedColumnSql()
+ && source.GetComputedColumnIsStored() == target.GetComputedColumnIsStored()
&& Equals(GetDefaultValue(source), GetDefaultValue(target))
&& source.GetDefaultValueSql() == target.GetDefaultValueSql();
@@ -995,6 +996,7 @@ protected virtual IEnumerable Diff(
|| columnTypeChanged
|| source.DefaultValueSql != target.DefaultValueSql
|| source.ComputedColumnSql != target.ComputedColumnSql
+ || source.ComputedColumnIsStored != target.ComputedColumnIsStored
|| !Equals(GetDefaultValue(sourceProperty, GetValueConverter(sourceProperty, sourceTypeMapping)),
GetDefaultValue(targetProperty, GetValueConverter(sourceProperty, targetTypeMapping)))
|| source.Comment != target.Comment
@@ -1107,6 +1109,7 @@ private void Initialize(
columnOperation.DefaultValueSql = column.DefaultValueSql;
columnOperation.ComputedColumnSql = column.ComputedColumnSql;
+ columnOperation.ComputedColumnIsStored = column.ComputedColumnIsStored;
columnOperation.Comment = column.Comment;
columnOperation.Collation = column.Collation;
columnOperation.AddAnnotations(migrationsAnnotations);
diff --git a/src/EFCore.Relational/Migrations/MigrationBuilder.cs b/src/EFCore.Relational/Migrations/MigrationBuilder.cs
index 323678d19d0..34fc0362777 100644
--- a/src/EFCore.Relational/Migrations/MigrationBuilder.cs
+++ b/src/EFCore.Relational/Migrations/MigrationBuilder.cs
@@ -59,6 +59,7 @@ public MigrationBuilder([CanBeNull] string activeProvider)
/// The default value for the column.
/// The SQL expression to use for the column's default constraint.
/// The SQL expression to use to compute the column value.
+ /// Whether the value of the computed column is stored in the database or not.
/// Indicates whether or not the column is constrained to fixed-length data.
/// A comment to associate with the column.
/// A collation to apply to the column.
@@ -81,6 +82,7 @@ public virtual OperationBuilder AddColumn(
[CanBeNull] object defaultValue = null,
[CanBeNull] string defaultValueSql = null,
[CanBeNull] string computedColumnSql = null,
+ bool? computedColumnIsStored = null,
bool? fixedLength = null,
[CanBeNull] string comment = null,
[CanBeNull] string collation = null,
@@ -104,6 +106,7 @@ public virtual OperationBuilder AddColumn(
DefaultValue = defaultValue,
DefaultValueSql = defaultValueSql,
ComputedColumnSql = computedColumnSql,
+ ComputedColumnIsStored = computedColumnIsStored,
IsFixedLength = fixedLength,
Comment = comment,
Collation = collation,
@@ -325,6 +328,7 @@ public virtual OperationBuilder AddUniqueConstrain
/// The default value for the column.
/// The SQL expression to use for the column's default constraint.
/// The SQL expression to use to compute the column value.
+ /// Whether the value of the computed column is stored in the database or not.
///
/// The CLR type that the column was previously mapped to. Can be null, in which case previous value is considered unknown.
///
@@ -355,6 +359,7 @@ public virtual OperationBuilder AddUniqueConstrain
///
/// The previous SQL expression used to compute the column value. Can be null, in which case previous value is considered unknown.
///
+ /// Whether the value of the previous computed column was stored in the database or not.
/// Indicates whether or not the column is constrained to fixed-length data.
/// Indicates whether or not the column was previously constrained to fixed-length data.
/// A comment to associate with the column.
@@ -386,6 +391,7 @@ public virtual AlterOperationBuilder AlterColumn(
[CanBeNull] object defaultValue = null,
[CanBeNull] string defaultValueSql = null,
[CanBeNull] string computedColumnSql = null,
+ bool? computedColumnIsStored = null,
[CanBeNull] Type oldClrType = null,
[CanBeNull] string oldType = null,
bool? oldUnicode = null,
@@ -395,6 +401,7 @@ public virtual AlterOperationBuilder AlterColumn(
[CanBeNull] object oldDefaultValue = null,
[CanBeNull] string oldDefaultValueSql = null,
[CanBeNull] string oldComputedColumnSql = null,
+ bool? oldComputedColumnIsStored = null,
bool? fixedLength = null,
bool? oldFixedLength = null,
[CanBeNull] string comment = null,
@@ -423,6 +430,7 @@ public virtual AlterOperationBuilder AlterColumn(
DefaultValue = defaultValue,
DefaultValueSql = defaultValueSql,
ComputedColumnSql = computedColumnSql,
+ ComputedColumnIsStored = computedColumnIsStored,
IsFixedLength = fixedLength,
Comment = comment,
Collation = collation,
@@ -439,6 +447,7 @@ public virtual AlterOperationBuilder AlterColumn(
DefaultValue = oldDefaultValue,
DefaultValueSql = oldDefaultValueSql,
ComputedColumnSql = oldComputedColumnSql,
+ ComputedColumnIsStored = oldComputedColumnIsStored,
IsFixedLength = oldFixedLength,
Comment = oldComment,
Collation = oldCollation,
diff --git a/src/EFCore.Relational/Migrations/Operations/Builders/ColumnsBuilder.cs b/src/EFCore.Relational/Migrations/Operations/Builders/ColumnsBuilder.cs
index 52548a9b5bd..8be2b93c936 100644
--- a/src/EFCore.Relational/Migrations/Operations/Builders/ColumnsBuilder.cs
+++ b/src/EFCore.Relational/Migrations/Operations/Builders/ColumnsBuilder.cs
@@ -43,6 +43,7 @@ public ColumnsBuilder([NotNull] CreateTableOperation createTableOperation)
/// The default value for the column.
/// The SQL expression to use for the column's default constraint.
/// The SQL expression to use to compute the column value.
+ /// Whether the value of the computed column is stored in the database or not.
/// Indicates whether or not the column is constrained to fixed-length data.
/// A comment to be applied to the column.
/// A collation to be applied to the column.
@@ -59,6 +60,7 @@ public virtual OperationBuilder Column(
[CanBeNull] object defaultValue = null,
[CanBeNull] string defaultValueSql = null,
[CanBeNull] string computedColumnSql = null,
+ bool? computedColumnIsStored = null,
bool? fixedLength = null,
[CanBeNull] string comment = null,
[CanBeNull] string collation = null,
@@ -79,6 +81,7 @@ public virtual OperationBuilder Column(
DefaultValue = defaultValue,
DefaultValueSql = defaultValueSql,
ComputedColumnSql = computedColumnSql,
+ ComputedColumnIsStored = computedColumnIsStored,
IsFixedLength = fixedLength,
Comment = comment,
Collation = collation,
diff --git a/src/EFCore.Relational/Migrations/Operations/ColumnOperation.cs b/src/EFCore.Relational/Migrations/Operations/ColumnOperation.cs
index cc03d96e4d3..a4067cf1a9d 100644
--- a/src/EFCore.Relational/Migrations/Operations/ColumnOperation.cs
+++ b/src/EFCore.Relational/Migrations/Operations/ColumnOperation.cs
@@ -3,6 +3,7 @@
using System;
using JetBrains.Annotations;
+using Microsoft.EntityFrameworkCore.Metadata.Internal;
namespace Microsoft.EntityFrameworkCore.Migrations.Operations
{
@@ -81,7 +82,13 @@ public class ColumnOperation : MigrationOperation
public virtual string ComputedColumnSql { get; [param: CanBeNull] set; }
///
- /// Comment for this column.
+ /// Whether the value of the computed column this property is mapped to is stored in the database, or calculated when
+ /// it is read.
+ ///
+ public virtual bool? ComputedColumnIsStored { get; set; }
+
+ ///
+ /// Comment for this column
///
public virtual string Comment { get; [param: CanBeNull] set; }
diff --git a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs
index 84253b0296a..63cd2e8728e 100644
--- a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs
+++ b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs
@@ -284,6 +284,14 @@ public static string DuplicateColumnNameComputedSqlMismatch([CanBeNull] object e
GetString("DuplicateColumnNameComputedSqlMismatch", nameof(entityType1), nameof(property1), nameof(entityType2), nameof(property2), nameof(columnName), nameof(table), nameof(value1), nameof(value2)),
entityType1, property1, entityType2, property2, columnName, table, value1, value2);
+ ///
+ /// '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}' but are configured to use different stored computed column settings ('{value1}' and '{value2}').
+ ///
+ public static string DuplicateColumnNameComputedIsStoredMismatch([CanBeNull] object entityType1, [CanBeNull] object property1, [CanBeNull] object entityType2, [CanBeNull] object property2, [CanBeNull] object columnName, [CanBeNull] object table, [CanBeNull] object value1, [CanBeNull] object value2)
+ => string.Format(
+ GetString("DuplicateColumnNameComputedIsStoredMismatch", nameof(entityType1), nameof(property1), nameof(entityType2), nameof(property2), nameof(columnName), nameof(table), nameof(value1), nameof(value2)),
+ entityType1, property1, entityType2, property2, columnName, table, value1, value2);
+
///
/// '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}' but are configured to use different default values ('{value1}' and '{value2}').
///
diff --git a/src/EFCore.Relational/Properties/RelationalStrings.resx b/src/EFCore.Relational/Properties/RelationalStrings.resx
index 9d7e535ded0..b0fd389ec80 100644
--- a/src/EFCore.Relational/Properties/RelationalStrings.resx
+++ b/src/EFCore.Relational/Properties/RelationalStrings.resx
@@ -353,6 +353,9 @@
'{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}' but are configured to use different computed values ('{value1}' and '{value2}').
+
+ '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}' but are configured to use different stored computed column settings ('{value1}' and '{value2}').
+
'{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}' but are configured to use different default values ('{value1}' and '{value2}').
diff --git a/src/EFCore.Relational/Scaffolding/Metadata/DatabaseColumn.cs b/src/EFCore.Relational/Scaffolding/Metadata/DatabaseColumn.cs
index b088ddc920b..47ff96a103d 100644
--- a/src/EFCore.Relational/Scaffolding/Metadata/DatabaseColumn.cs
+++ b/src/EFCore.Relational/Scaffolding/Metadata/DatabaseColumn.cs
@@ -4,6 +4,7 @@
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Metadata.Internal;
#nullable enable
@@ -47,10 +48,17 @@ public DatabaseColumn([NotNull] DatabaseTable table, [NotNull] string name, [Not
public virtual string? DefaultValueSql { get; [param: CanBeNull] set; }
///
- /// The SQL expression that computes the value of the column, or null if this is not a computed column.
+ /// Whether the value of the computed column this property is mapped to is stored in the database, or calculated when
+ /// it is read.
///
public virtual string? ComputedColumnSql { get; [param: CanBeNull] set; }
+ ///
+ /// Whether the value of the computed column this property is mapped to is stored in the database, or calculated when
+ /// it is read.
+ ///
+ public virtual bool? ComputedColumnIsStored { get; set; }
+
///
/// The column comment, or null if none is set.
///
diff --git a/src/EFCore.SqlServer/Internal/SqlServerLoggerExtensions.cs b/src/EFCore.SqlServer/Internal/SqlServerLoggerExtensions.cs
index 8fc5b9c63e3..a04ae24ae5b 100644
--- a/src/EFCore.SqlServer/Internal/SqlServerLoggerExtensions.cs
+++ b/src/EFCore.SqlServer/Internal/SqlServerLoggerExtensions.cs
@@ -152,7 +152,8 @@ public static void ColumnFound(
bool nullable,
bool identity,
[CanBeNull] string defaultValue,
- [CanBeNull] string computedValue)
+ [CanBeNull] string computedValue,
+ bool? computedValueIsStored)
{
var definition = SqlServerResources.LogFoundColumn(diagnostics);
@@ -174,7 +175,8 @@ public static void ColumnFound(
nullable,
identity,
defaultValue,
- computedValue));
+ computedValue,
+ computedValueIsStored));
}
// No DiagnosticsSource events because these are purely design-time messages
diff --git a/src/EFCore.SqlServer/Migrations/SqlServerMigrationsSqlGenerator.cs b/src/EFCore.SqlServer/Migrations/SqlServerMigrationsSqlGenerator.cs
index 7f75332d437..3e8ef3e7e9d 100644
--- a/src/EFCore.SqlServer/Migrations/SqlServerMigrationsSqlGenerator.cs
+++ b/src/EFCore.SqlServer/Migrations/SqlServerMigrationsSqlGenerator.cs
@@ -11,6 +11,7 @@
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Migrations.Operations;
using Microsoft.EntityFrameworkCore.SqlServer.Internal;
using Microsoft.EntityFrameworkCore.SqlServer.Metadata.Internal;
@@ -243,6 +244,7 @@ protected override void Generate(
DefaultValue = operation.DefaultValue,
DefaultValueSql = operation.DefaultValueSql,
ComputedColumnSql = operation.ComputedColumnSql,
+ ComputedColumnIsStored = operation.ComputedColumnIsStored,
IsFixedLength = operation.IsFixedLength
};
addColumnOperation.AddAnnotations(operation.GetAnnotations());
@@ -1412,6 +1414,11 @@ protected override void ComputedColumnDefinition(
.Append(" AS ")
.Append(operation.ComputedColumnSql);
+ if (operation.ComputedColumnIsStored == true)
+ {
+ builder.Append(" PERSISTED");
+ }
+
if (operation.Collation != null)
{
builder
diff --git a/src/EFCore.SqlServer/Properties/SqlServerStrings.resx b/src/EFCore.SqlServer/Properties/SqlServerStrings.resx
index 054b057c58f..d57bff836ba 100644
--- a/src/EFCore.SqlServer/Properties/SqlServerStrings.resx
+++ b/src/EFCore.SqlServer/Properties/SqlServerStrings.resx
@@ -167,8 +167,8 @@
Debug SqlServerEventId.TypeAliasFound string string
- Found column with table: {tableName}, column name: {columnName}, ordinal: {ordinal}, data type: {dataType}, maximum length: {maxLength}, precision: {precision}, scale: {scale}, nullable: {isNullable}, identity: {isIdentity}, default value: {defaultValue}, computed value: {computedValue}
- Debug SqlServerEventId.ColumnFound string string int string int int int bool bool string string
+ Found column with table: {tableName}, column name: {columnName}, ordinal: {ordinal}, data type: {dataType}, maximum length: {maxLength}, precision: {precision}, scale: {scale}, nullable: {isNullable}, identity: {isIdentity}, default value: {defaultValue}, computed value: {computedValue}, computed value is stored: {computedValueIsStored}
+ Debug SqlServerEventId.ColumnFound string string int string int int int bool bool string string bool?
Found foreign key on table: {tableName}, name: {foreignKeyName}, principal table: {principalTableName}, delete action: {deleteAction}.
diff --git a/src/EFCore.SqlServer/Scaffolding/Internal/SqlServerDatabaseModelFactory.cs b/src/EFCore.SqlServer/Scaffolding/Internal/SqlServerDatabaseModelFactory.cs
index df7caeeb86d..c36014baf66 100644
--- a/src/EFCore.SqlServer/Scaffolding/Internal/SqlServerDatabaseModelFactory.cs
+++ b/src/EFCore.SqlServer/Scaffolding/Internal/SqlServerDatabaseModelFactory.cs
@@ -13,6 +13,7 @@
using Microsoft.Data.SqlClient;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Scaffolding;
using Microsoft.EntityFrameworkCore.Scaffolding.Metadata;
@@ -613,6 +614,7 @@ private void GetColumns(
[c].[is_identity],
[dc].[definition] AS [default_sql],
[cc].[definition] AS [computed_sql],
+ [cc].[is_persisted] AS [computed_is_persisted],
CAST([e].[value] AS nvarchar(MAX)) AS [comment],
[c].[collation_name]
FROM
@@ -673,6 +675,7 @@ UNION ALL
var isIdentity = dataRecord.GetValueOrDefault("is_identity");
var defaultValue = dataRecord.GetValueOrDefault("default_sql");
var computedValue = dataRecord.GetValueOrDefault("computed_sql");
+ var computedIsPersisted = dataRecord.GetValueOrDefault("computed_is_persisted");
var comment = dataRecord.GetValueOrDefault("comment");
var collation = dataRecord.GetValueOrDefault("collation_name");
@@ -687,7 +690,8 @@ UNION ALL
nullable,
isIdentity,
defaultValue,
- computedValue);
+ computedValue,
+ computedIsPersisted);
string storeType;
string systemTypeName;
@@ -711,6 +715,7 @@ UNION ALL
IsNullable = nullable,
DefaultValueSql = defaultValue,
ComputedColumnSql = computedValue,
+ ComputedColumnIsStored = computedIsPersisted,
Comment = comment,
Collation = collation == databaseCollation ? null : collation,
ValueGenerated = isIdentity
diff --git a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationOperationGeneratorTest.cs b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationOperationGeneratorTest.cs
index 4bc3da4d6c8..01a10a1f8f6 100644
--- a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationOperationGeneratorTest.cs
+++ b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationOperationGeneratorTest.cs
@@ -167,7 +167,8 @@ public void AddColumnOperation_ComputedExpression()
Name = "Id",
Table = "Post",
ClrType = typeof(int),
- ComputedColumnSql = "1"
+ ComputedColumnSql = "1",
+ ComputedColumnIsStored = true
},
"mb.AddColumn("
+ _eol
@@ -177,13 +178,16 @@ public void AddColumnOperation_ComputedExpression()
+ _eol
+ " nullable: false,"
+ _eol
- + " computedColumnSql: \"1\");",
+ + " computedColumnSql: \"1\","
+ + _eol
+ + " computedColumnIsStored: true);",
o =>
{
Assert.Equal("Id", o.Name);
Assert.Equal("Post", o.Table);
Assert.Equal(typeof(int), o.ClrType);
Assert.Equal("1", o.ComputedColumnSql);
+ Assert.True(o.ComputedColumnIsStored);
});
}
@@ -734,7 +738,8 @@ public void AlterColumnOperation_computedColumnSql()
Name = "Id",
Table = "Post",
ClrType = typeof(int),
- ComputedColumnSql = "1"
+ ComputedColumnSql = "1",
+ ComputedColumnIsStored = true
},
"mb.AlterColumn("
+ _eol
@@ -744,12 +749,15 @@ public void AlterColumnOperation_computedColumnSql()
+ _eol
+ " nullable: false,"
+ _eol
- + " computedColumnSql: \"1\");",
+ + " computedColumnSql: \"1\","
+ + _eol
+ + " computedColumnIsStored: true);",
o =>
{
Assert.Equal("Id", o.Name);
Assert.Equal("Post", o.Table);
Assert.Equal("1", o.ComputedColumnSql);
+ Assert.True(o.ComputedColumnIsStored);
Assert.Equal(typeof(int), o.ClrType);
Assert.Null(o.ColumnType);
Assert.Null(o.IsUnicode);
@@ -771,6 +779,7 @@ public void AlterColumnOperation_computedColumnSql()
Assert.Null(o.OldColumn.DefaultValue);
Assert.Null(o.OldColumn.DefaultValueSql);
Assert.Null(o.OldColumn.ComputedColumnSql);
+ Assert.Null(o.OldColumn.ComputedColumnIsStored);
});
}
@@ -1303,7 +1312,8 @@ public void CreateTableOperation_Columns_computedColumnSql()
Name = "Id",
Table = "Post",
ClrType = typeof(int),
- ComputedColumnSql = "1"
+ ComputedColumnSql = "1",
+ ComputedColumnIsStored = true
}
}
},
@@ -1315,7 +1325,7 @@ public void CreateTableOperation_Columns_computedColumnSql()
+ _eol
+ " {"
+ _eol
- + " Id = table.Column(nullable: false, computedColumnSql: \"1\")"
+ + " Id = table.Column(nullable: false, computedColumnSql: \"1\", computedColumnIsStored: true)"
+ _eol
+ " },"
+ _eol
@@ -1332,6 +1342,7 @@ public void CreateTableOperation_Columns_computedColumnSql()
Assert.Equal("Post", o.Columns[0].Table);
Assert.Equal(typeof(int), o.Columns[0].ClrType);
Assert.Equal("1", o.Columns[0].ComputedColumnSql);
+ Assert.True(o.Columns[0].ComputedColumnIsStored);
});
}
diff --git a/test/EFCore.Relational.Specification.Tests/MigrationsTestBase.cs b/test/EFCore.Relational.Specification.Tests/MigrationsTestBase.cs
index 7e42cab13a8..dd759190fea 100644
--- a/test/EFCore.Relational.Specification.Tests/MigrationsTestBase.cs
+++ b/test/EFCore.Relational.Specification.Tests/MigrationsTestBase.cs
@@ -345,8 +345,11 @@ public virtual Task Add_column_with_defaultValueSql()
Assert.Contains("2", sumColumn.DefaultValueSql);
});
- [ConditionalFact]
- public virtual Task Add_column_with_computedSql()
+ [ConditionalTheory]
+ [InlineData(true)]
+ [InlineData(false)]
+ [InlineData(null)]
+ public virtual Task Add_column_with_computedSql(bool? computedColumnStored)
=> Test(
builder => builder.Entity(
"People", e =>
@@ -356,13 +359,16 @@ public virtual Task Add_column_with_computedSql()
e.Property("Y");
}),
builder => { },
- builder => builder.Entity("People").Property("Sum").HasComputedColumnSql($"{DelimitIdentifier("X")} + {DelimitIdentifier("Y")}"),
+ builder => builder.Entity("People").Property("Sum")
+ .HasComputedColumnSql($"{DelimitIdentifier("X")} + {DelimitIdentifier("Y")}", computedColumnStored),
model =>
{
var table = Assert.Single(model.Tables);
var sumColumn = Assert.Single(table.Columns, c => c.Name == "Sum");
Assert.Contains("X", sumColumn.ComputedColumnSql);
Assert.Contains("Y", sumColumn.ComputedColumnSql);
+ if (computedColumnStored != null)
+ Assert.Equal(computedColumnStored, sumColumn.ComputedColumnIsStored);
});
// TODO: Check this out
@@ -600,8 +606,11 @@ public virtual Task Alter_column_make_required_with_composite_index()
Assert.Contains(table.Columns.Single(c => c.Name == "LastName"), index.Columns);
});
- [ConditionalFact]
- public virtual Task Alter_column_make_computed()
+ [ConditionalTheory]
+ [InlineData(true)]
+ [InlineData(false)]
+ [InlineData(null)]
+ public virtual Task Alter_column_make_computed(bool? computedColumnStored)
=> Test(
builder => builder.Entity(
"People", e =>
@@ -611,7 +620,8 @@ public virtual Task Alter_column_make_computed()
e.Property("Y");
}),
builder => builder.Entity("People").Property("Sum"),
- builder => builder.Entity("People").Property("Sum").HasComputedColumnSql($"{DelimitIdentifier("X")} + {DelimitIdentifier("Y")}"),
+ builder => builder.Entity("People").Property("Sum")
+ .HasComputedColumnSql($"{DelimitIdentifier("X")} + {DelimitIdentifier("Y")}", computedColumnStored),
model =>
{
var table = Assert.Single(model.Tables);
@@ -619,6 +629,8 @@ public virtual Task Alter_column_make_computed()
Assert.Contains("X", sumColumn.ComputedColumnSql);
Assert.Contains("Y", sumColumn.ComputedColumnSql);
Assert.Contains("+", sumColumn.ComputedColumnSql);
+ if (computedColumnStored != null)
+ Assert.Equal(computedColumnStored, sumColumn.ComputedColumnIsStored);
});
[ConditionalFact]
@@ -643,6 +655,28 @@ public virtual Task Alter_column_change_computed()
Assert.Contains("-", sumColumn.ComputedColumnSql);
});
+ [ConditionalFact]
+ public virtual Task Alter_column_change_computed_type()
+ => Test(
+ builder => builder.Entity(
+ "People", e =>
+ {
+ e.Property("Id");
+ e.Property("X");
+ e.Property("Y");
+ e.Property("Sum");
+ }),
+ builder => builder.Entity("People").Property("Sum")
+ .HasComputedColumnSql($"{DelimitIdentifier("X")} + {DelimitIdentifier("Y")}", stored: false),
+ builder => builder.Entity("People").Property("Sum")
+ .HasComputedColumnSql($"{DelimitIdentifier("X")} + {DelimitIdentifier("Y")}", stored: true),
+ model =>
+ {
+ var table = Assert.Single(model.Tables);
+ var sumColumn = Assert.Single(table.Columns, c => c.Name == "Sum");
+ Assert.True(sumColumn.ComputedColumnIsStored);
+ });
+
[ConditionalFact]
public virtual Task Alter_column_add_comment()
=> Test(
diff --git a/test/EFCore.SqlServer.FunctionalTests/MigrationsSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/MigrationsSqlServerTest.cs
index 3ac2d0678ca..8318bee4e62 100644
--- a/test/EFCore.SqlServer.FunctionalTests/MigrationsSqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/MigrationsSqlServerTest.cs
@@ -328,12 +328,14 @@ public override async Task Add_column_with_defaultValueSql()
@"ALTER TABLE [People] ADD [Sum] int NOT NULL DEFAULT (1 + 2);");
}
- public override async Task Add_column_with_computedSql()
+ public override async Task Add_column_with_computedSql(bool? computedColumnStored)
{
- await base.Add_column_with_computedSql();
+ await base.Add_column_with_computedSql(computedColumnStored);
+
+ var computedColumnTypeSql = computedColumnStored == true ? " PERSISTED" : "";
AssertSql(
- @"ALTER TABLE [People] ADD [Sum] AS [X] + [Y];");
+ @$"ALTER TABLE [People] ADD [Sum] AS [X] + [Y]{computedColumnTypeSql};");
}
public override async Task Add_column_with_required()
@@ -513,20 +515,21 @@ FROM [sys].[default_constraints] [d]
CREATE INDEX [IX_People_FirstName_LastName] ON [People] ([FirstName], [LastName]);");
}
- [ConditionalFact]
- public override async Task Alter_column_make_computed()
+ public override async Task Alter_column_make_computed(bool? computedColumnStored)
{
- await base.Alter_column_make_computed();
+ await base.Alter_column_make_computed(computedColumnStored);
+
+ var computedColumnTypeSql = computedColumnStored == true ? " PERSISTED" : "";
AssertSql(
- @"DECLARE @var0 sysname;
+ $@"DECLARE @var0 sysname;
SELECT @var0 = [d].[name]
FROM [sys].[default_constraints] [d]
INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id]
WHERE ([d].[parent_object_id] = OBJECT_ID(N'[People]') AND [c].[name] = N'Sum');
IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];');
ALTER TABLE [People] DROP COLUMN [Sum];
-ALTER TABLE [People] ADD [Sum] AS [X] + [Y];");
+ALTER TABLE [People] ADD [Sum] AS [X] + [Y]{computedColumnTypeSql};");
}
public override async Task Alter_column_change_computed()
@@ -544,6 +547,21 @@ FROM [sys].[default_constraints] [d]
ALTER TABLE [People] ADD [Sum] AS [X] - [Y];");
}
+ public override async Task Alter_column_change_computed_type()
+ {
+ await base.Alter_column_change_computed_type();
+
+ AssertSql(
+ @"DECLARE @var0 sysname;
+SELECT @var0 = [d].[name]
+FROM [sys].[default_constraints] [d]
+INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id]
+WHERE ([d].[parent_object_id] = OBJECT_ID(N'[People]') AND [c].[name] = N'Sum');
+IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];');
+ALTER TABLE [People] DROP COLUMN [Sum];
+ALTER TABLE [People] ADD [Sum] AS [X] + [Y] PERSISTED;");
+ }
+
[ConditionalFact]
public override async Task Alter_column_add_comment()
{
diff --git a/test/EFCore.SqlServer.FunctionalTests/Scaffolding/SqlServerDatabaseModelFactoryTest.cs b/test/EFCore.SqlServer.FunctionalTests/Scaffolding/SqlServerDatabaseModelFactoryTest.cs
index ab2e9bee668..69b5c98b7c2 100644
--- a/test/EFCore.SqlServer.FunctionalTests/Scaffolding/SqlServerDatabaseModelFactoryTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/Scaffolding/SqlServerDatabaseModelFactoryTest.cs
@@ -1334,7 +1334,8 @@ FixedDefaultValue datetime2 NOT NULL DEFAULT ('October 20, 2015 11am'),
ComputedValue AS GETDATE(),
A int NOT NULL,
B int NOT NULL,
- SumOfAAndB AS A + B PERSISTED,
+ SumOfAAndB AS A + B,
+ SumOfAAndBPersisted AS A + B PERSISTED,
);",
Enumerable.Empty(),
Enumerable.Empty(),
@@ -1342,14 +1343,23 @@ ComputedValue AS GETDATE(),
{
var columns = dbModel.Tables.Single().Columns;
- Assert.Equal("('October 20, 2015 11am')", columns.Single(c => c.Name == "FixedDefaultValue").DefaultValueSql);
- Assert.Null(columns.Single(c => c.Name == "FixedDefaultValue").ComputedColumnSql);
+ var fixedDefaultValue = columns.Single(c => c.Name == "FixedDefaultValue");
+ Assert.Equal("('October 20, 2015 11am')", fixedDefaultValue.DefaultValueSql);
+ Assert.Null(fixedDefaultValue.ComputedColumnSql);
- Assert.Null(columns.Single(c => c.Name == "ComputedValue").DefaultValueSql);
- Assert.Equal("(getdate())", columns.Single(c => c.Name == "ComputedValue").ComputedColumnSql);
+ var computedValue = columns.Single(c => c.Name == "ComputedValue");
+ Assert.Null(computedValue.DefaultValueSql);
+ Assert.Equal("(getdate())", computedValue.ComputedColumnSql);
- Assert.Null(columns.Single(c => c.Name == "SumOfAAndB").DefaultValueSql);
- Assert.Equal("([A]+[B])", columns.Single(c => c.Name == "SumOfAAndB").ComputedColumnSql);
+ var sumOfAAndB = columns.Single(c => c.Name == "SumOfAAndB");
+ Assert.Null(sumOfAAndB.DefaultValueSql);
+ Assert.Equal("([A]+[B])", sumOfAAndB.ComputedColumnSql);
+ Assert.False(sumOfAAndB.ComputedColumnIsStored);
+
+ var sumOfAAndBPersisted = columns.Single(c => c.Name == "SumOfAAndBPersisted");
+ Assert.Null(sumOfAAndBPersisted.DefaultValueSql);
+ Assert.Equal("([A]+[B])", sumOfAAndBPersisted.ComputedColumnSql);
+ Assert.True(sumOfAAndBPersisted.ComputedColumnIsStored);
},
"DROP TABLE DefaultComputedValues;");
}
diff --git a/test/EFCore.Sqlite.FunctionalTests/MigrationsSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/MigrationsSqliteTest.cs
index 995c584abb0..adbc99d98bc 100644
--- a/test/EFCore.Sqlite.FunctionalTests/MigrationsSqliteTest.cs
+++ b/test/EFCore.Sqlite.FunctionalTests/MigrationsSqliteTest.cs
@@ -212,8 +212,10 @@ public override async Task Add_column_with_defaultValueSql()
Assert.Contains("Cannot add a column with non-constant default", ex.Message);
}
- public override Task Add_column_with_computedSql()
- => AssertNotSupportedAsync(base.Add_column_with_computedSql, SqliteStrings.ComputedColumnsNotSupported);
+ public override Task Add_column_with_computedSql(bool? computedColumnStored)
+ => AssertNotSupportedAsync(
+ () => base.Add_column_with_computedSql(computedColumnStored),
+ SqliteStrings.ComputedColumnsNotSupported);
public override async Task Add_column_with_max_length()
{
@@ -272,12 +274,17 @@ public override Task Alter_column_make_required_with_composite_index()
=> AssertNotSupportedAsync(
base.Alter_column_make_required_with_composite_index, SqliteStrings.InvalidMigrationOperation("AlterColumnOperation"));
- public override Task Alter_column_make_computed()
- => AssertNotSupportedAsync(base.Alter_column_make_computed, SqliteStrings.InvalidMigrationOperation("AlterColumnOperation"));
+ public override Task Alter_column_make_computed(bool? computedColumnStored)
+ => AssertNotSupportedAsync(
+ () => base.Alter_column_make_computed(computedColumnStored),
+ SqliteStrings.InvalidMigrationOperation("AlterColumnOperation"));
public override Task Alter_column_change_computed()
=> AssertNotSupportedAsync(base.Alter_column_change_computed, SqliteStrings.ComputedColumnsNotSupported);
+ public override Task Alter_column_change_computed_type()
+ => AssertNotSupportedAsync(base.Alter_column_change_computed, SqliteStrings.ComputedColumnsNotSupported);
+
public override Task Alter_column_add_comment()
=> AssertNotSupportedAsync(base.Alter_column_add_comment, SqliteStrings.InvalidMigrationOperation("AlterColumnOperation"));