Skip to content

Commit

Permalink
Fix to #19609 - Query: allow user functions to be annotated with null…
Browse files Browse the repository at this point in the history
…ability propagation information

Added fluent API for function to specify it's nullability and function parameter to specify whether it propagates null. Also added property to DbFunction attribute to allow specify nullability there.
  • Loading branch information
maumar committed Jul 27, 2020
1 parent 2a3ff06 commit f53fe59
Show file tree
Hide file tree
Showing 20 changed files with 454 additions and 21 deletions.
20 changes: 19 additions & 1 deletion src/EFCore.Abstractions/DbFunctionAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,12 @@ namespace Microsoft.EntityFrameworkCore
public class DbFunctionAttribute : Attribute
#pragma warning restore CA1813 // Avoid unsealed attributes
{
private static readonly bool DefaultNullable = true;

private string _name;
private string _schema;
private bool _builtIn;
private bool? _nullable;

/// <summary>
/// Initializes a new instance of the <see cref="DbFunctionAttribute" /> class.
Expand Down Expand Up @@ -68,12 +71,27 @@ public virtual string Schema
}

/// <summary>
/// The value indicating wheather the database function is built-in or not.
/// The value indicating whether the database function is built-in or not.
/// </summary>
public virtual bool IsBuiltIn
{
get => _builtIn;
set => _builtIn = value;
}

/// <summary>
/// The value indicating whether the database function can return null result or not.
/// </summary>
public virtual bool IsNullable
{
get => _nullable ?? DefaultNullable;
set => _nullable = value;
}

/// <summary>
/// Use this method if you want to know the nullability of
/// the database function or <see langword="null"/> if it was not specified.
/// </summary>
public bool? GetIsNullable() => _nullable;
}
}
15 changes: 14 additions & 1 deletion src/EFCore.Relational/Metadata/Builders/DbFunctionBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,24 @@ public DbFunctionBuilder([NotNull] IMutableDbFunction function)
/// <summary>
/// Marks whether the database function is built-in.
/// </summary>
/// <param name="builtIn"> The value indicating wheather the database function is built-in. </param>
/// <param name="builtIn"> The value indicating whether the database function is built-in. </param>
/// <returns> The same builder instance so that multiple configuration calls can be chained. </returns>
public new virtual DbFunctionBuilder IsBuiltIn(bool builtIn = true)
=> (DbFunctionBuilder)base.IsBuiltIn(builtIn);


/// <summary>
/// Marks whether the database function can return null value.
/// </summary>
/// <param name="nullable"> The value indicating whether the database function can return null. </param>
/// <returns> The same builder instance so that multiple configuration calls can be chained. </returns>
public virtual DbFunctionBuilderBase IsNullable(bool nullable = true)
{
Builder.IsNullable(nullable, ConfigurationSource.Explicit);

return this;
}

/// <summary>
/// Sets the return store type of the database function.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ public virtual DbFunctionBuilderBase HasSchema([CanBeNull] string schema)
/// <summary>
/// Marks whether the database function is built-in.
/// </summary>
/// <param name="builtIn"> The value indicating wheather the database function is built-in. </param>
/// <param name="builtIn"> The value indicating whether the database function is built-in. </param>
/// <returns> The same builder instance so that multiple configuration calls can be chained. </returns>
public virtual DbFunctionBuilderBase IsBuiltIn(bool builtIn = true)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,18 @@ public virtual DbFunctionParameterBuilder HasStoreType([CanBeNull] string storeT
return this;
}

/// <summary>
/// Indicates whether parameter propagates nullability, meaning if it's value is null the database function itself returns null.
/// </summary>
/// <param name="propagatesNullability"> Value which indicates whether parameter propagates nullability. </param>
/// <returns> The same builder instance so that further configuration calls can be chained. </returns>
public virtual DbFunctionParameterBuilder PropagatesNullability(bool propagatesNullability = true)
{
Builder.PropagatesNullability(propagatesNullability, ConfigurationSource.Explicit);

return this;
}

#region Hidden System.Object members

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public interface IConventionDbFunctionBuilder : IConventionAnnotatableBuilder
bool CanSetSchema([CanBeNull] string schema, bool fromDataAnnotation = false);

/// <summary>
/// Sets the value indicating wheather the database function is built-in or not.
/// Sets the value indicating whether the database function is built-in or not.
/// </summary>
/// <param name="builtIn"> The value indicating whether the database function is built-in or not. </param>
/// <param name="fromDataAnnotation"> Indicates whether the configuration was specified using a data annotation. </param>
Expand All @@ -76,6 +76,25 @@ public interface IConventionDbFunctionBuilder : IConventionAnnotatableBuilder
/// <returns> <see langword="true" /> if the given schema can be set for the database function. </returns>
bool CanSetIsBuiltIn(bool builtIn, bool fromDataAnnotation = false);

