From b775a93521b5e031d2521a01da144d2a6b5c5922 Mon Sep 17 00:00:00 2001 From: Smit Patel Date: Wed, 19 Jun 2019 16:10:10 -0700 Subject: [PATCH] Query: Add builtIn flag to SqlFunction and use it to quoting logic Fuctions without arguments passed considered niladic. Non-niladic functions without arguments must pass empty array of args. Functions with instance are considered built in. Functions without schema are considered built in. Functions with schema are non-built-in (Schema can be null) To map a DbFunction to built-in function use HasTranslation API Resolves #12757 Resolves #14882 Resolves #15501 --- .../Query/Pipeline/ISqlExpressionFactory.cs | 1 - .../Metadata/Internal/DbFunction.cs | 10 +-- .../Query/Pipeline/ISqlExpressionFactory.cs | 6 +- .../Query/Pipeline/QuerySqlGenerator.cs | 21 +++-- .../Query/Pipeline/SqlExpressionFactory.cs | 13 ++- .../SqlExpressions/SqlFunctionExpression.cs | 29 ++++--- ...erverGeometryCollectionMemberTranslator.cs | 2 +- .../SqlServerGeometryMemberTranslator.cs | 5 +- .../SqlServerLineStringMemberTranslator.cs | 2 +- .../SqlServerMultiCurveMemberTranslator.cs | 2 +- .../SqlServerPointMemberTranslator.cs | 1 - .../SqlServerPolygonMemberTranslator.cs | 4 +- .../SqlServerConventionSetBuilder.cs | 4 - .../SqlServerDbFunctionAttributeConvention.cs | 43 ---------- .../Conventions/SqlServerIndexConvention.cs | 2 +- .../SqlServerDateTimeMemberTranslator.cs | 6 +- .../Pipeline/SqlServerNewGuidTranslator.cs | 2 +- .../Pipeline/SqlServerQuerySqlGenerator.cs | 18 ++++ .../Query/UdfDbFunctionTestBase.cs | 10 ++- .../SqlServerDbFunctionMetadataTests.cs | 83 ------------------- 20 files changed, 77 insertions(+), 187 deletions(-) delete mode 100644 src/EFCore.SqlServer/Metadata/Conventions/SqlServerDbFunctionAttributeConvention.cs delete mode 100644 test/EFCore.SqlServer.FunctionalTests/SqlServerDbFunctionMetadataTests.cs diff --git a/src/EFCore.Cosmos/Query/Pipeline/ISqlExpressionFactory.cs b/src/EFCore.Cosmos/Query/Pipeline/ISqlExpressionFactory.cs index a0b4320b715..f75854cde7f 100644 --- a/src/EFCore.Cosmos/Query/Pipeline/ISqlExpressionFactory.cs +++ b/src/EFCore.Cosmos/Query/Pipeline/ISqlExpressionFactory.cs @@ -13,7 +13,6 @@ public interface ISqlExpressionFactory { SqlExpression ApplyTypeMapping(SqlExpression sqlExpression, CoreTypeMapping typeMapping); SqlExpression ApplyDefaultTypeMapping(SqlExpression sqlExpression); - //CoreTypeMapping GetTypeMappingForValue(object value); CoreTypeMapping FindMapping(Type type); SqlBinaryExpression MakeBinary(ExpressionType operatorType, SqlExpression left, SqlExpression right, CoreTypeMapping typeMapping); diff --git a/src/EFCore.Relational/Metadata/Internal/DbFunction.cs b/src/EFCore.Relational/Metadata/Internal/DbFunction.cs index 3f63200566d..d67d2433216 100644 --- a/src/EFCore.Relational/Metadata/Internal/DbFunction.cs +++ b/src/EFCore.Relational/Metadata/Internal/DbFunction.cs @@ -141,14 +141,6 @@ public virtual ConfigurationSource GetConfigurationSource() public virtual void UpdateConfigurationSource(ConfigurationSource configurationSource) => ((Model)_model).FindAnnotation(_annotationName).UpdateConfigurationSource(configurationSource); - /// - /// 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 DefaultSchema { get; [param: CanBeNull] set; } - /// /// 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 @@ -157,7 +149,7 @@ public virtual void UpdateConfigurationSource(ConfigurationSource configurationS /// public virtual string Schema { - get => _schema ?? _model.GetDefaultSchema() ?? DefaultSchema; + get => _schema ?? _model.GetDefaultSchema(); set => SetSchema(value, ConfigurationSource.Explicit); } diff --git a/src/EFCore.Relational/Query/Pipeline/ISqlExpressionFactory.cs b/src/EFCore.Relational/Query/Pipeline/ISqlExpressionFactory.cs index 902cbc9e1a7..562126421c7 100644 --- a/src/EFCore.Relational/Query/Pipeline/ISqlExpressionFactory.cs +++ b/src/EFCore.Relational/Query/Pipeline/ISqlExpressionFactory.cs @@ -56,11 +56,11 @@ SqlFunctionExpression Function( SqlFunctionExpression Function( SqlExpression instance, string functionName, IEnumerable arguments, Type returnType, RelationalTypeMapping typeMapping = null); SqlFunctionExpression Function( - string functionName, bool niladic, Type returnType, RelationalTypeMapping typeMapping = null); + string functionName, Type returnType, RelationalTypeMapping typeMapping = null); SqlFunctionExpression Function( - string schema, string functionName, bool niladic, Type returnType, RelationalTypeMapping typeMapping = null); + string schema, string functionName, Type returnType, RelationalTypeMapping typeMapping = null); SqlFunctionExpression Function( - SqlExpression instance, string functionName, bool niladic, Type returnType, RelationalTypeMapping typeMapping = null); + SqlExpression instance, string functionName, Type returnType, RelationalTypeMapping typeMapping = null); ExistsExpression Exists(SelectExpression subquery, bool negated); InExpression In(SqlExpression item, SqlExpression values, bool negated); diff --git a/src/EFCore.Relational/Query/Pipeline/QuerySqlGenerator.cs b/src/EFCore.Relational/Query/Pipeline/QuerySqlGenerator.cs index 853d484a892..ec86119e76d 100644 --- a/src/EFCore.Relational/Query/Pipeline/QuerySqlGenerator.cs +++ b/src/EFCore.Relational/Query/Pipeline/QuerySqlGenerator.cs @@ -163,14 +163,7 @@ protected override Expression VisitProjection(ProjectionExpression projectionExp protected override Expression VisitSqlFunction(SqlFunctionExpression sqlFunctionExpression) { - if (!string.IsNullOrEmpty(sqlFunctionExpression.Schema)) - { - _relationalCommandBuilder - .Append(_sqlGenerationHelper.DelimitIdentifier(sqlFunctionExpression.Schema)) - .Append(".") - .Append(_sqlGenerationHelper.DelimitIdentifier(sqlFunctionExpression.FunctionName)); - } - else + if (sqlFunctionExpression.IsBuiltIn) { if (sqlFunctionExpression.Instance != null) { @@ -180,6 +173,18 @@ protected override Expression VisitSqlFunction(SqlFunctionExpression sqlFunction _relationalCommandBuilder.Append(sqlFunctionExpression.FunctionName); } + else + { + if (!string.IsNullOrEmpty(sqlFunctionExpression.Schema)) + { + _relationalCommandBuilder + .Append(_sqlGenerationHelper.DelimitIdentifier(sqlFunctionExpression.Schema)) + .Append("."); + } + + _relationalCommandBuilder + .Append(_sqlGenerationHelper.DelimitIdentifier(sqlFunctionExpression.FunctionName)); + } if (!sqlFunctionExpression.IsNiladic) { diff --git a/src/EFCore.Relational/Query/Pipeline/SqlExpressionFactory.cs b/src/EFCore.Relational/Query/Pipeline/SqlExpressionFactory.cs index 9fd80f22455..60b65babcbd 100644 --- a/src/EFCore.Relational/Query/Pipeline/SqlExpressionFactory.cs +++ b/src/EFCore.Relational/Query/Pipeline/SqlExpressionFactory.cs @@ -406,7 +406,6 @@ public SqlFunctionExpression Function( string schema, string functionName, IEnumerable arguments, Type returnType, RelationalTypeMapping typeMapping = null) { var typeMappedArguments = new List(); - foreach (var argument in arguments) { typeMappedArguments.Add(ApplyDefaultTypeMapping(argument)); @@ -439,22 +438,22 @@ public SqlFunctionExpression Function( } public SqlFunctionExpression Function( - string functionName, bool niladic, Type returnType, RelationalTypeMapping typeMapping = null) + string functionName, Type returnType, RelationalTypeMapping typeMapping = null) { - return new SqlFunctionExpression(functionName, niladic, returnType, typeMapping); + return new SqlFunctionExpression(functionName, returnType, typeMapping); } public SqlFunctionExpression Function( - string schema, string functionName, bool niladic, Type returnType, RelationalTypeMapping typeMapping = null) + string schema, string functionName, Type returnType, RelationalTypeMapping typeMapping = null) { - return new SqlFunctionExpression(schema, functionName, niladic, returnType, typeMapping); + return new SqlFunctionExpression(schema, functionName, returnType, typeMapping); } public SqlFunctionExpression Function( - SqlExpression instance, string functionName, bool niladic, Type returnType, RelationalTypeMapping typeMapping = null) + SqlExpression instance, string functionName, Type returnType, RelationalTypeMapping typeMapping = null) { instance = ApplyDefaultTypeMapping(instance); - return new SqlFunctionExpression(instance, functionName, niladic, returnType, typeMapping); + return new SqlFunctionExpression(instance, functionName, returnType, typeMapping); } public ExistsExpression Exists(SelectExpression subquery, bool negated) diff --git a/src/EFCore.Relational/Query/Pipeline/SqlExpressions/SqlFunctionExpression.cs b/src/EFCore.Relational/Query/Pipeline/SqlExpressions/SqlFunctionExpression.cs index c8edfe33828..724996a8656 100644 --- a/src/EFCore.Relational/Query/Pipeline/SqlExpressions/SqlFunctionExpression.cs +++ b/src/EFCore.Relational/Query/Pipeline/SqlExpressions/SqlFunctionExpression.cs @@ -12,61 +12,61 @@ namespace Microsoft.EntityFrameworkCore.Relational.Query.Pipeline.SqlExpressions { public class SqlFunctionExpression : SqlExpression { + // niladic public SqlFunctionExpression( string functionName, - bool niladic, Type type, RelationalTypeMapping typeMapping) - : this(null, null, functionName, niladic, null, type, typeMapping) + : this(instance: null, schema: null, functionName, niladic: true, arguments: null, builtIn: true, type, typeMapping) { } + // niladic public SqlFunctionExpression( string schema, string functionName, - bool niladic, Type type, RelationalTypeMapping typeMapping) - : this(null, schema, functionName, niladic, null, type, typeMapping) + : this(instance: null, schema, functionName, niladic: true, arguments: null, builtIn: true, type, typeMapping) { } + // niladic public SqlFunctionExpression( SqlExpression instance, string functionName, - bool niladic, Type type, RelationalTypeMapping typeMapping) - : this(instance, null, functionName, niladic, null, type, typeMapping) + : this(instance, schema: null, functionName, niladic: true, arguments: null, builtIn: true, type, typeMapping) { } public SqlFunctionExpression( + SqlExpression instance, string functionName, IEnumerable arguments, Type type, RelationalTypeMapping typeMapping) - : this(null, null, functionName, false, arguments, type, typeMapping) + : this(instance, schema: null, functionName, niladic: false, arguments, builtIn: true, type, typeMapping) { } public SqlFunctionExpression( - string schema, string functionName, IEnumerable arguments, Type type, RelationalTypeMapping typeMapping) - : this(null, schema, functionName, false, arguments, type, typeMapping) + : this(instance: null, schema: null, functionName, niladic: false, arguments, builtIn: true, type, typeMapping) { } public SqlFunctionExpression( - SqlExpression instance, + string schema, string functionName, IEnumerable arguments, Type type, RelationalTypeMapping typeMapping) - : this(instance, null, functionName, false, arguments, type, typeMapping) + : this(instance: null, schema, functionName, niladic: false, arguments, builtIn: false, type, typeMapping) { } @@ -76,6 +76,7 @@ private SqlFunctionExpression( string functionName, bool niladic, IEnumerable arguments, + bool builtIn, Type type, RelationalTypeMapping typeMapping) : base(type, typeMapping) @@ -84,12 +85,14 @@ private SqlFunctionExpression( FunctionName = functionName; Schema = schema; IsNiladic = niladic; + IsBuiltIn = builtIn; Arguments = (arguments ?? Array.Empty()).ToList(); } public string FunctionName { get; } public string Schema { get; } public bool IsNiladic { get; } + public bool IsBuiltIn { get; } public IReadOnlyList Arguments { get; } public Expression Instance { get; } @@ -112,6 +115,7 @@ protected override Expression VisitChildren(ExpressionVisitor visitor) FunctionName, IsNiladic, arguments, + IsBuiltIn, Type, TypeMapping) : this; @@ -124,12 +128,13 @@ public SqlFunctionExpression ApplyTypeMapping(RelationalTypeMapping typeMapping) FunctionName, IsNiladic, Arguments, + IsBuiltIn, Type, typeMapping ?? TypeMapping); public SqlFunctionExpression Update(SqlExpression instance, IReadOnlyList arguments) => instance != Instance || !arguments.SequenceEqual(Arguments) - ? new SqlFunctionExpression(instance, Schema, FunctionName, IsNiladic, arguments, Type, TypeMapping) + ? new SqlFunctionExpression(instance, Schema, FunctionName, IsNiladic, arguments, IsBuiltIn, Type, TypeMapping) : this; public override void Print(ExpressionPrinter expressionPrinter) diff --git a/src/EFCore.SqlServer.NTS/Query/Pipeline/SqlServerGeometryCollectionMemberTranslator.cs b/src/EFCore.SqlServer.NTS/Query/Pipeline/SqlServerGeometryCollectionMemberTranslator.cs index af5dd910fd4..a4229a56b2d 100644 --- a/src/EFCore.SqlServer.NTS/Query/Pipeline/SqlServerGeometryCollectionMemberTranslator.cs +++ b/src/EFCore.SqlServer.NTS/Query/Pipeline/SqlServerGeometryCollectionMemberTranslator.cs @@ -26,7 +26,7 @@ public SqlExpression Translate(SqlExpression instance, MemberInfo member, Type r return _sqlExpressionFactory.Function( instance, "STNumGeometries", - false, + Array.Empty(), returnType); } diff --git a/src/EFCore.SqlServer.NTS/Query/Pipeline/SqlServerGeometryMemberTranslator.cs b/src/EFCore.SqlServer.NTS/Query/Pipeline/SqlServerGeometryMemberTranslator.cs index b3dbfdb0b85..7fadb072ef4 100644 --- a/src/EFCore.SqlServer.NTS/Query/Pipeline/SqlServerGeometryMemberTranslator.cs +++ b/src/EFCore.SqlServer.NTS/Query/Pipeline/SqlServerGeometryMemberTranslator.cs @@ -68,7 +68,7 @@ public SqlExpression Translate(SqlExpression instance, MemberInfo member, Type r return _sqlExpressionFactory.Function( instance, functionName, - false, + Array.Empty(), returnType, resultTypeMapping); } @@ -98,7 +98,7 @@ public SqlExpression Translate(SqlExpression instance, MemberInfo member, Type r _sqlExpressionFactory.Function( instance, "STGeometryType", - false, + Array.Empty(), typeof(string)), whenClauses.ToArray()); } @@ -108,7 +108,6 @@ public SqlExpression Translate(SqlExpression instance, MemberInfo member, Type r return _sqlExpressionFactory.Function( instance, "STSrid", - true, returnType); } } diff --git a/src/EFCore.SqlServer.NTS/Query/Pipeline/SqlServerLineStringMemberTranslator.cs b/src/EFCore.SqlServer.NTS/Query/Pipeline/SqlServerLineStringMemberTranslator.cs index 80f503caf13..afa33e1095b 100644 --- a/src/EFCore.SqlServer.NTS/Query/Pipeline/SqlServerLineStringMemberTranslator.cs +++ b/src/EFCore.SqlServer.NTS/Query/Pipeline/SqlServerLineStringMemberTranslator.cs @@ -26,7 +26,7 @@ public SqlExpression Translate(SqlExpression instance, MemberInfo member, Type r return _sqlExpressionFactory.Function( instance, "STNumPoints", - false, + Array.Empty(), returnType); } diff --git a/src/EFCore.SqlServer.NTS/Query/Pipeline/SqlServerMultiCurveMemberTranslator.cs b/src/EFCore.SqlServer.NTS/Query/Pipeline/SqlServerMultiCurveMemberTranslator.cs index fab3b14b4e2..03e74a243ad 100644 --- a/src/EFCore.SqlServer.NTS/Query/Pipeline/SqlServerMultiCurveMemberTranslator.cs +++ b/src/EFCore.SqlServer.NTS/Query/Pipeline/SqlServerMultiCurveMemberTranslator.cs @@ -26,7 +26,7 @@ public SqlExpression Translate(SqlExpression instance, MemberInfo member, Type r return _sqlExpressionFactory.Function( instance, "STIsClosed", - false, + Array.Empty(), returnType); } diff --git a/src/EFCore.SqlServer.NTS/Query/Pipeline/SqlServerPointMemberTranslator.cs b/src/EFCore.SqlServer.NTS/Query/Pipeline/SqlServerPointMemberTranslator.cs index f2a34c809ef..e4e4ad30b5f 100644 --- a/src/EFCore.SqlServer.NTS/Query/Pipeline/SqlServerPointMemberTranslator.cs +++ b/src/EFCore.SqlServer.NTS/Query/Pipeline/SqlServerPointMemberTranslator.cs @@ -55,7 +55,6 @@ public SqlExpression Translate(SqlExpression instance, MemberInfo member, Type r return _sqlExpressionFactory.Function( instance, propertyName, - true, returnType); } } diff --git a/src/EFCore.SqlServer.NTS/Query/Pipeline/SqlServerPolygonMemberTranslator.cs b/src/EFCore.SqlServer.NTS/Query/Pipeline/SqlServerPolygonMemberTranslator.cs index ccb50020642..7867de38e2b 100644 --- a/src/EFCore.SqlServer.NTS/Query/Pipeline/SqlServerPolygonMemberTranslator.cs +++ b/src/EFCore.SqlServer.NTS/Query/Pipeline/SqlServerPolygonMemberTranslator.cs @@ -60,7 +60,7 @@ public SqlExpression Translate(SqlExpression instance, MemberInfo member, Type r _sqlExpressionFactory.Function( instance, "NumRings", - false, + Array.Empty(), returnType), _sqlExpressionFactory.Constant(1)); } @@ -75,7 +75,7 @@ public SqlExpression Translate(SqlExpression instance, MemberInfo member, Type r return _sqlExpressionFactory.Function( instance, functionName, - false, + Array.Empty(), returnType, resultTypeMapping); } diff --git a/src/EFCore.SqlServer/Metadata/Conventions/SqlServerConventionSetBuilder.cs b/src/EFCore.SqlServer/Metadata/Conventions/SqlServerConventionSetBuilder.cs index 4774a98ea4a..dda38ebbeab 100644 --- a/src/EFCore.SqlServer/Metadata/Conventions/SqlServerConventionSetBuilder.cs +++ b/src/EFCore.SqlServer/Metadata/Conventions/SqlServerConventionSetBuilder.cs @@ -92,10 +92,6 @@ public override ConventionSet CreateConventionSet() ReplaceConvention( conventionSet.PropertyAnnotationChangedConventions, (RelationalValueGenerationConvention)valueGenerationConvention); - ReplaceConvention( - conventionSet.ModelAnnotationChangedConventions, - (RelationalDbFunctionAttributeConvention)new SqlServerDbFunctionAttributeConvention(Dependencies, RelationalDependencies)); - ReplaceConvention(conventionSet.ModelFinalizedConventions, storeGenerationConvention); return conventionSet; diff --git a/src/EFCore.SqlServer/Metadata/Conventions/SqlServerDbFunctionAttributeConvention.cs b/src/EFCore.SqlServer/Metadata/Conventions/SqlServerDbFunctionAttributeConvention.cs deleted file mode 100644 index dba56194f0f..00000000000 --- a/src/EFCore.SqlServer/Metadata/Conventions/SqlServerDbFunctionAttributeConvention.cs +++ /dev/null @@ -1,43 +0,0 @@ -// 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.Builders; -using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata.Internal; - -// ReSharper disable once CheckNamespace -namespace Microsoft.EntityFrameworkCore.Metadata.Conventions -{ - /// - /// A convention that configures model function mappings based on public static methods on the context marked with - /// and sets the default schema to 'dbo'. - /// - public class SqlServerDbFunctionAttributeConvention : RelationalDbFunctionAttributeConvention - { - /// - /// Creates a new instance of . - /// - /// Parameter object containing dependencies for this convention. - /// Parameter object containing relational dependencies for this convention. - public SqlServerDbFunctionAttributeConvention( - [NotNull] ProviderConventionSetBuilderDependencies dependencies, - [NotNull] RelationalConventionSetBuilderDependencies relationalDependencies) - : base(dependencies, relationalDependencies) - { - } - - /// - /// Called when an is added to the model. - /// - /// The builder for the . - /// Additional information associated with convention execution. - protected override void ProcessDbFunctionAdded( - IConventionDbFunctionBuilder dbFunctionBuilder, IConventionContext context) - { - base.ProcessDbFunctionAdded(dbFunctionBuilder, context); - - ((DbFunction)dbFunctionBuilder.Metadata).DefaultSchema = "dbo"; - } - } -} diff --git a/src/EFCore.SqlServer/Metadata/Conventions/SqlServerIndexConvention.cs b/src/EFCore.SqlServer/Metadata/Conventions/SqlServerIndexConvention.cs index b2707ec6de6..093a9770403 100644 --- a/src/EFCore.SqlServer/Metadata/Conventions/SqlServerIndexConvention.cs +++ b/src/EFCore.SqlServer/Metadata/Conventions/SqlServerIndexConvention.cs @@ -27,7 +27,7 @@ public class SqlServerIndexConvention : private readonly ISqlGenerationHelper _sqlGenerationHelper; /// - /// Creates a new instance of . + /// Creates a new instance of . /// /// Parameter object containing dependencies for this convention. /// Parameter object containing relational dependencies for this convention. diff --git a/src/EFCore.SqlServer/Query/Pipeline/SqlServerDateTimeMemberTranslator.cs b/src/EFCore.SqlServer/Query/Pipeline/SqlServerDateTimeMemberTranslator.cs index 35f33151314..60d1a6d3c83 100644 --- a/src/EFCore.SqlServer/Query/Pipeline/SqlServerDateTimeMemberTranslator.cs +++ b/src/EFCore.SqlServer/Query/Pipeline/SqlServerDateTimeMemberTranslator.cs @@ -69,13 +69,13 @@ public SqlExpression Translate(SqlExpression instance, MemberInfo member, Type r case nameof(DateTime.Now): return _sqlExpressionFactory.Function( declaringType == typeof(DateTime) ? "GETDATE" : "SYSDATETIMEOFFSET", - false, + Array.Empty(), returnType); case nameof(DateTime.UtcNow): var serverTranslation = _sqlExpressionFactory.Function( declaringType == typeof(DateTime) ? "GETUTCDATE" : "SYSUTCDATETIME", - false, + Array.Empty(), returnType); return declaringType == typeof(DateTime) @@ -90,7 +90,7 @@ public SqlExpression Translate(SqlExpression instance, MemberInfo member, Type r _sqlExpressionFactory.Fragment("date"), _sqlExpressionFactory.Function( "GETDATE", - false, + Array.Empty(), typeof(DateTime)) }, returnType); diff --git a/src/EFCore.SqlServer/Query/Pipeline/SqlServerNewGuidTranslator.cs b/src/EFCore.SqlServer/Query/Pipeline/SqlServerNewGuidTranslator.cs index 988548f7a3e..a4754a84090 100644 --- a/src/EFCore.SqlServer/Query/Pipeline/SqlServerNewGuidTranslator.cs +++ b/src/EFCore.SqlServer/Query/Pipeline/SqlServerNewGuidTranslator.cs @@ -24,7 +24,7 @@ public SqlExpression Translate(SqlExpression instance, MethodInfo method, IList< return _methodInfo.Equals(method) ? _sqlExpressionFactory.Function( "NEWID", - false, + Array.Empty(), method.ReturnType) : null; } diff --git a/src/EFCore.SqlServer/Query/Pipeline/SqlServerQuerySqlGenerator.cs b/src/EFCore.SqlServer/Query/Pipeline/SqlServerQuerySqlGenerator.cs index 48317a054ea..2cc0f5caaa4 100644 --- a/src/EFCore.SqlServer/Query/Pipeline/SqlServerQuerySqlGenerator.cs +++ b/src/EFCore.SqlServer/Query/Pipeline/SqlServerQuerySqlGenerator.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.Linq.Expressions; using Microsoft.EntityFrameworkCore.Relational.Query.Pipeline; using Microsoft.EntityFrameworkCore.Relational.Query.Pipeline.SqlExpressions; using Microsoft.EntityFrameworkCore.Storage; @@ -51,5 +52,22 @@ protected override void GenerateLimitOffset(SelectExpression selectExpression) } } } + + protected override Expression VisitSqlFunction(SqlFunctionExpression sqlFunctionExpression) + { + if (!sqlFunctionExpression.IsBuiltIn + && string.IsNullOrEmpty(sqlFunctionExpression.Schema)) + { + sqlFunctionExpression = new SqlFunctionExpression( + schema: "dbo", + sqlFunctionExpression.FunctionName, + sqlFunctionExpression.Arguments, + sqlFunctionExpression.Type, + sqlFunctionExpression.TypeMapping); + } + + + return base.VisitSqlFunction(sqlFunctionExpression); + } } } diff --git a/test/EFCore.Relational.Specification.Tests/Query/UdfDbFunctionTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/UdfDbFunctionTestBase.cs index 14eb9bf963a..bc2a99d8f26 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/UdfDbFunctionTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/UdfDbFunctionTestBase.cs @@ -211,7 +211,9 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .HasName("GetCustomerWithMostOrdersAfterDate"); modelBuilder.HasDbFunction(typeof(UDFSqlContext).GetMethod(nameof(GetReportingPeriodStartDateStatic))) .HasName("GetReportingPeriodStartDate"); - modelBuilder.HasDbFunction(typeof(UDFSqlContext).GetMethod(nameof(IsDateStatic))).HasSchema("").HasName("IsDate"); + var isDateMethodInfo = typeof(UDFSqlContext).GetMethod(nameof(IsDateStatic)); + modelBuilder.HasDbFunction(isDateMethodInfo) + .HasTranslation(args => new SqlFunctionExpression("IsDate", args, isDateMethodInfo.ReturnType, null)); var methodInfo = typeof(UDFSqlContext).GetMethod(nameof(MyCustomLengthStatic)); @@ -229,7 +231,9 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .HasName("GetCustomerWithMostOrdersAfterDate"); modelBuilder.HasDbFunction(typeof(UDFSqlContext).GetMethod(nameof(GetReportingPeriodStartDateInstance))) .HasName("GetReportingPeriodStartDate"); - modelBuilder.HasDbFunction(typeof(UDFSqlContext).GetMethod(nameof(IsDateInstance))).HasSchema("").HasName("IsDate"); + var isDateMethodInfo2 = typeof(UDFSqlContext).GetMethod(nameof(IsDateInstance)); + modelBuilder.HasDbFunction(isDateMethodInfo2) + .HasTranslation(args => new SqlFunctionExpression("IsDate", args, isDateMethodInfo2.ReturnType, null)); modelBuilder.HasDbFunction(typeof(UDFSqlContext).GetMethod(nameof(DollarValueInstance))).HasName("DollarValue"); @@ -860,7 +864,7 @@ public virtual void Scalar_Function_With_Translator_Translates_Instance() } } - [ConditionalFact (Skip = "Issue#14935")] + [ConditionalFact(Skip = "Issue#14935")] public virtual void Scalar_Function_ClientEval_Method_As_Translateable_Method_Parameter_Instance() { using (var context = CreateContext()) diff --git a/test/EFCore.SqlServer.FunctionalTests/SqlServerDbFunctionMetadataTests.cs b/test/EFCore.SqlServer.FunctionalTests/SqlServerDbFunctionMetadataTests.cs deleted file mode 100644 index e8f53a6bac7..00000000000 --- a/test/EFCore.SqlServer.FunctionalTests/SqlServerDbFunctionMetadataTests.cs +++ /dev/null @@ -1,83 +0,0 @@ -// 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.Reflection; -using Microsoft.EntityFrameworkCore.Metadata.Conventions; -using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; -using Microsoft.EntityFrameworkCore.TestUtilities; -using Microsoft.Extensions.DependencyInjection; -using Xunit; - -// ReSharper disable InconsistentNaming -namespace Microsoft.EntityFrameworkCore -{ - public class SqlServerDbFunctionMetadataTests - { - public static class TestMethods - { - public static int Foo() - { - throw new Exception(); - } - } - - public static MethodInfo MethodFoo = typeof(TestMethods).GetRuntimeMethod(nameof(TestMethods.Foo), Array.Empty()); - - [ConditionalFact] - public virtual void DbFunction_defaults_schema_to_dbo_if_no_default_schema_or_set_schema() - { - var modelBuilder = GetModelBuilder(); - - var dbFunction = modelBuilder.HasDbFunction(MethodFoo); - - modelBuilder.FinalizeModel(); - - Assert.Equal("dbo", dbFunction.Metadata.Schema); - } - - [ConditionalFact] - public virtual void DbFunction_set_schema_is_not_overridden_by_default_or_dbo() - { - var modelBuilder = GetModelBuilder(); - - modelBuilder.HasDefaultSchema("qwerty"); - - var dbFunction = modelBuilder.HasDbFunction(MethodFoo).HasSchema("abc"); - - modelBuilder.FinalizeModel(); - - Assert.Equal("abc", dbFunction.Metadata.Schema); - } - - [ConditionalFact] - public virtual void DbFunction_default_schema_not_overridden_by_dbo() - { - var modelBuilder = GetModelBuilder(); - - modelBuilder.HasDefaultSchema("qwerty"); - - var dbFunction = modelBuilder.HasDbFunction(MethodFoo); - - modelBuilder.FinalizeModel(); - - Assert.Equal("qwerty", dbFunction.Metadata.Schema); - } - - private ModelBuilder GetModelBuilder() - { - var conventionSet = new ConventionSet(); - - conventionSet.ModelAnnotationChangedConventions.Add( - new SqlServerDbFunctionAttributeConvention(CreateDependencies(), CreateRelationalDependencies())); - - return new ModelBuilder(conventionSet); - } - - private ProviderConventionSetBuilderDependencies CreateDependencies() - => SqlServerTestHelpers.Instance.CreateContextServices().GetRequiredService(); - - private RelationalConventionSetBuilderDependencies CreateRelationalDependencies() - => SqlServerTestHelpers.Instance.CreateContextServices().GetRequiredService(); - } -}