Skip to content

Commit

Permalink
Add DbFunctionParameter support for DbFunctions
Browse files Browse the repository at this point in the history
  • Loading branch information
pmiddleton committed May 14, 2019
1 parent 2daec6a commit 835ef92
Show file tree
Hide file tree
Showing 21 changed files with 1,171 additions and 32 deletions.
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 (RelationalDependencies.TypeMappingSource.FindMapping(methodInfo.ReturnType) == 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 && RelationalDependencies.TypeMappingSource.FindMapping(parameter.ClrType) == 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
26 changes: 26 additions & 0 deletions src/EFCore.Relational/Metadata/Builders/DbFunctionBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@
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;
Expand Down Expand Up @@ -128,6 +131,29 @@ IConventionDbFunctionBuilder IConventionDbFunctionBuilder.HasTranslation(
return null;
}


/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
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;
}

bool IConventionDbFunctionBuilder.CanSetTranslation(
Func<IReadOnlyCollection<SqlExpression>, SqlExpression> translation, bool fromDataAnnotation)
=> Overrides(fromDataAnnotation, _function.GetTranslationConfigurationSource())
Expand Down
200 changes: 200 additions & 0 deletions src/EFCore.Relational/Metadata/Builders/DbFunctionBuilder.cs.orig
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
// 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;
<<<<<<< HEAD
=======
using System.Linq;
using System.Linq.Expressions;
>>>>>>> Add DbFunctionParameter support for DbFunctions
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;

namespace Microsoft.EntityFrameworkCore.Metadata.Builders
{
/// <summary>
/// Provides a simple API for configuring a <see cref="IMutableDbFunction" />.
/// </summary>
public class DbFunctionBuilder : IConventionDbFunctionBuilder
{
private readonly DbFunction _function;

/// <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 DbFunctionBuilder([NotNull] IMutableDbFunction function)
{
Check.NotNull(function, nameof(function));

_function = (DbFunction)function;
}

/// <summary>
/// The function being configured.
/// </summary>
public virtual IMutableDbFunction Metadata => _function;

/// <summary>
/// Sets the name of the database function.
/// </summary>
/// <param name="name"> The name of the function in the database. </param>
/// <returns> The same builder instance so that multiple configuration calls can be chained. </returns>
public virtual DbFunctionBuilder HasName([NotNull] string name)
{
Check.NotEmpty(name, nameof(name));

_function.FunctionName = name;

return this;
}

IConventionDbFunctionBuilder IConventionDbFunctionBuilder.HasName(string name, bool fromDataAnnotation)
{
if (((IConventionDbFunctionBuilder)this).CanSetName(name, fromDataAnnotation))
{
((IConventionDbFunction)_function).SetFunctionName(name, fromDataAnnotation);
return this;
}

return null;
}

bool IConventionDbFunctionBuilder.CanSetName(string name, bool fromDataAnnotation)
=> Overrides(fromDataAnnotation, _function.GetFunctionNameConfigurationSource())
|| _function.FunctionName == name;

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

return this;
}

IConventionDbFunctionBuilder IConventionDbFunctionBuilder.HasSchema(string schema, bool fromDataAnnotation)
{
if (((IConventionDbFunctionBuilder)this).CanSetSchema(schema, fromDataAnnotation))
{
((IConventionDbFunction)_function).SetSchema(schema, fromDataAnnotation);
return this;
}

return null;
}

bool IConventionDbFunctionBuilder.CanSetSchema(string schema, bool fromDataAnnotation)
=> Overrides(fromDataAnnotation, _function.GetSchemaConfigurationSource())
|| _function.Schema == schema;

/// <summary>
/// <para>
/// Sets a callback that will be invoked to perform custom translation of this
/// function. The callback takes a collection of expressions corresponding to
/// the parameters passed to the function call. The callback should return an
/// expression representing the desired translation.
/// </para>
/// <para>
/// See https://go.microsoft.com/fwlink/?linkid=852477 for more information.
/// </para>
/// </summary>
/// <param name="translation"> The translation to use. </param>
/// <returns> The same builder instance so that multiple configuration calls can be chained. </returns>
public virtual DbFunctionBuilder HasTranslation([NotNull] Func<IReadOnlyCollection<SqlExpression>, SqlExpression> translation)
{
Check.NotNull(translation, nameof(translation));

_function.Translation = translation;

return this;
}

<<<<<<< HEAD
IConventionDbFunctionBuilder IConventionDbFunctionBuilder.HasTranslation(
Func<IReadOnlyCollection<SqlExpression>, SqlExpression> translation, bool fromDataAnnotation)
{
if (((IConventionDbFunctionBuilder)this).CanSetTranslation(translation, fromDataAnnotation))
{
((IConventionDbFunction)_function).SetTranslation(translation, fromDataAnnotation);
return this;
}

return null;
}

bool IConventionDbFunctionBuilder.CanSetTranslation(
Func<IReadOnlyCollection<SqlExpression>, SqlExpression> translation, bool fromDataAnnotation)
=> Overrides(fromDataAnnotation, _function.GetTranslationConfigurationSource())
|| _function.Translation == translation;

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

/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public virtual DbFunctionParameterBuilder HasParameter([NotNull] string name)
{
return new DbFunctionParameterBuilder((DbFunctionParameter)FindParameter(name));
}

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

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

return parameter;
}
>>>>>>> Add DbFunctionParameter support for DbFunctions

