Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

SQLite RevEng: Include computed columns #21588

Merged
merged 1 commit into from
Jul 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions src/EFCore.Design/Design/Internal/CSharpHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -787,8 +787,7 @@ internal static IReadOnlyCollection<Enum> GetFlags(Enum flags)
/// </summary>
public virtual string UnknownLiteral(object value)
{
if (value == null
|| value == DBNull.Value)
if (value == null)
{
return "null";
}
Expand Down
24 changes: 17 additions & 7 deletions src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1290,18 +1290,28 @@ protected virtual void GenerateFluentApiForDefaultValue(
[NotNull] IProperty property,
[NotNull] IndentedStringBuilder stringBuilder)
{
if (property.GetDefaultValue() is object defaultValue)
var defaultValue = property.GetDefaultValue();
if (defaultValue == null)
{
return;
}

stringBuilder
.AppendLine()
.Append(".")
.Append(nameof(RelationalPropertyBuilderExtensions.HasDefaultValue))
.Append("(");

if (defaultValue != DBNull.Value)
{
stringBuilder
.AppendLine()
.Append(".")
.Append(nameof(RelationalPropertyBuilderExtensions.HasDefaultValue))
.Append("(")
.Append(Code.UnknownLiteral(FindValueConverter(property) is ValueConverter valueConverter
? valueConverter.ConvertToProvider(defaultValue)
: defaultValue))
.Append(")");
: defaultValue));
}

stringBuilder
.Append(")");
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -626,11 +626,17 @@ private void GenerateProperty(IProperty property, bool useDataAnnotations)
$"({(property.IsUnicode() == false ? "false" : "")})");
}

if (property.GetDefaultValue() != null)
var defaultValue = property.GetDefaultValue();
if (defaultValue == DBNull.Value)
{
lines.Add($".{nameof(RelationalPropertyBuilderExtensions.HasDefaultValue)}()");
annotations.Remove(RelationalAnnotationNames.DefaultValue);
}
else if (defaultValue != null)
{
lines.Add(
$".{nameof(RelationalPropertyBuilderExtensions.HasDefaultValue)}" +
bricelam marked this conversation as resolved.
Show resolved Hide resolved
$"({_code.UnknownLiteral(property.GetDefaultValue())})");
$"({_code.UnknownLiteral(defaultValue)})");
annotations.Remove(RelationalAnnotationNames.DefaultValue);
}

Expand Down
21 changes: 15 additions & 6 deletions src/EFCore.Relational/Design/AnnotationCodeGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -145,15 +145,24 @@ public virtual IReadOnlyList<MethodCallCodeFragment> GenerateFluentApiCalls(
annotations,
RelationalAnnotationNames.ColumnName, nameof(RelationalPropertyBuilderExtensions.HasColumnName), methodCallCodeFragments);

GenerateSimpleFluentApiCall(
annotations,
RelationalAnnotationNames.DefaultValueSql, nameof(RelationalPropertyBuilderExtensions.HasDefaultValueSql),
methodCallCodeFragments);
if (TryGetAndRemove(annotations, RelationalAnnotationNames.DefaultValueSql, out string defaultValueSql))
{
methodCallCodeFragments.Add(
defaultValueSql?.Length == 0
? new MethodCallCodeFragment(
nameof(RelationalPropertyBuilderExtensions.HasDefaultValueSql))
: new MethodCallCodeFragment(
nameof(RelationalPropertyBuilderExtensions.HasDefaultValueSql),
defaultValueSql));
}

