Skip to content

Commit

Permalink
Add DbFunctionParameter support for DbFunctions
Browse files Browse the repository at this point in the history
Resolves #13752
Resolves #13782
  • Loading branch information
pmiddleton authored and smitpatel committed Jun 21, 2019
1 parent d48ceef commit c2c05fc
Show file tree
Hide file tree
Showing 21 changed files with 912 additions and 71 deletions.
13 changes: 7 additions & 6 deletions src/EFCore.Relational/Extensions/RelationalModelExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
Expand Down Expand Up @@ -300,21 +301,21 @@ public static IConventionDbFunction RemoveDbFunction([NotNull] this IConventionM
/// Returns all <see cref="IDbFunction" />s contained in the model.
/// </summary>
/// <param name="model"> The model to get the functions in. </param>
public static IReadOnlyList<IDbFunction> GetDbFunctions([NotNull] this IModel model)
=> DbFunction.GetDbFunctions(model).ToList();
public static IEnumerable<IDbFunction> GetDbFunctions([NotNull] this IModel model)
=> DbFunction.GetDbFunctions(model.AsModel());

/// <summary>
/// Returns all <see cref="IMutableDbFunction" />s contained in the model.
/// </summary>
/// <param name="model"> The model to get the functions in. </param>
public static IReadOnlyList<IMutableDbFunction> GetDbFunctions([NotNull] this IMutableModel model)
=> (IReadOnlyList<IMutableDbFunction>)((IModel)model).GetDbFunctions();
public static IEnumerable<IMutableDbFunction> GetDbFunctions([NotNull] this IMutableModel model)
=> DbFunction.GetDbFunctions((Model)model);

/// <summary>
/// Returns all <see cref="IConventionDbFunction" />s contained in the model.
/// </summary>
/// <param name="model"> The model to get the functions in. </param>
public static IReadOnlyList<IConventionDbFunction> GetDbFunctions([NotNull] this IConventionModel model)
=> (IReadOnlyList<IConventionDbFunction>)((IModel)model).GetDbFunctions();
public static IEnumerable<IConventionDbFunction> GetDbFunctions([NotNull] this IConventionModel model)
=> DbFunction.GetDbFunctions((Model)model);
}
}
29 changes: 13 additions & 16 deletions src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,26 +80,23 @@ protected virtual void ValidateDbFunctions([NotNull] IModel model, [NotNull] IDi
RelationalStrings.DbFunctionNameEmpty(methodInfo.DisplayName()));
}

if (dbFunction.Translation == null)
if (dbFunction.TypeMapping == null)
{
if (RelationalDependencies.TypeMappingSource.FindMapping(methodInfo.ReturnType) == null)
throw new InvalidOperationException(
RelationalStrings.DbFunctionInvalidReturnType(
methodInfo.DisplayName(),
methodInfo.ReturnType.ShortDisplayName()));
}

foreach (var parameter in dbFunction.Parameters)
{
if(parameter.TypeMapping == null)
{
throw new InvalidOperationException(
RelationalStrings.DbFunctionInvalidReturnType(
RelationalStrings.DbFunctionInvalidParameterType(
parameter.Name,
methodInfo.DisplayName(),
methodInfo.ReturnType.ShortDisplayName()));
}

foreach (var parameter in methodInfo.GetParameters())
{
if (RelationalDependencies.TypeMappingSource.FindMapping(parameter.ParameterType) == null)
{
throw new InvalidOperationException(
RelationalStrings.DbFunctionInvalidParameterType(
parameter.Name,
methodInfo.DisplayName(),
parameter.ParameterType.ShortDisplayName()));
}
parameter.ClrType.ShortDisplayName()));
}
}
}
Expand Down
70 changes: 70 additions & 0 deletions src/EFCore.Relational/Metadata/Builders/DbFunctionBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,15 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Relational.Query.Pipeline.SqlExpressions;
using Microsoft.EntityFrameworkCore.Utilities;
using Microsoft.EntityFrameworkCore.Storage;

