diff --git a/src/EFCore.Relational/Extensions/RelationalModelExtensions.cs b/src/EFCore.Relational/Extensions/RelationalModelExtensions.cs
index fd26fb76755..a96b8e804d6 100644
--- a/src/EFCore.Relational/Extensions/RelationalModelExtensions.cs
+++ b/src/EFCore.Relational/Extensions/RelationalModelExtensions.cs
@@ -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;
@@ -300,21 +301,21 @@ public static IConventionDbFunction RemoveDbFunction([NotNull] this IConventionM
/// Returns all s contained in the model.
///
/// The model to get the functions in.
- public static IReadOnlyList GetDbFunctions([NotNull] this IModel model)
- => DbFunction.GetDbFunctions(model).ToList();
+ public static IEnumerable GetDbFunctions([NotNull] this IModel model)
+ => DbFunction.GetDbFunctions((Model)model);
///
/// Returns all s contained in the model.
///
/// The model to get the functions in.
- public static IReadOnlyList GetDbFunctions([NotNull] this IMutableModel model)
- => (IReadOnlyList)((IModel)model).GetDbFunctions();
+ public static IEnumerable GetDbFunctions([NotNull] this IMutableModel model)
+ => DbFunction.GetDbFunctions((Model)model);
///
/// Returns all s contained in the model.
///
/// The model to get the functions in.
- public static IReadOnlyList GetDbFunctions([NotNull] this IConventionModel model)
- => (IReadOnlyList)((IModel)model).GetDbFunctions();
+ public static IEnumerable GetDbFunctions([NotNull] this IConventionModel model)
+ => DbFunction.GetDbFunctions((Model)model);
}
}
diff --git a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs
index 33b313d8add..ffb336591ff 100644
--- a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs
+++ b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs
@@ -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()));
}
}
}
diff --git a/src/EFCore.Relational/Metadata/Builders/DbFunctionBuilder.cs b/src/EFCore.Relational/Metadata/Builders/DbFunctionBuilder.cs
index 66630d134e3..84b00fb732a 100644
--- a/src/EFCore.Relational/Metadata/Builders/DbFunctionBuilder.cs
+++ b/src/EFCore.Relational/Metadata/Builders/DbFunctionBuilder.cs
@@ -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
{
@@ -98,6 +102,53 @@ bool IConventionDbFunctionBuilder.CanSetSchema(string schema, bool fromDataAnnot
=> Overrides(fromDataAnnotation, _function.GetSchemaConfigurationSource())
|| _function.Schema == schema;
+ ///
+ /// Sets the store type of the database function.
+ ///
+ /// The store type of the function in the database.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual DbFunctionBuilder HasStoreType([CanBeNull] string storeType)
+ {
+ _function.StoreType = storeType;
+
+ return this;
+ }
+
+ ///
+ IConventionDbFunctionBuilder IConventionDbFunctionBuilder.HasStoreType(string storeType, bool fromDataAnnotation)
+ {
+ if (((IConventionDbFunctionBuilder)this).CanSetStoreType(storeType, fromDataAnnotation))
+ {
+ ((IConventionDbFunction)_function).SetStoreType(storeType, fromDataAnnotation);
+ return this;
+ }
+
+ return null;
+ }
+
+ ///
+ bool IConventionDbFunctionBuilder.CanSetStoreType(string storeType, bool fromDataAnnotation)
+ => Overrides(fromDataAnnotation, _function.GetStoreTypeConfigurationSource())
+ || _function.StoreType == storeType;
+
+ ///
+ IConventionDbFunctionBuilder IConventionDbFunctionBuilder.HasTypeMapping(
+ RelationalTypeMapping returnTypeMapping, bool fromDataAnnotation)
+ {
+ if (((IConventionDbFunctionBuilder)this).CanSetTypeMapping(returnTypeMapping, fromDataAnnotation))
+ {
+ ((IConventionDbFunction)_function).SetTypeMapping(returnTypeMapping, fromDataAnnotation);
+ return this;
+ }
+
+ return null;
+ }
+
+ ///
+ bool IConventionDbFunctionBuilder.CanSetTypeMapping(RelationalTypeMapping returnTypeMapping, bool fromDataAnnotation)
+ => Overrides(fromDataAnnotation, _function.GetTypeMappingConfigurationSource())
+ || _function.TypeMapping == returnTypeMapping;
+
///
///
/// Sets a callback that will be invoked to perform custom translation of this
@@ -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;
+ }
+
///
bool IConventionDbFunctionBuilder.CanSetTranslation(
Func, SqlExpression> translation, bool fromDataAnnotation)
diff --git a/src/EFCore.Relational/Metadata/Builders/DbFunctionParameterBuilder.cs b/src/EFCore.Relational/Metadata/Builders/DbFunctionParameterBuilder.cs
new file mode 100644
index 00000000000..b2c8ef9db5e
--- /dev/null
+++ b/src/EFCore.Relational/Metadata/Builders/DbFunctionParameterBuilder.cs
@@ -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
+{
+ ///
+ ///
+ /// Provides a simple API for configuring a .
+ ///
+ ///
+ /// Instances of this class are returned from methods when using the API
+ /// and it is not designed to be directly constructed in your application code.
+ ///
+ ///
+ public class DbFunctionParameterBuilder : IConventionDbFunctionParameterBuilder
+ {
+ private readonly DbFunctionParameter _parameter;
+
+ ///
+ /// 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.
+ ///
+ [EntityFrameworkInternal]
+ public DbFunctionParameterBuilder([NotNull] IMutableDbFunctionParameter parameter)
+ {
+ Check.NotNull(parameter, nameof(parameter));
+
+ _parameter = (DbFunctionParameter)parameter;
+ }
+
+ public virtual IMutableDbFunctionParameter Metadata => _parameter;
+
+ ///
+ IConventionDbFunctionParameter IConventionDbFunctionParameterBuilder.Metadata => _parameter;
+
+ ///
+ /// Specify if this parametere supports Nullability Propagation
+ ///
+ 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
+
+ ///
+ /// Returns a string that represents the current object.
+ ///
+ /// A string that represents the current object.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override string ToString() => base.ToString();
+
+ ///
+ /// Determines whether the specified object is equal to the current object.
+ ///
+ /// The object to compare with the current object.
+ /// true if the specified object is equal to the current object; otherwise, false.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ // ReSharper disable once BaseObjectEqualsIsObjectEquals
+ public override bool Equals(object obj) => base.Equals(obj);
+
+ ///
+ /// Serves as the default hash function.
+ ///
+ /// A hash code for the current object.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ // ReSharper disable once BaseObjectGetHashCodeCallInGetHashCode
+ public override int GetHashCode() => base.GetHashCode();
+
+ #endregion
+ }
+}
diff --git a/src/EFCore.Relational/Metadata/Builders/IConventionDbFunctionBuilder.cs b/src/EFCore.Relational/Metadata/Builders/IConventionDbFunctionBuilder.cs
index b944d3ecc24..70cc4c6b0bd 100644
--- a/src/EFCore.Relational/Metadata/Builders/IConventionDbFunctionBuilder.cs
+++ b/src/EFCore.Relational/Metadata/Builders/IConventionDbFunctionBuilder.cs
@@ -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
{
@@ -56,6 +57,44 @@ public interface IConventionDbFunctionBuilder
/// true if the given schema can be set for the database function.
bool CanSetSchema([CanBeNull] string schema, bool fromDataAnnotation = false);
+ ///
+ /// Sets the store type of the function in the database.
+ ///
+ /// The store type of the function in the database.
+ /// Indicates whether the configuration was specified using a data annotation.
+ ///
+ /// The same builder instance if the configuration was applied,
+ /// null otherwise.
+ ///
+ IConventionDbFunctionBuilder HasStoreType([CanBeNull] string storeType, bool fromDataAnnotation = false);
+
+ ///
+ /// Returns a value indicating whether the given store type can be set for the database function.
+ ///
+ /// The store type of the function in the database.
+ /// Indicates whether the configuration was specified using a data annotation.
+ /// true if the given store type can be set for the database function.
+ bool CanSetStoreType([CanBeNull] string storeType, bool fromDataAnnotation = false);
+
+ ///
+ /// Sets the return type mapping of the database function.
+ ///
+ /// The return type mapping of the function in the database.
+ /// Indicates whether the configuration was specified using a data annotation.
+ ///
+ /// The same builder instance if the configuration was applied,
+ /// null otherwise.
+ ///
+ IConventionDbFunctionBuilder HasTypeMapping([CanBeNull] RelationalTypeMapping typeMapping, bool fromDataAnnotation = false);
+
+ ///
+ /// Returns a value indicating whether the given return type mapping can be set for the database function.
+ ///
+ /// The return type mapping of the function in the database.
+ /// Indicates whether the configuration was specified using a data annotation.
+ /// true if the given return type mapping can be set for the database function.
+ bool CanSetTypeMapping([CanBeNull] RelationalTypeMapping typeMapping, bool fromDataAnnotation = false);
+
///
///
/// Sets a callback that will be invoked to perform custom translation of this
diff --git a/src/EFCore.Relational/Metadata/Builders/IConventionDbFunctionParameterBuilder.cs b/src/EFCore.Relational/Metadata/Builders/IConventionDbFunctionParameterBuilder.cs
new file mode 100644
index 00000000000..5e2056de83a
--- /dev/null
+++ b/src/EFCore.Relational/Metadata/Builders/IConventionDbFunctionParameterBuilder.cs
@@ -0,0 +1,28 @@
+// 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 JetBrains.Annotations;
+using Microsoft.EntityFrameworkCore.Storage;
+
+namespace Microsoft.EntityFrameworkCore.Metadata
+{
+ ///
+ /// Provides a simple API for configuring a .
+ ///
+ public interface IConventionDbFunctionParameterBuilder
+ {
+ IConventionDbFunctionParameter Metadata { get; }
+
+ IConventionDbFunctionParameterBuilder HasNullPropagation(bool supportsNullPropagation, bool fromDataAnnotation = false);
+
+ bool CanSetSupportsNullPropagation(bool supportsNullPropagation, bool fromDataAnnotation = false);
+
+ IConventionDbFunctionParameterBuilder HasStoreType([CanBeNull] string storeType, bool fromDataAnnotation = false);
+
+ bool CanSetStoreType([CanBeNull] string storeType, bool fromDataAnnotation = false);
+
+ IConventionDbFunctionParameterBuilder HasTypeMapping([CanBeNull] RelationalTypeMapping typeMapping, bool fromDataAnnotation = false);
+
+ bool CanSetTypeMapping([CanBeNull] RelationalTypeMapping typeMapping, bool fromDataAnnotation = false);
+ }
+}
diff --git a/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilder.cs b/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilder.cs
index f0b7ddd88b2..3182a76aef4 100644
--- a/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilder.cs
+++ b/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilder.cs
@@ -84,6 +84,7 @@ public override ConventionSet CreateConventionSet()
var dbFunctionAttributeConvention = new RelationalDbFunctionAttributeConvention(Dependencies, RelationalDependencies);
conventionSet.ModelInitializedConventions.Add(dbFunctionAttributeConvention);
+ conventionSet.ModelAnnotationChangedConventions.Add(dbFunctionAttributeConvention);
var sharedTableConvention = new SharedTableConvention(Dependencies, RelationalDependencies);
ConventionSet.AddBefore(
@@ -95,7 +96,10 @@ public override ConventionSet CreateConventionSet()
sharedTableConvention,
typeof(ValidatingConvention));
- conventionSet.ModelAnnotationChangedConventions.Add(dbFunctionAttributeConvention);
+ ConventionSet.AddBefore(
+ conventionSet.ModelFinalizedConventions,
+ new DbFunctionTypeMappingConvention(Dependencies, RelationalDependencies),
+ typeof(ValidatingConvention));
return conventionSet;
}
diff --git a/src/EFCore.Relational/Metadata/Conventions/Internal/DbFunctionTypeMappingConvention.cs b/src/EFCore.Relational/Metadata/Conventions/Internal/DbFunctionTypeMappingConvention.cs
new file mode 100644
index 00000000000..e5446532980
--- /dev/null
+++ b/src/EFCore.Relational/Metadata/Conventions/Internal/DbFunctionTypeMappingConvention.cs
@@ -0,0 +1,62 @@
+// 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.Builders;
+using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata.Internal;
+using Microsoft.EntityFrameworkCore.Storage;
+
+namespace Microsoft.EntityFrameworkCore.Metadata.Conventions
+{
+ ///
+ /// 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.
+ ///
+ public class DbFunctionTypeMappingConvention : IModelFinalizedConvention
+ {
+ private readonly IRelationalTypeMappingSource _relationalTypeMappingSource;
+
+ ///
+ /// Creates a new instance of .
+ ///
+ /// Parameter object containing dependencies for this convention.
+ /// Parameter object containing relational dependencies for this convention.
+ public DbFunctionTypeMappingConvention(
+ [NotNull] ProviderConventionSetBuilderDependencies dependencies,
+ [NotNull] RelationalConventionSetBuilderDependencies relationalDependencies)
+ {
+ _relationalTypeMappingSource = (IRelationalTypeMappingSource)dependencies.TypeMappingSource;
+ }
+
+ ///
+ /// Called after a model is finalized.
+ ///
+ /// The builder for the model.
+ /// Additional information associated with convention execution.
+ public virtual void ProcessModelFinalized(IConventionModelBuilder modelBuilder, IConventionContext context)
+ {
+ foreach (var dbFunction in modelBuilder.Metadata.GetDbFunctions())
+ {
+ var typeMapping = !string.IsNullOrEmpty(dbFunction.StoreType)
+ ? _relationalTypeMappingSource.FindMapping(dbFunction.StoreType)
+ : _relationalTypeMappingSource.FindMapping(dbFunction.MethodInfo.ReturnType);
+
+ dbFunction.Builder.HasTypeMapping(typeMapping);
+
+ foreach (var parameter in dbFunction.Parameters)
+ {
+ typeMapping = !string.IsNullOrEmpty(parameter.StoreType)
+ ? _relationalTypeMappingSource.FindMapping(parameter.StoreType)
+ : _relationalTypeMappingSource.FindMapping(parameter.ClrType);
+
+ parameter.Builder.HasTypeMapping(typeMapping);
+ }
+ }
+ }
+ }
+}
diff --git a/src/EFCore.Relational/Metadata/IConventionDbFunction.cs b/src/EFCore.Relational/Metadata/IConventionDbFunction.cs
index a1999b4f085..842ee0ced79 100644
--- a/src/EFCore.Relational/Metadata/IConventionDbFunction.cs
+++ b/src/EFCore.Relational/Metadata/IConventionDbFunction.cs
@@ -6,6 +6,7 @@
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore.Relational.Query.Pipeline.SqlExpressions;
+using Microsoft.EntityFrameworkCore.Storage;
namespace Microsoft.EntityFrameworkCore.Metadata
{
@@ -57,6 +58,32 @@ public interface IConventionDbFunction : IDbFunction
/// The configuration source for .
ConfigurationSource? GetSchemaConfigurationSource();
+ ///
+ /// Sets the store type of the function in the database.
+ ///
+ /// The store type of the function in the database.
+ /// Indicates whether the configuration was specified using a data annotation.
+ void SetStoreType([CanBeNull] string storeType, bool fromDataAnnotation = false);
+
+ ///
+ /// Returns the configuration source for .
+ ///
+ /// The configuration source for .
+ ConfigurationSource? GetStoreTypeConfigurationSource();
+
+ ///
+ /// Sets the type mapping of the function in the database.
+ ///
+ /// The type mapping of the function in the database.
+ /// Indicates whether the configuration was specified using a data annotation.
+ void SetTypeMapping([CanBeNull] RelationalTypeMapping typeMapping, bool fromDataAnnotation = false);
+
+ ///
+ /// Returns the configuration source for .
+ ///
+ /// The configuration source for .
+ ConfigurationSource? GetTypeMappingConfigurationSource();
+
///
/// Sets the translation callback for performing custom translation of the method call into a SQL expression fragment.
///
@@ -71,5 +98,10 @@ public interface IConventionDbFunction : IDbFunction
///
/// The configuration source for .
ConfigurationSource? GetTranslationConfigurationSource();
+
+ ///
+ /// The parameters for this function
+ ///
+ new IReadOnlyList Parameters { get; }
}
}
diff --git a/src/EFCore.Relational/Metadata/IConventionDbFunctionParameter.cs b/src/EFCore.Relational/Metadata/IConventionDbFunctionParameter.cs
new file mode 100644
index 00000000000..73ce0dab3e4
--- /dev/null
+++ b/src/EFCore.Relational/Metadata/IConventionDbFunctionParameter.cs
@@ -0,0 +1,55 @@
+// 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 JetBrains.Annotations;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Storage;
+
+namespace Microsoft.EntityFrameworkCore.Metadata
+{
+ public interface IConventionDbFunctionParameter : IDbFunctionParameter
+ {
+ new IConventionDbFunction Parent { get; }
+
+ IConventionDbFunctionParameterBuilder Builder { get; }
+
+ ///
+ /// Sets the store type of the parameter in the database.
+ ///
+ /// The store type of the parameter in the database.
+ /// Indicates whether the configuration was specified using a data annotation.
+ void SetStoreType([CanBeNull] string storeType, bool fromDataAnnotation = false);
+
+ ///
+ /// Returns the configuration source for .
+ ///
+ /// The configuration source for .
+ ConfigurationSource? GetStoreTypeConfigurationSource();
+
+ ///
+ /// Sets the type mapping of the parameter in the database.
+ ///
+ /// The type mapping of the parameter in the database.
+ /// Indicates whether the configuration was specified using a data annotation.
+ void SetTypeMapping([CanBeNull] RelationalTypeMapping typeMapping, bool fromDataAnnotation = false);
+
+ ///
+ /// Returns the configuration source for .
+ ///
+ /// The configuration source for .
+ ConfigurationSource? GetTypeMappingConfigurationSource();
+
+ ///
+ /// Sets if the parameter propagates null in the database.
+ ///
+ /// The supports null propagation value.
+ /// Indicates whether the configuration was specified using a data annotation.
+ void SetSupportsNullPropagation(bool supportsNullPropagation, bool fromDataAnnotation = false);
+
+ ///
+ /// Returns the configuration source for .
+ ///
+ /// The configuration source for .
+ ConfigurationSource? GetSupportsNullPropagationConfigurationSource();
+ }
+}
diff --git a/src/EFCore.Relational/Metadata/IDbFunction.cs b/src/EFCore.Relational/Metadata/IDbFunction.cs
index 353763942f5..67efc15e0ad 100644
--- a/src/EFCore.Relational/Metadata/IDbFunction.cs
+++ b/src/EFCore.Relational/Metadata/IDbFunction.cs
@@ -5,6 +5,7 @@
using System.Collections.Generic;
using System.Reflection;
using Microsoft.EntityFrameworkCore.Relational.Query.Pipeline.SqlExpressions;
+using Microsoft.EntityFrameworkCore.Storage;
namespace Microsoft.EntityFrameworkCore.Metadata
{
@@ -33,6 +34,21 @@ public interface IDbFunction
///
MethodInfo MethodInfo { get; }
+ ///
+ /// The configured store type string
+ ///
+ string StoreType { get; }
+
+ ///
+ /// The type mapping for the function's return type
+ ///
+ RelationalTypeMapping TypeMapping { get; }
+
+ ///
+ /// The parameters for this function
+ ///
+ IReadOnlyList Parameters { get; }
+
///
/// A translation callback for performing custom translation of the method call into a SQL expression fragment.
///
diff --git a/src/EFCore.Relational/Metadata/IDbFunctionParameter.cs b/src/EFCore.Relational/Metadata/IDbFunctionParameter.cs
new file mode 100644
index 00000000000..b763c9cb6c3
--- /dev/null
+++ b/src/EFCore.Relational/Metadata/IDbFunctionParameter.cs
@@ -0,0 +1,26 @@
+// 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 Microsoft.EntityFrameworkCore.Storage;
+
+namespace Microsoft.EntityFrameworkCore.Metadata
+{
+ ///
+ /// Represents a db function parameter in an .
+ ///
+ public interface IDbFunctionParameter
+ {
+ IDbFunction Parent { get; }
+
+ string Name { get; }
+
+ Type ClrType { get; }
+
+ bool SupportsNullPropagation { get; }
+
+ string StoreType { get; }
+
+ RelationalTypeMapping TypeMapping { get; }
+ }
+}
diff --git a/src/EFCore.Relational/Metadata/IMutableDbFunction.cs b/src/EFCore.Relational/Metadata/IMutableDbFunction.cs
index 10c27724dec..9c16c624e49 100644
--- a/src/EFCore.Relational/Metadata/IMutableDbFunction.cs
+++ b/src/EFCore.Relational/Metadata/IMutableDbFunction.cs
@@ -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
{
@@ -17,13 +18,23 @@ public interface IMutableDbFunction : IDbFunction
///
/// The name of the function in the database.
///
- new string FunctionName { get; [param: NotNull] set; }
+ new string FunctionName { get; [param: CanBeNull] set; }
///
/// The schema of the function in the database.
///
new string Schema { get; [param: CanBeNull] set; }
+ ///
+ /// The store type of the function in the database.
+ ///
+ new string StoreType { get; [param: CanBeNull] set; }
+
+ ///
+ /// The type mapping of the function in the database.
+ ///
+ new RelationalTypeMapping TypeMapping { get; [param: CanBeNull] set; }
+
///
/// The in which this function is defined.
///
diff --git a/src/EFCore.Relational/Metadata/IMutableDbFunctionParameter.cs b/src/EFCore.Relational/Metadata/IMutableDbFunctionParameter.cs
new file mode 100644
index 00000000000..d0217bd99cb
--- /dev/null
+++ b/src/EFCore.Relational/Metadata/IMutableDbFunctionParameter.cs
@@ -0,0 +1,23 @@
+// 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 JetBrains.Annotations;
+using Microsoft.EntityFrameworkCore.Storage;
+
+namespace Microsoft.EntityFrameworkCore.Metadata
+{
+ ///
+ /// Represents a db function parametere in an .
+ ///
+ public interface IMutableDbFunctionParameter : IDbFunctionParameter
+ {
+ new IMutableDbFunction Parent { get; }
+
+ new bool SupportsNullPropagation { get; set; }
+
+ new string StoreType { get; [param: CanBeNull] set; }
+
+ new RelationalTypeMapping TypeMapping { get; [param: CanBeNull] set; }
+ }
+}
diff --git a/src/EFCore.Relational/Metadata/Internal/DbFunction.cs b/src/EFCore.Relational/Metadata/Internal/DbFunction.cs
index 0e25e58e7dc..3f63200566d 100644
--- a/src/EFCore.Relational/Metadata/Internal/DbFunction.cs
+++ b/src/EFCore.Relational/Metadata/Internal/DbFunction.cs
@@ -11,6 +11,7 @@
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore.Relational.Query.Pipeline.SqlExpressions;
+using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Utilities;
namespace Microsoft.EntityFrameworkCore.Metadata.Internal
@@ -25,13 +26,18 @@ public class DbFunction : IMutableDbFunction, IConventionDbFunction
{
private readonly IMutableModel _model;
private readonly string _annotationName;
+ private readonly List _parameters;
private string _schema;
private string _functionName;
+ private string _storeType;
+ private RelationalTypeMapping _typeMapping;
+ private Func, SqlExpression> _translation;
private ConfigurationSource? _schemaConfigurationSource;
private ConfigurationSource? _functionNameConfigurationSource;
+ private ConfigurationSource? _storeTypeConfigurationSource;
+ private ConfigurationSource? _typeMappingConfigurationSource;
private ConfigurationSource? _translationConfigurationSource;
- private Func, SqlExpression> _translation;
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -55,6 +61,7 @@ public DbFunction(
if (!methodInfo.IsStatic
&& !typeof(DbContext).IsAssignableFrom(methodInfo.DeclaringType))
{
+ // ReSharper disable once AssignNullToNotNullAttribute
throw new ArgumentException(
RelationalStrings.DbFunctionInvalidInstanceType(
methodInfo.DisplayName(), methodInfo.DeclaringType.ShortDisplayName()));
@@ -71,6 +78,11 @@ public DbFunction(
MethodInfo = methodInfo;
_model = model;
+
+ _parameters = methodInfo.GetParameters()
+ .Select((pi, i) => new DbFunctionParameter(this, pi.Name, pi.ParameterType))
+ .ToList();
+
_annotationName = BuildAnnotationName(methodInfo);
if (configurationSource == ConfigurationSource.Explicit)
{
@@ -96,18 +108,19 @@ public DbFunction(
/// 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.
///
- public static IEnumerable GetDbFunctions([NotNull] IModel model)
+ public static IEnumerable GetDbFunctions([NotNull] Model model)
{
Check.NotNull(model, nameof(model));
return model.GetAnnotations()
.Where(a => a.Name.StartsWith(RelationalAnnotationNames.DbFunction, StringComparison.Ordinal))
.Select(a => a.Value)
- .Cast();
+ .Cast();
}
private static string BuildAnnotationName(MethodBase methodBase)
=>
+ // ReSharper disable once AssignNullToNotNullAttribute
$"{RelationalAnnotationNames.DbFunction}{methodBase.DeclaringType.ShortDisplayName()}{methodBase.Name}({string.Join(",", methodBase.GetParameters().Select(p => p.ParameterType.Name))})";
///
@@ -180,7 +193,7 @@ private void UpdateSchemaConfigurationSource(ConfigurationSource configurationSo
///
public virtual string FunctionName
{
- get => _functionName;
+ get => _functionName ?? MethodInfo.Name;
set => SetFunctionName(value, ConfigurationSource.Explicit);
}
@@ -218,6 +231,80 @@ private void UpdateFunctionNameConfigurationSource(ConfigurationSource configura
///
public virtual MethodInfo MethodInfo { get; }
+ ///
+ /// 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.
+ ///
+ public virtual string StoreType
+ {
+ get => _storeType;
+ set => SetStoreType(value, ConfigurationSource.Explicit);
+ }
+
+ ///
+ /// 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.
+ ///
+ public virtual void SetStoreType([NotNull] string storeType, ConfigurationSource configurationSource)
+ {
+ Check.NotNull(storeType, nameof(storeType));
+
+ _storeType = storeType;
+
+ UpdateStoreTypeConfigurationSource(configurationSource);
+ }
+
+ private void UpdateStoreTypeConfigurationSource(ConfigurationSource configurationSource)
+ => _storeTypeConfigurationSource = configurationSource.Max(_storeTypeConfigurationSource);
+
+ ///
+ /// 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.
+ ///
+ public virtual ConfigurationSource? GetStoreTypeConfigurationSource() => _storeTypeConfigurationSource;
+
+ ///
+ /// 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.
+ ///
+ public virtual RelationalTypeMapping TypeMapping
+ {
+ get => _typeMapping;
+ set => SetTypeMapping(value, ConfigurationSource.Explicit);
+ }
+
+ ///
+ /// 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.
+ ///
+ public virtual void SetTypeMapping([CanBeNull] RelationalTypeMapping typeMapping, ConfigurationSource configurationSource)
+ {
+ _typeMapping = typeMapping;
+
+ UpdateTypeMappingConfigurationSource(configurationSource);
+ }
+
+ private void UpdateTypeMappingConfigurationSource(ConfigurationSource configurationSource)
+ => _typeMappingConfigurationSource = configurationSource.Max(_typeMappingConfigurationSource);
+
+ ///
+ /// 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.
+ ///
+ public virtual ConfigurationSource? GetTypeMappingConfigurationSource() => _typeMappingConfigurationSource;
+
///
/// 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
@@ -290,8 +377,22 @@ void IConventionDbFunction.SetFunctionName(string name, bool fromDataAnnotation)
void IConventionDbFunction.SetSchema(string schema, bool fromDataAnnotation)
=> SetSchema(schema, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+ void IConventionDbFunction.SetStoreType(string storeType, bool fromDataAnnotation)
+ => SetStoreType(storeType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+
+ void IConventionDbFunction.SetTypeMapping(RelationalTypeMapping returnTypeMapping, bool fromDataAnnotation)
+ => SetTypeMapping(returnTypeMapping, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+
void IConventionDbFunction.SetTranslation(
Func, SqlExpression> translation, bool fromDataAnnotation)
=> SetTranslation(translation, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+
+ ///
+ /// 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.
+ ///
+ public virtual IReadOnlyList Parameters => _parameters;
+
+ IReadOnlyList IConventionDbFunction.Parameters => _parameters;
}
}
diff --git a/src/EFCore.Relational/Metadata/Internal/DbFunctionParameter.cs b/src/EFCore.Relational/Metadata/Internal/DbFunctionParameter.cs
new file mode 100644
index 00000000000..48e953b4825
--- /dev/null
+++ b/src/EFCore.Relational/Metadata/Internal/DbFunctionParameter.cs
@@ -0,0 +1,121 @@
+// 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 JetBrains.Annotations;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+using Microsoft.EntityFrameworkCore.Storage;
+using Microsoft.EntityFrameworkCore.Utilities;
+
+namespace Microsoft.EntityFrameworkCore.Metadata.Internal
+{
+ ///
+ /// 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.
+ ///
+ public class DbFunctionParameter : IMutableDbFunctionParameter, IConventionDbFunctionParameter
+ {
+ private readonly IMutableDbFunction _parent;
+ private readonly string _name;
+ private readonly Type _clrType;
+ private bool _supportsNullPropagation;
+ private string _storeType;
+ private RelationalTypeMapping _typeMapping;
+
+ private ConfigurationSource? _supportsNullPropagationConfigurationSource;
+ private ConfigurationSource? _storeTypeConfigurationSource;
+ private ConfigurationSource? _typeMappingConfigurationSource;
+
+
+ ///
+ /// 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.
+ ///
+ public DbFunctionParameter([NotNull] IMutableDbFunction parent, [NotNull] string name, [NotNull] Type clrType)
+ {
+ Check.NotEmpty(name, nameof(name));
+ Check.NotNull(clrType, nameof(clrType));
+ Check.NotNull(parent, nameof(parent));
+
+ _name = name;
+ _parent = parent;
+ _clrType = clrType;
+ }
+
+ public virtual IConventionDbFunctionParameterBuilder Builder => new DbFunctionParameterBuilder(this);
+
+ public virtual bool SupportsNullPropagation
+ {
+ get => _supportsNullPropagation;
+ set => SetSupportsNullPropagation(value, ConfigurationSource.Explicit);
+ }
+
+ public virtual void SetSupportsNullPropagation(bool supportsNullPropagation, ConfigurationSource configurationSource)
+ {
+ _supportsNullPropagation = supportsNullPropagation;
+
+ UpdateSupportsNullPropagationConfigurationSource(configurationSource);
+ }
+
+ private void UpdateSupportsNullPropagationConfigurationSource(ConfigurationSource configurationSource)
+ => _supportsNullPropagationConfigurationSource = configurationSource.Max(_supportsNullPropagationConfigurationSource);
+
+ public virtual ConfigurationSource? GetSupportsNullPropagationConfigurationSource() => _supportsNullPropagationConfigurationSource;
+
+ public virtual string StoreType
+ {
+ get => _storeType;
+ set => SetStoreType(value, ConfigurationSource.Explicit);
+ }
+
+ public virtual void SetStoreType([CanBeNull] string storeType, ConfigurationSource configurationSource)
+ {
+ _storeType = storeType;
+
+ UpdateStoreTypeConfigurationSource(configurationSource);
+ }
+
+ private void UpdateStoreTypeConfigurationSource(ConfigurationSource configurationSource)
+ => _storeTypeConfigurationSource = configurationSource.Max(_storeTypeConfigurationSource);
+
+ public virtual ConfigurationSource? GetStoreTypeConfigurationSource() => _storeTypeConfigurationSource;
+
+ public virtual RelationalTypeMapping TypeMapping
+ {
+ get => _typeMapping;
+ set => SetTypeMapping(value, ConfigurationSource.Explicit);
+ }
+
+ public virtual void SetTypeMapping(RelationalTypeMapping typeMapping, ConfigurationSource configurationSource)
+ {
+ _typeMapping = typeMapping;
+
+ UpdateTypeMappingConfigurationSource(configurationSource);
+ }
+
+ private void UpdateTypeMappingConfigurationSource(ConfigurationSource configurationSource)
+ => _typeMappingConfigurationSource = configurationSource.Max(_typeMappingConfigurationSource);
+
+ public virtual ConfigurationSource? GetTypeMappingConfigurationSource() => _typeMappingConfigurationSource;
+
+ IConventionDbFunction IConventionDbFunctionParameter.Parent => (IConventionDbFunction)_parent;
+
+ IDbFunction IDbFunctionParameter.Parent => _parent;
+
+ IMutableDbFunction IMutableDbFunctionParameter.Parent => _parent;
+
+ string IDbFunctionParameter.Name => _name;
+
+ Type IDbFunctionParameter.ClrType => _clrType;
+
+ void IConventionDbFunctionParameter.SetStoreType(string storeType, bool fromDataAnnotation)
+ => SetStoreType(storeType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+
+ void IConventionDbFunctionParameter.SetSupportsNullPropagation(bool supportsNullPropagation, bool fromDataAnnotation)
+ => SetSupportsNullPropagation(supportsNullPropagation,
+ fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+
+ void IConventionDbFunctionParameter.SetTypeMapping(RelationalTypeMapping typeMapping, bool fromDataAnnotation)
+ => SetTypeMapping(typeMapping, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+ }
+}
diff --git a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs
index d471c519e94..f01dbab60ae 100644
--- a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs
+++ b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs
@@ -1,10 +1,11 @@
-//
+//
using System;
using System.Reflection;
using System.Resources;
using System.Threading;
using JetBrains.Annotations;
+using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.Extensions.Logging;
namespace Microsoft.EntityFrameworkCore.Diagnostics
@@ -341,7 +342,9 @@ public static string ConflictingColumnServerGeneration([CanBeNull] object confli
/// The check constraint '{checkConstraint}' cannot be added to the entity type '{entityType}' because another check constraint with the same name already exists.
///
public static string DuplicateCheckConstraint([CanBeNull] object checkConstraint, [CanBeNull] object entityType)
- => string.Format(GetString("DuplicateCheckConstraint", nameof(checkConstraint), nameof(entityType)), checkConstraint, entityType);
+ => string.Format(
+ GetString("DuplicateCheckConstraint", nameof(checkConstraint), nameof(entityType)),
+ checkConstraint, entityType);
///
/// The foreign keys {index1} on '{entityType1}' and {index2} on '{entityType2}' are both mapped to '{table}.{foreignKeyName}' but use different columns ({columnNames1} and {columnNames2}).
@@ -461,6 +464,14 @@ public static string DbFunctionInvalidParameterType([CanBeNull] object parameter
GetString("DbFunctionInvalidParameterType", nameof(parameter), nameof(function), nameof(type)),
parameter, function, type);
+ ///
+ /// The DbFunction '{function}' does not have a parameter named '{parameter}'.
+ ///
+ public static string DbFunctionInvalidParameterName([CanBeNull] object function, [CanBeNull] object parameter)
+ => string.Format(
+ GetString("DbFunctionInvalidParameterName", nameof(function), nameof(parameter)),
+ function, parameter);
+
///
/// The DbFunction '{function}' is generic. Generic methods are not supported.
///
diff --git a/src/EFCore.Relational/Properties/RelationalStrings.resx b/src/EFCore.Relational/Properties/RelationalStrings.resx
index 813a2d54ded..d18607a8580 100644
--- a/src/EFCore.Relational/Properties/RelationalStrings.resx
+++ b/src/EFCore.Relational/Properties/RelationalStrings.resx
@@ -410,6 +410,9 @@
The parameter '{parameter}' for the DbFunction '{function}' has an invalid type '{type}'. Ensure the parameter type can be mapped by the current provider.
+
+ The DbFunction '{function}' does not have a parameter named '{parameter}'.
+
The DbFunction '{function}' is generic. Generic methods are not supported.
diff --git a/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs b/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs
index 172406f8512..bf3ac493bd8 100644
--- a/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs
+++ b/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs
@@ -1079,43 +1079,6 @@ var methodInfo
modelBuilder.Model);
}
- [ConditionalFact]
- public virtual void Detects_function_with_invalid_parameter_type_but_translate_callback_does_not_throw()
- {
- var modelBuilder = CreateConventionalModelBuilder();
-
- var methodInfo
- = typeof(DbFunctionMetadataTests.TestMethods)
- .GetRuntimeMethod(
- nameof(DbFunctionMetadataTests.TestMethods.MethodF),
- new[] { typeof(DbFunctionMetadataTests.MyBaseContext) });
-
- var dbFuncBuilder = modelBuilder.HasDbFunction(methodInfo);
-
- dbFuncBuilder.HasTranslation(parameters => null);
-
- Validate(modelBuilder.Model);
- }
-
- [ConditionalFact]
- public virtual void Detects_function_with_invalid_parameter_type_but_no_translate_callback_throws()
- {
- var modelBuilder = CreateConventionalModelBuilder();
-
- var methodInfo
- = typeof(DbFunctionMetadataTests.TestMethods)
- .GetRuntimeMethod(
- nameof(DbFunctionMetadataTests.TestMethods.MethodF),
- new[] { typeof(DbFunctionMetadataTests.MyBaseContext) });
-
- modelBuilder.HasDbFunction(methodInfo);
-
- VerifyError(
- RelationalStrings.DbFunctionInvalidParameterType(
- "context", methodInfo.DisplayName(), typeof(DbFunctionMetadataTests.MyBaseContext).ShortDisplayName()),
- modelBuilder.Model);
- }
-
private static void GenerateMapping(IMutableProperty property)
=> property[CoreAnnotationNames.TypeMapping]
= new TestRelationalTypeMappingSource(
diff --git a/test/EFCore.Relational.Tests/Metadata/DbFunctionMetadataTests.cs b/test/EFCore.Relational.Tests/Metadata/DbFunctionMetadataTests.cs
index a0b2ed6b9e3..5faa67913e8 100644
--- a/test/EFCore.Relational.Tests/Metadata/DbFunctionMetadataTests.cs
+++ b/test/EFCore.Relational.Tests/Metadata/DbFunctionMetadataTests.cs
@@ -168,6 +168,9 @@ public static int DuplicateNameTest()
public static MethodInfo MethodBmi = typeof(TestMethods).GetRuntimeMethod(
nameof(TestMethods.MethodB), new[] { typeof(string), typeof(int) });
+ public static MethodInfo MethodImi = typeof(TestMethods).GetRuntimeMethod(
+ nameof(TestMethods.MethodI), new Type[] { });
+
public static MethodInfo MethodHmi = typeof(TestMethods).GetTypeInfo().GetDeclaredMethod(nameof(TestMethods.MethodH));
public class TestMethods
@@ -203,6 +206,11 @@ public static int MethodH(T a, string b)
{
throw new Exception();
}
+
+ public static int MethodI()
+ {
+ throw new Exception();
+ }
}
[ConditionalFact]
@@ -424,6 +432,16 @@ public void Adding_method_with_relational_schema()
Assert.Equal("dbo", dbFuncBuilder.Metadata.Schema);
}
+ [ConditionalFact]
+ public void Adding_method_with_store_type()
+ {
+ var modelBuilder = GetModelBuilder();
+
+ var dbFuncBuilder = modelBuilder.HasDbFunction(MethodAmi).HasStoreType("int(8)");
+
+ Assert.Equal("int(8)", dbFuncBuilder.Metadata.StoreType);
+ }
+
[ConditionalFact]
public void Adding_method_with_relational_schema_fluent_overrides()
{
@@ -485,15 +503,134 @@ public virtual void Set_empty_function_name_throws()
expectedMessage, Assert.Throws(() => modelBuilder.HasDbFunction(MethodAmi).HasName("")).Message);
}
+ [ConditionalFact]
+ public void DbParameters_load_no_parameters()
+ {
+ var modelBuilder = GetModelBuilder();
+
+ var dbFuncBuilder = modelBuilder.HasDbFunction(MethodImi);
+ var dbFunc = dbFuncBuilder.Metadata;
+
+ Assert.Equal(0, dbFunc.Parameters.Count);
+ }
+
+ [ConditionalFact]
+ public void DbParameters_invalid_parameter_name_throws()
+ {
+ var modelBuilder = GetModelBuilder();
+
+ var dbFuncBuilder = modelBuilder.HasDbFunction(MethodBmi);
+
+ Assert.Equal(
+ RelationalStrings.DbFunctionInvalidParameterName("q", dbFuncBuilder.Metadata.MethodInfo.DisplayName()),
+ Assert.Throws(() => dbFuncBuilder.HasParameter("q")).Message);
+ }
+
+ [ConditionalFact]
+ public void DbParameters_load_with_parameters()
+ {
+ var modelBuilder = GetModelBuilder();
+
+ var dbFuncBuilder = modelBuilder.HasDbFunction(MethodBmi);
+ var dbFunc = dbFuncBuilder.Metadata;
+
+ Assert.Equal(2, dbFunc.Parameters.Count);
+
+ Assert.Equal("c", dbFunc.Parameters[0].Name);
+ Assert.Equal(typeof(string), dbFunc.Parameters[0].ClrType);
+
+ Assert.Equal("d", dbFunc.Parameters[1].Name);
+ Assert.Equal(typeof(int), dbFunc.Parameters[1].ClrType);
+ }
+
+ [ConditionalFact]
+ public void DbParameters_dbfunctionType()
+ {
+ var modelBuilder = GetModelBuilder();
+
+ var dbFuncBuilder = modelBuilder.HasDbFunction(MethodBmi);
+ var dbFunc = dbFuncBuilder.Metadata;
+
+ dbFuncBuilder.HasParameter("c");
+
+ Assert.Equal(2, dbFunc.Parameters.Count);
+
+ Assert.Equal("c", dbFunc.Parameters[0].Name);
+ Assert.Equal(typeof(string), dbFunc.Parameters[0].ClrType);
+
+ Assert.Equal("d", dbFunc.Parameters[1].Name);
+ Assert.Equal(typeof(int), dbFunc.Parameters[1].ClrType);
+ }
+
+ [ConditionalFact]
+ public void DbParameters_name()
+ {
+ var modelBuilder = GetModelBuilder();
+
+ var dbFuncBuilder = modelBuilder.HasDbFunction(MethodBmi);
+ var dbFunc = dbFuncBuilder.Metadata;
+
+ dbFuncBuilder.HasParameter("c");
+
+ Assert.Equal(2, dbFunc.Parameters.Count);
+
+ Assert.Equal("c", dbFunc.Parameters[0].Name);
+ Assert.Equal(typeof(string), dbFunc.Parameters[0].ClrType);
+
+ Assert.Equal("d", dbFunc.Parameters[1].Name);
+ Assert.Equal(typeof(int), dbFunc.Parameters[1].ClrType);
+ }
+
+ [ConditionalFact]
+ public void DbParameters_NullabilityPropagation()
+ {
+ var modelBuilder = GetModelBuilder();
+
+ var dbFuncBuilder = modelBuilder.HasDbFunction(MethodBmi);
+ var dbFunc = dbFuncBuilder.Metadata;
+
+ dbFuncBuilder.HasParameter("c").HasNullabilityPropagation(true);
+
+ Assert.Equal(2, dbFunc.Parameters.Count);
+
+ Assert.Equal("c", dbFunc.Parameters[0].Name);
+ Assert.Equal(typeof(string), dbFunc.Parameters[0].ClrType);
+ Assert.Equal(true, dbFunc.Parameters[0].SupportsNullPropagation);
+
+ Assert.Equal("d", dbFunc.Parameters[1].Name);
+ Assert.Equal(typeof(int), dbFunc.Parameters[1].ClrType);
+ }
+
+ [ConditionalFact]
+ public void DbParameters_StoreType()
+ {
+ var modelBuilder = GetModelBuilder();
+
+ var dbFuncBuilder = modelBuilder.HasDbFunction(MethodBmi);
+ var dbFunc = dbFuncBuilder.Metadata;
+
+ dbFuncBuilder.HasParameter("c").HasStoreType("varchar(max)");
+
+ Assert.Equal(2, dbFunc.Parameters.Count);
+
+ Assert.Equal("c", dbFunc.Parameters[0].Name);
+ Assert.Equal(typeof(string), dbFunc.Parameters[0].ClrType);
+ Assert.Equal("varchar(max)", dbFunc.Parameters[0].StoreType);
+
+ Assert.Equal("d", dbFunc.Parameters[1].Name);
+ Assert.Equal(typeof(int), dbFunc.Parameters[1].ClrType);
+ }
+
private ModelBuilder GetModelBuilder(DbContext dbContext = null)
{
var conventionSet = new ConventionSet();
- var dbFunctionAttributeConvention =new RelationalDbFunctionAttributeConvention(
- CreateDependencies().With(new CurrentDbContext (dbContext ?? new DbContext(new DbContextOptions()))),
- CreateRelationalDependencies());
+ var dependencies = CreateDependencies().With(new CurrentDbContext(dbContext ?? new DbContext(new DbContextOptions())));
+ var relationalDependencies = CreateRelationalDependencies();
+ var dbFunctionAttributeConvention = new RelationalDbFunctionAttributeConvention(dependencies, relationalDependencies);
conventionSet.ModelInitializedConventions.Add(dbFunctionAttributeConvention);
conventionSet.ModelAnnotationChangedConventions.Add(dbFunctionAttributeConvention);
+ conventionSet.ModelFinalizedConventions.Add(new DbFunctionTypeMappingConvention(dependencies, relationalDependencies));
return new ModelBuilder(conventionSet);
}
diff --git a/test/EFCore.Relational.Tests/Metadata/RelationalMetadataExtensionsTest.cs b/test/EFCore.Relational.Tests/Metadata/RelationalMetadataExtensionsTest.cs
index 6d9d64de789..e00c57ca23f 100644
--- a/test/EFCore.Relational.Tests/Metadata/RelationalMetadataExtensionsTest.cs
+++ b/test/EFCore.Relational.Tests/Metadata/RelationalMetadataExtensionsTest.cs
@@ -382,7 +382,7 @@ public void Can_get_and_set_dbfunction()
var dbFunc = model.AddDbFunction(testMethod);
Assert.NotNull(dbFunc);
- Assert.Null(dbFunc.FunctionName);
+ Assert.NotNull(dbFunc.FunctionName);
Assert.Null(dbFunc.Schema);
}