if (TryGetAndRemove(annotations, RelationalAnnotationNames.ComputedColumnSql, out object computedColumnSql))
if (TryGetAndRemove(annotations, RelationalAnnotationNames.ComputedColumnSql, out string computedColumnSql))
{
methodCallCodeFragments.Add(
TryGetAndRemove(annotations, RelationalAnnotationNames.IsStored, out bool isStored)
computedColumnSql?.Length == 0
? new MethodCallCodeFragment(
nameof(RelationalPropertyBuilderExtensions.HasComputedColumnSql))
: TryGetAndRemove(annotations, RelationalAnnotationNames.IsStored, out bool isStored)
? new MethodCallCodeFragment(
nameof(RelationalPropertyBuilderExtensions.HasComputedColumnSql),
computedColumnSql,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,28 @@ public static bool CanSetIsFixedLength(
bool fromDataAnnotation = false)
=> propertyBuilder.CanSetAnnotation(RelationalAnnotationNames.IsFixedLength, fixedLength, fromDataAnnotation);

/// <summary>
/// <para>
/// Configures the default value expression for the column that the property maps to when targeting a
/// relational database.
/// </para>
/// <para>
/// When called with no argument, this method tells EF that a column has a default value constraint of
/// some sort without needing to specify exactly what it is. This can be useful when mapping EF to an
/// existing database.
/// </para>
/// </summary>
/// <param name="propertyBuilder"> The builder for the property being configured. </param>
/// <returns> The same builder instance so that multiple calls can be chained. </returns>
public static PropertyBuilder HasDefaultValueSql([NotNull] this PropertyBuilder propertyBuilder)
{
Check.NotNull(propertyBuilder, nameof(propertyBuilder));

propertyBuilder.Metadata.SetDefaultValueSql(string.Empty);

return propertyBuilder;
}

/// <summary>
/// Configures the default value expression for the column that the property maps to when targeting a relational database.
/// </summary>
Expand All @@ -284,6 +306,24 @@ public static PropertyBuilder HasDefaultValueSql(
return propertyBuilder;
}

/// <summary>
/// <para>
/// Configures the default value expression for the column that the property maps to when targeting a
/// relational database.
/// </para>
/// <para>
/// When called with no argument, this method tells EF that a column has a default value constraint of
/// some sort without needing to specify exactly what it is. This can be useful when mapping EF to an
/// existing database.
/// </para>
/// </summary>
/// <typeparam name="TProperty"> The type of the property being configured. </typeparam>
/// <param name="propertyBuilder"> The builder for the property being configured. </param>
/// <returns> The same builder instance so that multiple calls can be chained. </returns>
public static PropertyBuilder<TProperty> HasDefaultValueSql<TProperty>(
[NotNull] this PropertyBuilder<TProperty> propertyBuilder)
=> (PropertyBuilder<TProperty>)HasDefaultValueSql((PropertyBuilder)propertyBuilder);

/// <summary>
/// Configures the default value expression for the column that the property maps to when targeting a relational database.
/// </summary>
Expand Down Expand Up @@ -333,9 +373,30 @@ public static bool CanSetDefaultValueSql(
bool fromDataAnnotation = false)
=> propertyBuilder.CanSetAnnotation(
RelationalAnnotationNames.DefaultValueSql,
Check.NullButNotEmpty(sql, nameof(sql)),
sql,
fromDataAnnotation);

/// <summary>
/// <para>
/// Configures the property to map to a computed column when targeting a relational database.
/// </para>
/// <para>
/// When called with no arguments, this method tells EF that a column is computed without needing to
/// specify the actual SQL used to computed it. This can be useful when mapping EF to an existing
/// database.
/// </para>
/// </summary>
/// <param name="propertyBuilder"> The builder for the property being configured. </param>
/// <returns> The same builder instance so that multiple calls can be chained. </returns>
public static PropertyBuilder HasComputedColumnSql([NotNull] this PropertyBuilder propertyBuilder)
{
Check.NotNull(propertyBuilder, nameof(propertyBuilder));

propertyBuilder.Metadata.SetComputedColumnSql(string.Empty);

return propertyBuilder;
}

/// <summary>
/// Configures the property to map to a computed column when targeting a relational database.
/// </summary>
Expand Down Expand Up @@ -365,6 +426,23 @@ public static PropertyBuilder HasComputedColumnSql(
return propertyBuilder;
}

/// <summary>
/// <para>
/// Configures the property to map to a computed column when targeting a relational database.
/// </para>
/// <para>
/// When called with no arguments, this method tells EF that a column is computed without needing to
/// specify the actual SQL used to computed it. This can be useful when mapping EF to an existing
/// database.
/// </para>
/// </summary>
/// <typeparam name="TProperty"> The type of the property being configured. </typeparam>
/// <param name="propertyBuilder"> The builder for the property being configured. </param>
/// <returns> The same builder instance so that multiple calls can be chained. </returns>
public static PropertyBuilder<TProperty> HasComputedColumnSql<TProperty>(
[NotNull] this PropertyBuilder<TProperty> propertyBuilder)
=> (PropertyBuilder<TProperty>)HasComputedColumnSql((PropertyBuilder)propertyBuilder);

/// <summary>
/// Configures the property to map to a computed column when targeting a relational database.
/// </summary>
Expand Down Expand Up @@ -447,7 +525,7 @@ public static bool CanSetComputedColumnSql(
bool fromDataAnnotation = false)
=> propertyBuilder.CanSetAnnotation(
RelationalAnnotationNames.ComputedColumnSql,
Check.NullButNotEmpty(sql, nameof(sql)),
sql,
fromDataAnnotation);

/// <summary>
Expand Down Expand Up @@ -482,15 +560,30 @@ public static bool CanSetIsStoredComputedColumn(
/// </para>
/// </summary>
/// <param name="propertyBuilder"> The builder for the property being configured. </param>
/// <returns> The same builder instance so that multiple calls can be chained. </returns>
public static PropertyBuilder HasDefaultValue([NotNull] this PropertyBuilder propertyBuilder)
{
Check.NotNull(propertyBuilder, nameof(propertyBuilder));

propertyBuilder.Metadata.SetDefaultValue(DBNull.Value);

return propertyBuilder;
}

/// <summary>
/// Configures the default value for the column that the property maps
/// to when targeting a relational database.
/// </summary>
/// <param name="propertyBuilder"> The builder for the property being configured. </param>
/// <param name="value"> The default value of the column. </param>
/// <returns> The same builder instance so that multiple calls can be chained. </returns>
public static PropertyBuilder HasDefaultValue(
[NotNull] this PropertyBuilder propertyBuilder,
[CanBeNull] object value = null)
[CanBeNull] object value)
{
Check.NotNull(propertyBuilder, nameof(propertyBuilder));

propertyBuilder.Metadata.SetDefaultValue(value ?? DBNull.Value);
propertyBuilder.Metadata.SetDefaultValue(value);

return propertyBuilder;
}
Expand All @@ -508,11 +601,22 @@ public static PropertyBuilder HasDefaultValue(
/// </summary>
/// <typeparam name="TProperty"> The type of the property being configured. </typeparam>
/// <param name="propertyBuilder"> The builder for the property being configured. </param>
/// <returns> The same builder instance so that multiple calls can be chained. </returns>
public static PropertyBuilder<TProperty> HasDefaultValue<TProperty>(
[NotNull] this PropertyBuilder<TProperty> propertyBuilder)
=> (PropertyBuilder<TProperty>)HasDefaultValue((PropertyBuilder)propertyBuilder);

/// <summary>
/// Configures the default value for the column that the property maps
/// to when targeting a relational database.
/// </summary>
/// <typeparam name="TProperty"> The type of the property being configured. </typeparam>
/// <param name="propertyBuilder"> The builder for the property being configured. </param>
/// <param name="value"> The default value of the column. </param>
/// <returns> The same builder instance so that multiple calls can be chained. </returns>
public static PropertyBuilder<TProperty> HasDefaultValue<TProperty>(
[NotNull] this PropertyBuilder<TProperty> propertyBuilder,
[CanBeNull] object value = null)
[CanBeNull] object value)
=> (PropertyBuilder<TProperty>)HasDefaultValue((PropertyBuilder)propertyBuilder, value);

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,7 @@ public static string GetDefaultValueSql([NotNull] this IProperty property, Store
public static void SetDefaultValueSql([NotNull] this IMutableProperty property, [CanBeNull] string value)
=> property.SetOrRemoveAnnotation(
RelationalAnnotationNames.DefaultValueSql,
Check.NullButNotEmpty(value, nameof(value)));
value);

/// <summary>
/// Sets the SQL expression that is used as the default value for the column this property is mapped to.
Expand All @@ -414,7 +414,7 @@ public static string SetDefaultValueSql(
{
property.SetOrRemoveAnnotation(
RelationalAnnotationNames.DefaultValueSql,
Check.NullButNotEmpty(value, nameof(value)),
value,
fromDataAnnotation);

return value;
Expand Down Expand Up @@ -475,7 +475,7 @@ public static string GetComputedColumnSql([NotNull] this IProperty property, Sto
public static void SetComputedColumnSql([NotNull] this IMutableProperty property, [CanBeNull] string value)
=> property.SetOrRemoveAnnotation(
RelationalAnnotationNames.ComputedColumnSql,
Check.NullButNotEmpty(value, nameof(value)));
value);

/// <summary>
/// Sets the SQL expression that is used as the computed value for the column this property is mapped to.
Expand All @@ -489,7 +489,7 @@ public static string SetComputedColumnSql(
{
property.SetOrRemoveAnnotation(
RelationalAnnotationNames.ComputedColumnSql,
Check.NullButNotEmpty(value, nameof(value)),
value,
fromDataAnnotation);

return value;
Expand Down
29 changes: 25 additions & 4 deletions src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.ChangeTracking.Internal;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Migrations.Operations;
Expand Down Expand Up @@ -1078,6 +1080,28 @@ private void Initialize(
IEnumerable<IAnnotation> migrationsAnnotations,
bool inline = false)
{
if (column.DefaultValue == DBNull.Value)
{
throw new InvalidOperationException(
RelationalStrings.DefaultValueUnspecified(
column.Name,
(column.Table.Name, column.Table.Schema).FormatTable()));
}
if (column.DefaultValueSql?.Length == 0)
{
throw new InvalidOperationException(
RelationalStrings.DefaultValueSqlUnspecified(
column.Name,
(column.Table.Name, column.Table.Schema).FormatTable()));
}
if (column.ComputedColumnSql?.Length == 0)
{
throw new InvalidOperationException(
RelationalStrings.ComputedColumnSqlUnspecified(
column.Name,
(column.Table.Name, column.Table.Schema).FormatTable()));
}

var property = column.PropertyMappings.First().Property;
var valueConverter = GetValueConverter(property, typeMapping);
columnOperation.ClrType
Expand All @@ -1092,13 +1116,10 @@ private void Initialize(
columnOperation.IsFixedLength = column.IsFixedLength;
columnOperation.IsRowVersion = column.IsRowVersion;
columnOperation.IsNullable = isNullable;

var defaultValue = column.DefaultValue;
columnOperation.DefaultValue = (defaultValue == DBNull.Value ? null : defaultValue)
columnOperation.DefaultValue = column.DefaultValue
?? (inline || isNullable
? null
: GetDefaultValue(columnOperation.ClrType));

columnOperation.DefaultValueSql = column.DefaultValueSql;
columnOperation.ComputedColumnSql = column.ComputedColumnSql;
columnOperation.IsStored = column.IsStored;
Expand Down
24 changes: 24 additions & 0 deletions src/EFCore.Relational/Properties/RelationalStrings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading