Skip to content

Commit

Permalink
Allow value converters to convert nulls (#24687)
Browse files Browse the repository at this point in the history
Fixes #13850
  • Loading branch information
ajcvickers committed Apr 20, 2021
1 parent 7e2c5ce commit 6c01b2c
Show file tree
Hide file tree
Showing 64 changed files with 2,558 additions and 243 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -712,13 +712,31 @@ var body
body = Expression.Convert(body, type);
}

Expression replaceExpression;
if (converter?.ConvertsNulls == true)
{
replaceExpression = ReplacingExpressionVisitor.Replace(
converter.ConvertFromProviderExpression.Parameters.Single(),
Expression.Default(converter.ProviderClrType),
converter.ConvertFromProviderExpression.Body);

if (replaceExpression.Type != type)
{
replaceExpression = Expression.Convert(replaceExpression, type);
}
}
else
{
replaceExpression = Expression.Default(type);
}

body = Expression.Condition(
Expression.OrElse(
Expression.Equal(jTokenParameter, Expression.Default(typeof(JToken))),
Expression.Equal(
Expression.MakeMemberAccess(jTokenParameter, _jTokenTypePropertyInfo),
Expression.Constant(JTokenType.Null))),
Expression.Default(type),
replaceExpression,
body);

valueExpression = Expression.Invoke(Expression.Lambda(body, jTokenParameter), jTokenExpression);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1570,8 +1570,7 @@ private void GenerateFluentApiForDefaultValue(
IProperty property,
IndentedStringBuilder stringBuilder)
{
var defaultValue = property.GetDefaultValue();
if (defaultValue == null)
if (!property.TryGetDefaultValue(out var defaultValue))
{
return;
}
Expand Down
22 changes: 12 additions & 10 deletions src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -621,17 +621,19 @@ private void GenerateProperty(IProperty property, bool useDataAnnotations)
}
}

var defaultValue = property.GetDefaultValue();
if (defaultValue == DBNull.Value)
if (property.TryGetDefaultValue(out var defaultValue))
{
lines.Add($".{nameof(RelationalPropertyBuilderExtensions.HasDefaultValue)}()");
annotations.Remove(RelationalAnnotationNames.DefaultValue);
}
else if (defaultValue != null)
{
lines.Add(
$".{nameof(RelationalPropertyBuilderExtensions.HasDefaultValue)}({_code.UnknownLiteral(defaultValue)})");
annotations.Remove(RelationalAnnotationNames.DefaultValue);
if (defaultValue == DBNull.Value)
{
lines.Add($".{nameof(RelationalPropertyBuilderExtensions.HasDefaultValue)}()");
annotations.Remove(RelationalAnnotationNames.DefaultValue);
}
else if (defaultValue != null)
{
lines.Add(
$".{nameof(RelationalPropertyBuilderExtensions.HasDefaultValue)}({_code.UnknownLiteral(defaultValue)})");
annotations.Remove(RelationalAnnotationNames.DefaultValue);
}
}

var valueGenerated = property.ValueGenerated;
Expand Down
73 changes: 59 additions & 14 deletions src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,7 @@ public static void SetColumnType(this IMutableProperty property, string? value)
/// <returns> The default columns to which the property would be mapped. </returns>
public static IEnumerable<IColumnMappingBase> GetDefaultColumnMappings(this IProperty property)
=> (IEnumerable<IColumnMappingBase>?)property.FindRuntimeAnnotationValue(
RelationalAnnotationNames.DefaultColumnMappings)
RelationalAnnotationNames.DefaultColumnMappings)
?? Enumerable.Empty<IColumnMappingBase>();

/// <summary>
Expand All @@ -395,7 +395,7 @@ public static IEnumerable<IColumnMappingBase> GetDefaultColumnMappings(this IPro
/// <returns> The table columns to which the property is mapped. </returns>
public static IEnumerable<IColumnMapping> GetTableColumnMappings(this IProperty property)
=> (IEnumerable<IColumnMapping>?)property.FindRuntimeAnnotationValue(
RelationalAnnotationNames.TableColumnMappings)
RelationalAnnotationNames.TableColumnMappings)
?? Enumerable.Empty<IColumnMapping>();

/// <summary>
Expand All @@ -405,7 +405,7 @@ public static IEnumerable<IColumnMapping> GetTableColumnMappings(this IProperty
/// <returns> The view columns to which the property is mapped. </returns>
public static IEnumerable<IViewColumnMapping> GetViewColumnMappings(this IProperty property)
=> (IEnumerable<IViewColumnMapping>?)property.FindRuntimeAnnotationValue(
RelationalAnnotationNames.ViewColumnMappings)
RelationalAnnotationNames.ViewColumnMappings)
?? Enumerable.Empty<IViewColumnMapping>();

/// <summary>
Expand All @@ -415,7 +415,7 @@ public static IEnumerable<IViewColumnMapping> GetViewColumnMappings(this IProper
/// <returns> The SQL query columns to which the property is mapped. </returns>
public static IEnumerable<ISqlQueryColumnMapping> GetSqlQueryColumnMappings(this IProperty property)
=> (IEnumerable<ISqlQueryColumnMapping>?)property.FindRuntimeAnnotationValue(
RelationalAnnotationNames.SqlQueryColumnMappings)
RelationalAnnotationNames.SqlQueryColumnMappings)
?? Enumerable.Empty<ISqlQueryColumnMapping>();

/// <summary>
Expand All @@ -425,7 +425,7 @@ public static IEnumerable<ISqlQueryColumnMapping> GetSqlQueryColumnMappings(this
/// <returns> The function columns to which the property is mapped. </returns>
public static IEnumerable<IFunctionColumnMapping> GetFunctionColumnMappings(this IProperty property)
=> (IEnumerable<IFunctionColumnMapping>?)property.FindRuntimeAnnotationValue(
RelationalAnnotationNames.FunctionColumnMappings)
RelationalAnnotationNames.FunctionColumnMappings)
?? Enumerable.Empty<IFunctionColumnMapping>();

/// <summary>
Expand Down Expand Up @@ -701,7 +701,30 @@ public static void SetIsStored(this IMutableProperty property, bool? value)
/// <param name="property"> The property. </param>
/// <returns> The object that is used as the default value for the column this property is mapped to. </returns>
public static object? GetDefaultValue(this IReadOnlyProperty property)
=> property.FindAnnotation(RelationalAnnotationNames.DefaultValue)?.Value;
{
property.TryGetDefaultValue(out var defaultValue);
return defaultValue;
}

/// <summary>
/// Returns the object that is used as the default value for the column this property is mapped to.
/// </summary>
/// <param name="property"> The property. </param>
/// <param name="defaultValue"> The default value, or the CLR default if no explicit default has been set. </param>
/// <returns> <see langword="true" /> if a default value has been explicitly set; <see langword="false" /> otherwise. </returns>
public static bool TryGetDefaultValue(this IReadOnlyProperty property, out object? defaultValue)
{
var annotation = property.FindAnnotation(RelationalAnnotationNames.DefaultValue);

if (annotation != null)
{
defaultValue = annotation.Value;
return defaultValue != null;
}

defaultValue = property.ClrType.GetDefaultValue();
return false;
}

/// <summary>
/// Returns the object that is used as the default value for the column this property is mapped to.
Expand All @@ -710,20 +733,38 @@ public static void SetIsStored(this IMutableProperty property, bool? value)
/// <param name="storeObject"> The identifier of the table-like store object containing the column. </param>
/// <returns> The object that is used as the default value for the column this property is mapped to. </returns>
public static object? GetDefaultValue(this IReadOnlyProperty property, in StoreObjectIdentifier storeObject)
{
property.TryGetDefaultValue(storeObject, out var defaultValue);
return defaultValue;
}

/// <summary>
/// Returns the object that is used as the default value for the column this property is mapped to.
/// </summary>
/// <param name="property"> The property. </param>
/// <param name="storeObject"> The identifier of the table-like store object containing the column. </param>
/// <param name="defaultValue"> The default value, or the CLR default if no explicit default has been set. </param>
/// <returns> <see langword="true" /> if a default value has been explicitly set; <see langword="false" /> otherwise. </returns>
public static bool TryGetDefaultValue(
this IReadOnlyProperty property,
in StoreObjectIdentifier storeObject,
out object? defaultValue)
{
var annotation = property.FindAnnotation(RelationalAnnotationNames.DefaultValue);
if (annotation != null)
{
return annotation.Value;
defaultValue = annotation.Value;
return defaultValue != null;
}

var sharedTableRootProperty = property.FindSharedStoreObjectRootProperty(storeObject);
if (sharedTableRootProperty != null)
{
return GetDefaultValue(sharedTableRootProperty, storeObject);
return TryGetDefaultValue(sharedTableRootProperty, storeObject, out defaultValue);
}

return null;
defaultValue = property.ClrType.GetDefaultValue();
return false;
}

/// <summary>
Expand Down Expand Up @@ -982,7 +1023,10 @@ public static bool IsColumnNullable(this IReadOnlyProperty property, in StoreObj
|| IsOptionalSharingDependent(property.DeclaringEntityType, storeObject, 0);
}

private static bool IsOptionalSharingDependent(IReadOnlyEntityType entityType, in StoreObjectIdentifier storeObject, int recursionDepth)
private static bool IsOptionalSharingDependent(
IReadOnlyEntityType entityType,
in StoreObjectIdentifier storeObject,
int recursionDepth)
{
if (recursionDepth++ == Metadata.Internal.RelationalEntityTypeExtensions.MaxEntityTypesSharingTable)
{
Expand Down Expand Up @@ -1277,7 +1321,8 @@ public static RelationalTypeMapping GetRelationalTypeMapping(this IReadOnlyPrope
}

private static IReadOnlyProperty? FindSharedObjectRootPrimaryKeyProperty(
IReadOnlyProperty property, in StoreObjectIdentifier storeObject)
IReadOnlyProperty property,
in StoreObjectIdentifier storeObject)
{
if (!property.IsPrimaryKey())
{
Expand Down Expand Up @@ -1393,7 +1438,7 @@ public static RelationalTypeMapping GetRelationalTypeMapping(this IReadOnlyPrope
/// <param name="storeObject"> The identifier of the table-like store object containing the column. </param>
/// <returns> An object that stores property facet overrides. </returns>
public static IAnnotatable? FindOverrides(this IProperty property, in StoreObjectIdentifier storeObject)
=> (IAnnotatable?)RelationalPropertyOverrides.Find(property, storeObject);
=> RelationalPropertyOverrides.Find(property, storeObject);

/// <summary>
/// <para>
Expand All @@ -1410,7 +1455,7 @@ public static RelationalTypeMapping GetRelationalTypeMapping(this IReadOnlyPrope
public static IMutableAnnotatable GetOrCreateOverrides(
this IMutableProperty property,
in StoreObjectIdentifier storeObject)
=> (IMutableAnnotatable)RelationalPropertyOverrides.GetOrCreate(property, storeObject);
=> RelationalPropertyOverrides.GetOrCreate(property, storeObject);

/// <summary>
/// <para>
Expand All @@ -1427,6 +1472,6 @@ public static IMutableAnnotatable GetOrCreateOverrides(
public static IConventionAnnotatable GetOrCreateOverrides(
this IConventionProperty property,
in StoreObjectIdentifier storeObject)
=> (IConventionAnnotatable)RelationalPropertyOverrides.GetOrCreate(property, storeObject);
=> RelationalPropertyOverrides.GetOrCreate(property, storeObject);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -153,9 +153,9 @@ private void ProcessTableChanged(
return valueGenerated
?? (property.GetComputedColumnSql(storeObject) != null
? ValueGenerated.OnAddOrUpdate
: property.GetDefaultValue(storeObject) != null || property.GetDefaultValueSql(storeObject) != null
: property.TryGetDefaultValue(storeObject, out _) || property.GetDefaultValueSql(storeObject) != null
? ValueGenerated.OnAdd
: (ValueGenerated?)null);
: null);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ protected virtual void Validate(
IConventionProperty property,
in StoreObjectIdentifier storeObject)
{
if (property.GetDefaultValue(storeObject) != null)
if (property.TryGetDefaultValue(storeObject, out _))
{
if (property.GetDefaultValueSql(storeObject) != null)
{
Expand Down
31 changes: 25 additions & 6 deletions src/EFCore.Relational/Metadata/IColumn.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,14 +71,33 @@ public virtual object? DefaultValue
{
get
{
var property = PropertyMappings.First().Property;
var value = property.GetDefaultValue(StoreObjectIdentifier.Table(Table.Name, Table.Schema));
var converter = property.GetValueConverter() ?? PropertyMappings.First().TypeMapping?.Converter;
TryGetDefaultValue(out var defaultValue);
return defaultValue;
}
}

/// <summary>
/// Gets the object that is used as the default value for this column.
/// </summary>
/// <param name="defaultValue"> The default value. </param>
/// <returns> True if the default value was explicitly set; false otherwise. </returns>
public virtual bool TryGetDefaultValue(out object? defaultValue)
{
var property = PropertyMappings.First().Property;

return converter != null
? converter.ConvertToProvider(value)
: value;
if (!property.TryGetDefaultValue(StoreObjectIdentifier.Table(Table.Name, Table.Schema), out defaultValue))
{
return false;
}

var converter = property.GetValueConverter() ?? PropertyMappings.First().TypeMapping?.Converter;

if (converter != null)
{
defaultValue = converter.ConvertToProvider(defaultValue);
}

return true;
}

/// <summary>
Expand Down
Loading

0 comments on commit 6c01b2c

Please sign in to comment.