#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
@@ -0,0 +1,47 @@
// 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.Text;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
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
{
private readonly InternalDbFunctionParameterBuilder _builder;

/// <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 DbFunctionParameterBuilder(DbFunctionParameter dbFunctionParameter)
{
_builder = new InternalDbFunctionParameterBuilder(dbFunctionParameter);
}

/// <summary>
/// Specify if this parametere supports Nullability Propagation
/// </summary>
public virtual DbFunctionParameterBuilder HasNullabilityPropagation(bool supportsNullabilityPropagation)
{
_builder.HasNullabilityPropagation(supportsNullabilityPropagation);

return this;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ public override ConventionSet CreateConventionSet()
conventionSet.ModelBuiltConventions.Add(storeGenerationConvention);
conventionSet.ModelBuiltConventions.Add(sharedTableConvention);

var dbFunctionTypeMappingConvention = new DbFunctionTypeMappingConvention(RelationalDependencies.TypeMappingSource);
conventionSet.ModelBuiltConventions.Add(dbFunctionTypeMappingConvention);

conventionSet.ModelAnnotationChangedConventions.Add(new RelationalDbFunctionConvention(logger));

return conventionSet;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// 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.Linq;
using System.Text;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Storage;

namespace Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal
{
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public class DbFunctionTypeMappingConvention : IModelBuiltConvention
{
private readonly IRelationalTypeMappingSource _typeMappingSource;

/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public DbFunctionTypeMappingConvention([NotNull] IRelationalTypeMappingSource typeMappingSource)
{
_typeMappingSource = typeMappingSource;
}

/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public virtual InternalModelBuilder Apply(InternalModelBuilder modelBuilder)
{
foreach (var dbFunction in ((IModel)modelBuilder.Metadata).GetDbFunctions())
{
var mutableDbFunc = modelBuilder.HasDbFunction(dbFunction.MethodInfo);
mutableDbFunc.Metadata.ReturnTypeMapping = _typeMappingSource.FindMapping(dbFunction.MethodInfo.ReturnType);

foreach (var dbFuncParam in dbFunction.Parameters.Cast<IMutableDbFunctionParameter>())
{
dbFuncParam.TypeMapping = _typeMappingSource.FindMapping(dbFuncParam.ClrType);
}
}

return modelBuilder;
}
}
}
Loading

0 comments on commit 835ef92

Please sign in to comment.