/// <summary>
/// Sets the value indicating whether the database function can return null value or not.
/// </summary>
/// <param name="nullable"> The value indicating whether the database function is built-in or not. </param>
/// <param name="fromDataAnnotation"> Indicates whether the configuration was specified using a data annotation. </param>
/// <returns>
/// The same builder instance if the configuration was applied,
/// <see langword="null" /> otherwise.
/// </returns>
IConventionDbFunctionBuilder IsNullable(bool nullable, bool fromDataAnnotation = false);

/// <summary>
/// Returns a value indicating whether the given nullable can be set for the database function.
/// </summary>
/// <param name="nullable"> The value indicating whether the database function can return null value or not. </param>
/// <param name="fromDataAnnotation"> Indicates whether the configuration was specified using a data annotation. </param>
/// <returns> <see langword="true" /> if the given schema can be set for the database function. </returns>
bool CanSetIsNullable(bool nullable, bool fromDataAnnotation = false);

/// <summary>
/// Sets the store type of the function in the database.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,11 @@ protected virtual void ProcessDbFunctionAdded(
{
dbFunctionBuilder.IsBuiltIn(dbFunctionAttribute.IsBuiltIn, fromDataAnnotation: true);
}

if (!dbFunctionAttribute.GetIsNullable() != null)
{
dbFunctionBuilder.IsNullable(dbFunctionAttribute.IsNullable, fromDataAnnotation: true);
}
}
}
}
Expand Down
16 changes: 15 additions & 1 deletion src/EFCore.Relational/Metadata/IConventionDbFunction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public interface IConventionDbFunction : IConventionAnnotatable, IDbFunction
ConfigurationSource? GetSchemaConfigurationSource();

/// <summary>
/// Sets the value indicating wheather the database function is built-in or not.
/// Sets the value indicating whether the database function is built-in or not.
/// </summary>
/// <param name="builtIn"> The value indicating whether the database function is built-in or not. </param>
/// <param name="fromDataAnnotation"> Indicates whether the configuration was specified using a data annotation. </param>
Expand All @@ -74,6 +74,20 @@ public interface IConventionDbFunction : IConventionAnnotatable, IDbFunction
/// <returns> The configuration source for <see cref="IDbFunction.IsBuiltIn" />. </returns>
ConfigurationSource? GetIsBuiltInConfigurationSource();

/// <summary>
/// Sets the value indicating whether the database function can return null value or not.
/// </summary>
/// <param name="nullable"> The value indicating whether the database function can return null value or not. </param>
/// <param name="fromDataAnnotation"> Indicates whether the configuration was specified using a data annotation. </param>
/// <returns> The configured value. </returns>
bool SetIsNullable(bool nullable, bool fromDataAnnotation = false);

/// <summary>
/// Gets the configuration source for <see cref="IDbFunction.IsNullable" />.
/// </summary>
/// <returns> The configuration source for <see cref="IDbFunction.IsNullable" />. </returns>
ConfigurationSource? GetIsNullableConfigurationSource();

/// <summary>
/// Sets the store type of the function in the database.
/// </summary>
Expand Down
5 changes: 5 additions & 0 deletions src/EFCore.Relational/Metadata/IDbFunction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ public interface IDbFunction : IAnnotatable
/// </summary>
bool IsAggregate { get; }

/// <summary>
/// Gets the value indicating whether the database function can return null.
/// </summary>
bool IsNullable { get; }

/// <summary>
/// Gets the configured store type string.
/// </summary>
Expand Down
7 changes: 6 additions & 1 deletion src/EFCore.Relational/Metadata/IMutableDbFunction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,15 @@ public interface IMutableDbFunction : IMutableAnnotatable, IDbFunction
new string Schema { get; [param: CanBeNull] set; }

/// <summary>
/// Gets or sets the value indicating wheather the database function is built-in or not.
/// Gets or sets the value indicating whether the database function is built-in or not.
/// </summary>
new bool IsBuiltIn { get; set; }

/// <summary>
/// Gets or sets the value indicating whether the database function can return null value or not.
/// </summary>
new bool IsNullable { get; set; }