namespace Microsoft.EntityFrameworkCore.Metadata.Builders
{
Expand Down Expand Up @@ -98,6 +102,53 @@ bool IConventionDbFunctionBuilder.CanSetSchema(string schema, bool fromDataAnnot
=> Overrides(fromDataAnnotation, _function.GetSchemaConfigurationSource())
|| _function.Schema == schema;

/// <summary>
/// Sets the store type of the database function.
/// </summary>
/// <param name="storeType"> The store type of the function in the database. </param>
/// <returns> The same builder instance so that multiple configuration calls can be chained. </returns>
public virtual DbFunctionBuilder HasStoreType([CanBeNull] string storeType)
{
_function.StoreType = storeType;

return this;
}

/// <inheritdoc />
IConventionDbFunctionBuilder IConventionDbFunctionBuilder.HasStoreType(string storeType, bool fromDataAnnotation)
{
if (((IConventionDbFunctionBuilder)this).CanSetStoreType(storeType, fromDataAnnotation))
{
((IConventionDbFunction)_function).SetStoreType(storeType, fromDataAnnotation);
return this;
}

return null;
}

/// <inheritdoc />
bool IConventionDbFunctionBuilder.CanSetStoreType(string storeType, bool fromDataAnnotation)
=> Overrides(fromDataAnnotation, _function.GetStoreTypeConfigurationSource())
|| _function.StoreType == storeType;

/// <inheritdoc />
IConventionDbFunctionBuilder IConventionDbFunctionBuilder.HasTypeMapping(
RelationalTypeMapping returnTypeMapping, bool fromDataAnnotation)
{
if (((IConventionDbFunctionBuilder)this).CanSetTypeMapping(returnTypeMapping, fromDataAnnotation))
{
((IConventionDbFunction)_function).SetTypeMapping(returnTypeMapping, fromDataAnnotation);
return this;
}

return null;
}

/// <inheritdoc />
bool IConventionDbFunctionBuilder.CanSetTypeMapping(RelationalTypeMapping returnTypeMapping, bool fromDataAnnotation)
=> Overrides(fromDataAnnotation, _function.GetTypeMappingConfigurationSource())
|| _function.TypeMapping == returnTypeMapping;

/// <summary>
/// <para>
/// Sets a callback that will be invoked to perform custom translation of this
Expand Down Expand Up @@ -136,6 +187,25 @@ IConventionDbFunctionBuilder IConventionDbFunctionBuilder.HasTranslation(
return null;
}

public virtual DbFunctionParameterBuilder HasParameter([NotNull] string name)
{
return new DbFunctionParameterBuilder((DbFunctionParameter)FindParameter(name));
}

private IDbFunctionParameter FindParameter(string name)
{
var parameter = Metadata.Parameters.SingleOrDefault(
funcParam => string.Compare(funcParam.Name, name, StringComparison.OrdinalIgnoreCase) == 0);

if (parameter == null)
{
throw new ArgumentException(
RelationalStrings.DbFunctionInvalidParameterName(name, Metadata.MethodInfo.DisplayName()));
}

return parameter;
}

/// <inheritdoc />
bool IConventionDbFunctionBuilder.CanSetTranslation(
Func<IReadOnlyCollection<SqlExpression>, SqlExpression> translation, bool fromDataAnnotation)
Expand Down
150 changes: 150 additions & 0 deletions src/EFCore.Relational/Metadata/Builders/DbFunctionParameterBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Utilities;

namespace Microsoft.EntityFrameworkCore.Metadata.Builders
{
/// <summary>
/// <para>
/// Provides a simple API for configuring a <see cref="DbFunctionParameter" />.
/// </para>
/// <para>
/// Instances of this class are returned from methods when using the <see cref="ModelBuilder" /> API
/// and it is not designed to be directly constructed in your application code.
/// </para>
/// </summary>
public class DbFunctionParameterBuilder : IConventionDbFunctionParameterBuilder
{
private readonly DbFunctionParameter _parameter;

/// <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>
[EntityFrameworkInternal]
public DbFunctionParameterBuilder([NotNull] IMutableDbFunctionParameter parameter)
{
Check.NotNull(parameter, nameof(parameter));

_parameter = (DbFunctionParameter)parameter;
}

public virtual IMutableDbFunctionParameter Metadata => _parameter;

/// <inheritdoc />
IConventionDbFunctionParameter IConventionDbFunctionParameterBuilder.Metadata => _parameter;

/// <summary>
/// Specify if this parameter supports Nullability Propagation
/// </summary>
public virtual DbFunctionParameterBuilder HasNullabilityPropagation(bool supportsNullabilityPropagation)
{
_parameter.SupportsNullPropagation = supportsNullabilityPropagation;

return this;
}

IConventionDbFunctionParameterBuilder IConventionDbFunctionParameterBuilder.HasNullPropagation(
bool supportsNullPropagation, bool fromDataAnnotation)
{
if (((IConventionDbFunctionParameterBuilder)this).CanSetSupportsNullPropagation(supportsNullPropagation, fromDataAnnotation))
{
((IConventionDbFunctionParameter)_parameter).SetSupportsNullPropagation(supportsNullPropagation, fromDataAnnotation);
return this;
}

return null;
}

bool IConventionDbFunctionParameterBuilder.CanSetSupportsNullPropagation(bool supportsNullPropagation, bool fromDataAnnotation)
{
return Overrides(fromDataAnnotation, _parameter.GetSupportsNullPropagationConfigurationSource())
|| _parameter.SupportsNullPropagation == supportsNullPropagation;
}

public virtual DbFunctionParameterBuilder HasStoreType([CanBeNull] string storeType)
{
_parameter.StoreType = storeType;

return this;
}

IConventionDbFunctionParameterBuilder IConventionDbFunctionParameterBuilder.HasStoreType(string storeType, bool fromDataAnnotation)
{
if (((IConventionDbFunctionParameterBuilder)this).CanSetStoreType(storeType, fromDataAnnotation))
{
((IConventionDbFunctionParameter)_parameter).SetStoreType(storeType, fromDataAnnotation);
return this;
}

return null;
}

bool IConventionDbFunctionParameterBuilder.CanSetStoreType(string storeType, bool fromDataAnnotation)
{
return Overrides(fromDataAnnotation, _parameter.GetStoreTypeConfigurationSource())
|| _parameter.StoreType == storeType;
}

IConventionDbFunctionParameterBuilder IConventionDbFunctionParameterBuilder.HasTypeMapping(
RelationalTypeMapping typeMapping, bool fromDataAnnotation)
{
if (((IConventionDbFunctionParameterBuilder)this).CanSetTypeMapping(typeMapping, fromDataAnnotation))
{
((IConventionDbFunctionParameter)_parameter).SetTypeMapping(typeMapping, fromDataAnnotation);
return this;
}

return null;
}

bool IConventionDbFunctionParameterBuilder.CanSetTypeMapping(RelationalTypeMapping typeMapping, bool fromDataAnnotation)
{
return Overrides(fromDataAnnotation, _parameter.GetTypeMappingConfigurationSource())
|| _parameter.TypeMapping == typeMapping;
}

private bool Overrides(bool fromDataAnnotation, ConfigurationSource? configurationSource)
=> (fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention)
.Overrides(configurationSource);

#region Hidden System.Object members

/// <summary>
/// Returns a string that represents the current object.
/// </summary>
/// <returns> A string that represents the current object. </returns>
[EditorBrowsable(EditorBrowsableState.Never)]
public override string ToString() => base.ToString();

/// <summary>
/// Determines whether the specified object is equal to the current object.
/// </summary>
/// <param name="obj"> The object to compare with the current object. </param>
/// <returns> true if the specified object is equal to the current object; otherwise, false. </returns>
[EditorBrowsable(EditorBrowsableState.Never)]
// ReSharper disable once BaseObjectEqualsIsObjectEquals
public override bool Equals(object obj) => base.Equals(obj);

/// <summary>
/// Serves as the default hash function.
/// </summary>
/// <returns> A hash code for the current object. </returns>
[EditorBrowsable(EditorBrowsableState.Never)]
// ReSharper disable once BaseObjectGetHashCodeCallInGetHashCode
public override int GetHashCode() => base.GetHashCode();

#endregion
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Collections.Generic;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Relational.Query.Pipeline.SqlExpressions;
using Microsoft.EntityFrameworkCore.Storage;

namespace Microsoft.EntityFrameworkCore.Metadata.Builders
{
Expand Down Expand Up @@ -56,6 +57,44 @@ public interface IConventionDbFunctionBuilder
/// <returns> <c>true</c> if the given schema can be set for the database function. </returns>
bool CanSetSchema([CanBeNull] string schema, bool fromDataAnnotation = false);

/// <summary>
/// Sets the store type of the function in the database.
/// </summary>
/// <param name="storeType"> The store type of the function in the database. </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,
/// <c>null</c> otherwise.
/// </returns>
IConventionDbFunctionBuilder HasStoreType([CanBeNull] string storeType, bool fromDataAnnotation = false);

/// <summary>
/// Returns a value indicating whether the given store type can be set for the database function.
/// </summary>
/// <param name="storeType"> The store type of the function in the database. </param>
/// <param name="fromDataAnnotation"> Indicates whether the configuration was specified using a data annotation. </param>
/// <returns> <c>true</c> if the given store type can be set for the database function. </returns>
bool CanSetStoreType([CanBeNull] string storeType, bool fromDataAnnotation = false);

/// <summary>
/// Sets the return type mapping of the database function.
/// </summary>
/// <param name="typeMapping"> The return type mapping of the function in the database. </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,
/// <c>null</c> otherwise.
/// </returns>
IConventionDbFunctionBuilder HasTypeMapping([CanBeNull] RelationalTypeMapping typeMapping, bool fromDataAnnotation = false);

/// <summary>
/// Returns a value indicating whether the given return type mapping can be set for the database function.
/// </summary>
/// <param name="typeMapping"> The return type mapping of the function in the database. </param>
/// <param name="fromDataAnnotation"> Indicates whether the configuration was specified using a data annotation. </param>
/// <returns> <c>true</c> if the given return type mapping can be set for the database function. </returns>
bool CanSetTypeMapping([CanBeNull] RelationalTypeMapping typeMapping, bool fromDataAnnotation = false);

/// <summary>
/// <para>
/// Sets a callback that will be invoked to perform custom translation of this
Expand Down
Loading

0 comments on commit c2c05fc

Please sign in to comment.