/// <summary>
/// Gets or sets the store type of the function in the database.
/// </summary>
Expand Down
48 changes: 48 additions & 0 deletions src/EFCore.Relational/Metadata/Internal/DbFunction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public class DbFunction : ConventionAnnotatable, IMutableDbFunction, IConvention
private string _schema;
private string _name;
private bool _builtIn;
private bool _nullable;
private string _storeType;
private RelationalTypeMapping _typeMapping;
private Func<IReadOnlyCollection<SqlExpression>, SqlExpression> _translation;
Expand All @@ -38,6 +39,7 @@ public class DbFunction : ConventionAnnotatable, IMutableDbFunction, IConvention
private ConfigurationSource? _schemaConfigurationSource;
private ConfigurationSource? _nameConfigurationSource;
private ConfigurationSource? _builtInConfigurationSource;
private ConfigurationSource? _nullableConfigurationSource;
private ConfigurationSource? _storeTypeConfigurationSource;
private ConfigurationSource? _typeMappingConfigurationSource;
private ConfigurationSource? _translationConfigurationSource;
Expand Down Expand Up @@ -113,6 +115,8 @@ public DbFunction(
: parameters
.Select(p => new DbFunctionParameter(this, p.Name, p.Type))
.ToList();

_nullable = true;
}

private static string GetFunctionName(MethodInfo methodInfo, ParameterInfo[] parameters)
Expand Down Expand Up @@ -386,6 +390,45 @@ public virtual bool SetIsBuiltIn(bool builtIn, ConfigurationSource configuration
/// </summary>
public virtual ConfigurationSource? GetIsBuiltInConfigurationSource() => _builtInConfigurationSource;

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual bool IsNullable
{
get => _nullable;
set => SetIsNullable(value, ConfigurationSource.Explicit);
}

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual bool SetIsNullable(bool nullable, ConfigurationSource configurationSource)
{
if (!IsScalar)
{
new InvalidOperationException(RelationalStrings.NullabilityInfoOnlyAllowedOnScalarFunctions);
}

_nullable = nullable;
_nullableConfigurationSource = configurationSource.Max(_nullableConfigurationSource);

return nullable;
}

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual ConfigurationSource? GetIsNullableConfigurationSource() => _nullableConfigurationSource;

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
Expand Down Expand Up @@ -605,6 +648,11 @@ string IConventionDbFunction.SetSchema(string schema, bool fromDataAnnotation)
bool IConventionDbFunction.SetIsBuiltIn(bool builtIn, bool fromDataAnnotation)
=> SetIsBuiltIn(builtIn, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);

/// <inheritdoc />
[DebuggerStepThrough]
bool IConventionDbFunction.SetIsNullable(bool nullable, bool fromDataAnnotation)
=> SetIsNullable(nullable, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);

/// <inheritdoc />
[DebuggerStepThrough]
string IConventionDbFunction.SetStoreType(string storeType, bool fromDataAnnotation)
Expand Down
52 changes: 52 additions & 0 deletions src/EFCore.Relational/Metadata/Internal/DbFunctionParameter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Diagnostics;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore.Metadata.Builders.Internal;
Expand All @@ -22,9 +23,11 @@ public class DbFunctionParameter : ConventionAnnotatable, IMutableDbFunctionPara
{
private string _storeType;
private RelationalTypeMapping _typeMapping;
private bool _propagatesNullability;

private ConfigurationSource? _storeTypeConfigurationSource;
private ConfigurationSource? _typeMappingConfigurationSource;
private ConfigurationSource? _propagatesNullabilityConfigurationSource;

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand Down Expand Up @@ -177,6 +180,55 @@ public virtual RelationalTypeMapping SetTypeMapping(
private void UpdateTypeMappingConfigurationSource(ConfigurationSource configurationSource)
=> _typeMappingConfigurationSource = configurationSource.Max(_typeMappingConfigurationSource);

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual bool PropagatesNullability
{
get => _propagatesNullability;
set => SetPropagatesNullability(value, ConfigurationSource.Explicit);
}

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual bool SetPropagatesNullability(bool propagatesNullability, ConfigurationSource configurationSource)
{
if (!Function.IsScalar)
{
new InvalidOperationException(RelationalStrings.NullabilityInfoOnlyAllowedOnScalarFunctions);
}

_propagatesNullability = propagatesNullability;

UpdatePropagatesNullabilityConfigurationSource(configurationSource);

return propagatesNullability;
}

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
private void UpdatePropagatesNullabilityConfigurationSource(ConfigurationSource configurationSource)
=> _propagatesNullabilityConfigurationSource = configurationSource.Max(_storeTypeConfigurationSource);

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual ConfigurationSource? GetPropagatesNullabilityConfigurationSource() => _propagatesNullabilityConfigurationSource;

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
Expand Down
Loading

0 comments on commit f53fe59

Please sign in to comment.