From 62a5f5050e990b451f4fcaf0c1962c6650d72fbb Mon Sep 17 00:00:00 2001 From: Smit Patel Date: Mon, 27 Apr 2020 18:28:09 -0700 Subject: [PATCH] Query: API Cleanup --- .../Query/CollectionInitializingExpression.cs | 78 --- .../Query/CollectionPopulatingExpression.cs | 52 -- .../Query/EntityProjectionExpression.cs | 8 +- .../Query/ISqlExpressionFactory.cs | 26 +- .../Query/Internal/CollateTranslator.cs | 19 +- .../CollectionInitializingExpression.cs | 150 ++++++ .../CollectionPopulatingExpression.cs | 102 ++++ ...omSqlParameterApplyingExpressionVisitor.cs | 28 +- ...sedQueryTranslationPostprocessorFactory.cs | 50 ++ ...qlExpressionOptimizingExpressionVisitor.cs | 63 ++- ...lityBasedSqlProcessingExpressionVisitor.cs | 3 + .../Query/QuerySqlGenerator.cs | 1 - .../RelationalMethodCallTranslatorProvider.cs | 2 +- ...meterBasedQueryTranslationPostprocessor.cs | 2 + ...eryTranslationPostprocessorDependencies.cs | 21 +- ...sedQueryTranslationPostprocessorFactory.cs | 24 - ...RelationalQueryTranslationPostprocessor.cs | 9 +- ...lationalSqlTranslatingExpressionVisitor.cs | 2 +- ...ranslatingExpressionVisitorDependencies.cs | 26 +- .../Query/SqlExpressionFactory.cs | 85 +--- .../Query/SqlExpressions/CaseExpression.cs | 28 +- .../Query/SqlExpressions/CollateExpression.cs | 5 +- .../SqlExpressions/ProjectionExpression.cs | 13 +- .../SqlExpressions/SqlFunctionExpression.cs | 454 +++++++++++------- ...rchConditionConvertingExpressionVisitor.cs | 10 +- .../SqlServerDateTimeMemberTranslator.cs | 11 +- .../SqlServerMemberTranslatorProvider.cs | 10 +- .../Internal/SqlServerQuerySqlGenerator.cs | 23 +- .../Query/UdfDbFunctionTestBase.cs | 87 ++-- .../Query/SpatialQuerySqlServerFixture.cs | 2 +- .../Query/SpatialQuerySqliteFixture.cs | 2 +- 31 files changed, 870 insertions(+), 526 deletions(-) delete mode 100644 src/EFCore.Relational/Query/CollectionInitializingExpression.cs delete mode 100644 src/EFCore.Relational/Query/CollectionPopulatingExpression.cs create mode 100644 src/EFCore.Relational/Query/Internal/CollectionInitializingExpression.cs create mode 100644 src/EFCore.Relational/Query/Internal/CollectionPopulatingExpression.cs rename src/EFCore.Relational/Query/{ => Internal}/FromSqlParameterApplyingExpressionVisitor.cs (74%) create mode 100644 src/EFCore.Relational/Query/Internal/RelationalParameterBasedQueryTranslationPostprocessorFactory.cs delete mode 100644 src/EFCore.Relational/Query/RelationalParameterBasedQueryTranslationPostprocessorFactory.cs rename src/EFCore.Relational/Query/{Internal => }/RelationalSqlTranslatingExpressionVisitorDependencies.cs (81%) diff --git a/src/EFCore.Relational/Query/CollectionInitializingExpression.cs b/src/EFCore.Relational/Query/CollectionInitializingExpression.cs deleted file mode 100644 index 6bfa0f17b5c..00000000000 --- a/src/EFCore.Relational/Query/CollectionInitializingExpression.cs +++ /dev/null @@ -1,78 +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.Linq.Expressions; -using JetBrains.Annotations; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Utilities; - -namespace Microsoft.EntityFrameworkCore.Query -{ - public class CollectionInitializingExpression : Expression, IPrintableExpression - { - public CollectionInitializingExpression( - int collectionId, - [CanBeNull] Expression parent, - [NotNull] Expression parentIdentifier, - [NotNull] Expression outerIdentifier, - [CanBeNull] INavigation navigation, - [NotNull] Type type) - { - Check.NotNull(parentIdentifier, nameof(parentIdentifier)); - Check.NotNull(outerIdentifier, nameof(outerIdentifier)); - Check.NotNull(type, nameof(type)); - - CollectionId = collectionId; - Parent = parent; - ParentIdentifier = parentIdentifier; - OuterIdentifier = outerIdentifier; - Navigation = navigation; - Type = type; - } - - protected override Expression VisitChildren(ExpressionVisitor visitor) - { - Check.NotNull(visitor, nameof(visitor)); - - var parent = visitor.Visit(Parent); - var parentIdentifier = visitor.Visit(ParentIdentifier); - var outerIdentifier = visitor.Visit(OuterIdentifier); - - return parent != Parent || parentIdentifier != ParentIdentifier || outerIdentifier != OuterIdentifier - ? new CollectionInitializingExpression(CollectionId, parent, parentIdentifier, outerIdentifier, Navigation, Type) - : this; - } - - public virtual void Print(ExpressionPrinter expressionPrinter) - { - Check.NotNull(expressionPrinter, nameof(expressionPrinter)); - - expressionPrinter.AppendLine("InitializeCollection:"); - using (expressionPrinter.Indent()) - { - expressionPrinter.AppendLine($"CollectionId: {CollectionId}"); - expressionPrinter.AppendLine($"Navigation: {Navigation?.Name}"); - expressionPrinter.Append("Parent:"); - expressionPrinter.Visit(Parent); - expressionPrinter.AppendLine(); - expressionPrinter.Append("ParentIdentifier:"); - expressionPrinter.Visit(ParentIdentifier); - expressionPrinter.AppendLine(); - expressionPrinter.Append("OuterIdentifier:"); - expressionPrinter.Visit(OuterIdentifier); - expressionPrinter.AppendLine(); - } - } - - public override Type Type { get; } - - public sealed override ExpressionType NodeType => ExpressionType.Extension; - - public virtual int CollectionId { get; } - public virtual Expression Parent { get; } - public virtual Expression ParentIdentifier { get; } - public virtual Expression OuterIdentifier { get; } - public virtual INavigation Navigation { get; } - } -} diff --git a/src/EFCore.Relational/Query/CollectionPopulatingExpression.cs b/src/EFCore.Relational/Query/CollectionPopulatingExpression.cs deleted file mode 100644 index f17ceb6bdec..00000000000 --- a/src/EFCore.Relational/Query/CollectionPopulatingExpression.cs +++ /dev/null @@ -1,52 +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.Linq.Expressions; -using JetBrains.Annotations; -using Microsoft.EntityFrameworkCore.Utilities; - -namespace Microsoft.EntityFrameworkCore.Query -{ - public class CollectionPopulatingExpression : Expression, IPrintableExpression - { - public CollectionPopulatingExpression([NotNull] RelationalCollectionShaperExpression parent, [NotNull] Type type, bool include) - { - Check.NotNull(parent, nameof(parent)); - Check.NotNull(type, nameof(type)); - - Parent = parent; - Type = type; - IsInclude = include; - } - - protected override Expression VisitChildren(ExpressionVisitor visitor) - { - Check.NotNull(visitor, nameof(visitor)); - - var parent = (RelationalCollectionShaperExpression)visitor.Visit(Parent); - - return parent != Parent - ? new CollectionPopulatingExpression(parent, Type, IsInclude) - : this; - } - - public virtual void Print(ExpressionPrinter expressionPrinter) - { - Check.NotNull(expressionPrinter, nameof(expressionPrinter)); - - expressionPrinter.AppendLine("PopulateCollection:"); - using (expressionPrinter.Indent()) - { - expressionPrinter.Append("Parent:"); - expressionPrinter.Visit(Parent); - } - } - - public override Type Type { get; } - - public sealed override ExpressionType NodeType => ExpressionType.Extension; - public virtual RelationalCollectionShaperExpression Parent { get; } - public virtual bool IsInclude { get; } - } -} diff --git a/src/EFCore.Relational/Query/EntityProjectionExpression.cs b/src/EFCore.Relational/Query/EntityProjectionExpression.cs index 9fb9180edee..123b433cc2a 100644 --- a/src/EFCore.Relational/Query/EntityProjectionExpression.cs +++ b/src/EFCore.Relational/Query/EntityProjectionExpression.cs @@ -42,6 +42,10 @@ public EntityProjectionExpression([NotNull] IEntityType entityType, [NotNull] ID _propertyExpressionsCache = propertyExpressions; } + public virtual IEntityType EntityType { get; } + public sealed override ExpressionType NodeType => ExpressionType.Extension; + public override Type Type => EntityType.ClrType; + protected override Expression VisitChildren(ExpressionVisitor visitor) { Check.NotNull(visitor, nameof(visitor)); @@ -109,10 +113,6 @@ public virtual EntityProjectionExpression UpdateEntityType([NotNull] IEntityType return new EntityProjectionExpression(derivedType, propertyExpressionCache); } - public virtual IEntityType EntityType { get; } - public sealed override ExpressionType NodeType => ExpressionType.Extension; - public override Type Type => EntityType.ClrType; - public virtual ColumnExpression BindProperty([NotNull] IProperty property) { Check.NotNull(property, nameof(property)); diff --git a/src/EFCore.Relational/Query/ISqlExpressionFactory.cs b/src/EFCore.Relational/Query/ISqlExpressionFactory.cs index 097b90d7ed3..ea80efa5452 100644 --- a/src/EFCore.Relational/Query/ISqlExpressionFactory.cs +++ b/src/EFCore.Relational/Query/ISqlExpressionFactory.cs @@ -24,10 +24,12 @@ namespace Microsoft.EntityFrameworkCore.Query /// public interface ISqlExpressionFactory { - SqlExpression ApplyTypeMapping([CanBeNull] SqlExpression sqlExpression, [CanBeNull] RelationalTypeMapping typeMapping); - SqlExpression ApplyDefaultTypeMapping([CanBeNull] SqlExpression sqlExpression); + [Obsolete("Use IRelationalTypeMappingSource directly.")] RelationalTypeMapping GetTypeMappingForValue([CanBeNull] object value); + [Obsolete("Use IRelationalTypeMappingSource directly.")] RelationalTypeMapping FindMapping([NotNull] Type type); + SqlExpression ApplyTypeMapping([CanBeNull] SqlExpression sqlExpression, [CanBeNull] RelationalTypeMapping typeMapping); + SqlExpression ApplyDefaultTypeMapping([CanBeNull] SqlExpression sqlExpression); SqlUnaryExpression MakeUnary( ExpressionType operatorType, @@ -43,9 +45,13 @@ SqlBinaryExpression MakeBinary( // Comparison SqlBinaryExpression Equal([NotNull] SqlExpression left, [NotNull] SqlExpression right); + SqlBinaryExpression NotEqual([NotNull] SqlExpression left, [NotNull] SqlExpression right); + SqlBinaryExpression GreaterThan([NotNull] SqlExpression left, [NotNull] SqlExpression right); + SqlBinaryExpression GreaterThanOrEqual([NotNull] SqlExpression left, [NotNull] SqlExpression right); + SqlBinaryExpression LessThan([NotNull] SqlExpression left, [NotNull] SqlExpression right); SqlBinaryExpression LessThanOrEqual([NotNull] SqlExpression left, [NotNull] SqlExpression right); @@ -109,28 +115,30 @@ SqlFunctionExpression Function( [NotNull] Type returnType, [CanBeNull] RelationalTypeMapping typeMapping = null); - [Obsolete("Use overload that explicitly specifies value for 'argumentsPropagateNullability' argument.")] + [Obsolete("Use overload that explicitly specifies value for 'instancePropagatesNullability' and 'argumentsPropagateNullability' arguments.")] SqlFunctionExpression Function( - [CanBeNull] SqlExpression instance, + [NotNull] SqlExpression instance, [NotNull] string name, [NotNull] IEnumerable arguments, [NotNull] Type returnType, [CanBeNull] RelationalTypeMapping typeMapping = null); + [Obsolete("Use overload that explicitly specifies value for 'nullable' argument.")] SqlFunctionExpression Function( [NotNull] string name, [NotNull] Type returnType, [CanBeNull] RelationalTypeMapping typeMapping = null); + [Obsolete("Use overload that explicitly specifies value for 'nullable' argument.")] SqlFunctionExpression Function( [NotNull] string schema, [NotNull] string name, [NotNull] Type returnType, [CanBeNull] RelationalTypeMapping typeMapping = null); - [Obsolete("Use overload that explicitly specifies value for 'argumentsPropagateNullability' argument.")] + [Obsolete("Use overload that explicitly specifies value for 'instancePropagatesNullability' argument.")] SqlFunctionExpression Function( - [CanBeNull] SqlExpression instance, + [NotNull] SqlExpression instance, [NotNull] string name, [NotNull] Type returnType, [CanBeNull] RelationalTypeMapping typeMapping = null); @@ -144,7 +152,7 @@ SqlFunctionExpression Function( [CanBeNull] RelationalTypeMapping typeMapping = null); SqlFunctionExpression Function( - [CanBeNull] string schema, + [NotNull] string schema, [NotNull] string name, [NotNull] IEnumerable arguments, bool nullable, @@ -153,7 +161,7 @@ SqlFunctionExpression Function( [CanBeNull] RelationalTypeMapping typeMapping = null); SqlFunctionExpression Function( - [CanBeNull] SqlExpression instance, + [NotNull] SqlExpression instance, [NotNull] string name, [NotNull] IEnumerable arguments, bool nullable, @@ -176,7 +184,7 @@ SqlFunctionExpression Function( [CanBeNull] RelationalTypeMapping typeMapping = null); SqlFunctionExpression Function( - [CanBeNull] SqlExpression instance, + [NotNull] SqlExpression instance, [NotNull] string name, bool nullable, bool instancePropagatesNullability, diff --git a/src/EFCore.Relational/Query/Internal/CollateTranslator.cs b/src/EFCore.Relational/Query/Internal/CollateTranslator.cs index 2ba3a47b21a..5187220dd5b 100644 --- a/src/EFCore.Relational/Query/Internal/CollateTranslator.cs +++ b/src/EFCore.Relational/Query/Internal/CollateTranslator.cs @@ -3,23 +3,28 @@ using System.Collections.Generic; using System.Reflection; -using JetBrains.Annotations; -using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Query.SqlExpressions; using Microsoft.EntityFrameworkCore.Utilities; namespace Microsoft.EntityFrameworkCore.Query.Internal { + /// + /// 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 class CollateTranslator : IMethodCallTranslator { private static readonly MethodInfo _methodInfo = typeof(RelationalDbFunctionsExtensions).GetMethod(nameof(RelationalDbFunctionsExtensions.Collate)); - private readonly ISqlExpressionFactory _sqlExpressionFactory; - - public CollateTranslator([NotNull] ISqlExpressionFactory sqlExpressionFactory) - => _sqlExpressionFactory = sqlExpressionFactory; - + /// + /// 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 SqlExpression Translate(SqlExpression instance, MethodInfo method, IReadOnlyList arguments) { Check.NotNull(method, nameof(method)); diff --git a/src/EFCore.Relational/Query/Internal/CollectionInitializingExpression.cs b/src/EFCore.Relational/Query/Internal/CollectionInitializingExpression.cs new file mode 100644 index 00000000000..f2a0787ba39 --- /dev/null +++ b/src/EFCore.Relational/Query/Internal/CollectionInitializingExpression.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.Linq.Expressions; +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Utilities; + +namespace Microsoft.EntityFrameworkCore.Query.Internal +{ + /// + /// 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 class CollectionInitializingExpression : Expression, IPrintableExpression + { + /// + /// 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 CollectionInitializingExpression( + int collectionId, + [CanBeNull] Expression parent, + [NotNull] Expression parentIdentifier, + [NotNull] Expression outerIdentifier, + [CanBeNull] INavigation navigation, + [NotNull] Type type) + { + Check.NotNull(parentIdentifier, nameof(parentIdentifier)); + Check.NotNull(outerIdentifier, nameof(outerIdentifier)); + Check.NotNull(type, nameof(type)); + + CollectionId = collectionId; + Parent = parent; + ParentIdentifier = parentIdentifier; + OuterIdentifier = outerIdentifier; + Navigation = navigation; + Type = type; + } + + /// + /// 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 int CollectionId { 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 Expression Parent { 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. + /// + /// + /// 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 Expression ParentIdentifier { 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 Expression OuterIdentifier { 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 INavigation Navigation { 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 override Type Type { 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 sealed override ExpressionType NodeType => ExpressionType.Extension; + + /// + /// 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. + /// + protected override Expression VisitChildren(ExpressionVisitor visitor) + { + Check.NotNull(visitor, nameof(visitor)); + + var parent = visitor.Visit(Parent); + var parentIdentifier = visitor.Visit(ParentIdentifier); + var outerIdentifier = visitor.Visit(OuterIdentifier); + + return parent != Parent || parentIdentifier != ParentIdentifier || outerIdentifier != OuterIdentifier + ? new CollectionInitializingExpression(CollectionId, parent, parentIdentifier, outerIdentifier, Navigation, Type) + : this; + } + + /// + /// 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 Print(ExpressionPrinter expressionPrinter) + { + Check.NotNull(expressionPrinter, nameof(expressionPrinter)); + + expressionPrinter.AppendLine("InitializeCollection:"); + using (expressionPrinter.Indent()) + { + expressionPrinter.AppendLine($"CollectionId: {CollectionId}"); + expressionPrinter.AppendLine($"Navigation: {Navigation?.Name}"); + expressionPrinter.Append("Parent:"); + expressionPrinter.Visit(Parent); + expressionPrinter.AppendLine(); + expressionPrinter.Append("ParentIdentifier:"); + expressionPrinter.Visit(ParentIdentifier); + expressionPrinter.AppendLine(); + expressionPrinter.Append("OuterIdentifier:"); + expressionPrinter.Visit(OuterIdentifier); + expressionPrinter.AppendLine(); + } + } + } +} diff --git a/src/EFCore.Relational/Query/Internal/CollectionPopulatingExpression.cs b/src/EFCore.Relational/Query/Internal/CollectionPopulatingExpression.cs new file mode 100644 index 00000000000..0457c0a44e4 --- /dev/null +++ b/src/EFCore.Relational/Query/Internal/CollectionPopulatingExpression.cs @@ -0,0 +1,102 @@ +// 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.Linq.Expressions; +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Utilities; + +namespace Microsoft.EntityFrameworkCore.Query.Internal +{ + /// + /// 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 class CollectionPopulatingExpression : Expression, IPrintableExpression + { + /// + /// 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 CollectionPopulatingExpression([NotNull] RelationalCollectionShaperExpression parent, [NotNull] Type type, bool include) + { + Check.NotNull(parent, nameof(parent)); + Check.NotNull(type, nameof(type)); + + Parent = parent; + Type = type; + IsInclude = include; + } + + /// + /// 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 RelationalCollectionShaperExpression Parent { 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 bool IsInclude { 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 override Type Type { 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 sealed override ExpressionType NodeType => ExpressionType.Extension; + + /// + /// 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. + /// + protected override Expression VisitChildren(ExpressionVisitor visitor) + { + Check.NotNull(visitor, nameof(visitor)); + + var parent = (RelationalCollectionShaperExpression)visitor.Visit(Parent); + + return parent != Parent + ? new CollectionPopulatingExpression(parent, Type, IsInclude) + : this; + } + + /// + /// 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 Print(ExpressionPrinter expressionPrinter) + { + Check.NotNull(expressionPrinter, nameof(expressionPrinter)); + + expressionPrinter.AppendLine("PopulateCollection:"); + using (expressionPrinter.Indent()) + { + expressionPrinter.Append("Parent:"); + expressionPrinter.Visit(Parent); + } + } + } +} diff --git a/src/EFCore.Relational/Query/FromSqlParameterApplyingExpressionVisitor.cs b/src/EFCore.Relational/Query/Internal/FromSqlParameterApplyingExpressionVisitor.cs similarity index 74% rename from src/EFCore.Relational/Query/FromSqlParameterApplyingExpressionVisitor.cs rename to src/EFCore.Relational/Query/Internal/FromSqlParameterApplyingExpressionVisitor.cs index 2a651f59724..72dcb57ff59 100644 --- a/src/EFCore.Relational/Query/FromSqlParameterApplyingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/Internal/FromSqlParameterApplyingExpressionVisitor.cs @@ -12,31 +12,53 @@ using Microsoft.EntityFrameworkCore.Storage.Internal; using Microsoft.EntityFrameworkCore.Utilities; -namespace Microsoft.EntityFrameworkCore.Query +namespace Microsoft.EntityFrameworkCore.Query.Internal { + /// + /// 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 class FromSqlParameterApplyingExpressionVisitor : ExpressionVisitor { private readonly IDictionary _visitedFromSqlExpressions = new Dictionary(LegacyReferenceEqualityComparer.Instance); private readonly ISqlExpressionFactory _sqlExpressionFactory; + private readonly IRelationalTypeMappingSource _typeMappingSource; private readonly ParameterNameGenerator _parameterNameGenerator; private readonly IReadOnlyDictionary _parametersValues; + /// + /// 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 FromSqlParameterApplyingExpressionVisitor( [NotNull] ISqlExpressionFactory sqlExpressionFactory, + [NotNull] IRelationalTypeMappingSource typeMappingSource, [NotNull] ParameterNameGenerator parameterNameGenerator, [NotNull] IReadOnlyDictionary parametersValues) { Check.NotNull(sqlExpressionFactory, nameof(sqlExpressionFactory)); + Check.NotNull(typeMappingSource, nameof(typeMappingSource)); Check.NotNull(parameterNameGenerator, nameof(parameterNameGenerator)); Check.NotNull(parametersValues, nameof(parametersValues)); _sqlExpressionFactory = sqlExpressionFactory; + _typeMappingSource = typeMappingSource; _parameterNameGenerator = parameterNameGenerator; _parametersValues = parametersValues; } + /// + /// 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 override Expression Visit(Expression expression) { if (expression is FromSqlExpression fromSql) @@ -72,7 +94,7 @@ public override Expression Visit(Expression expression) new TypeMappedRelationalParameter( parameterName, parameterName, - _sqlExpressionFactory.GetTypeMappingForValue(parameterValues[i]), + _typeMappingSource.GetMappingForValue(parameterValues[i]), parameterValues[i]?.GetType().IsNullableType())); } } @@ -105,7 +127,7 @@ public override Expression Visit(Expression expression) else { constantValues[i] = _sqlExpressionFactory.Constant( - value, _sqlExpressionFactory.GetTypeMappingForValue(value)); + value, _typeMappingSource.GetMappingForValue(value)); } } diff --git a/src/EFCore.Relational/Query/Internal/RelationalParameterBasedQueryTranslationPostprocessorFactory.cs b/src/EFCore.Relational/Query/Internal/RelationalParameterBasedQueryTranslationPostprocessorFactory.cs new file mode 100644 index 00000000000..db44e7ab03f --- /dev/null +++ b/src/EFCore.Relational/Query/Internal/RelationalParameterBasedQueryTranslationPostprocessorFactory.cs @@ -0,0 +1,50 @@ +// 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.Utilities; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.EntityFrameworkCore.Query.Internal +{ + /// + /// + /// 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. + /// + /// + /// The service lifetime is . This means a single instance + /// is used by many instances. The implementation must be thread-safe. + /// This service cannot depend on services registered as . + /// + /// + public class RelationalParameterBasedQueryTranslationPostprocessorFactory : IRelationalParameterBasedQueryTranslationPostprocessorFactory + { + private readonly RelationalParameterBasedQueryTranslationPostprocessorDependencies _dependencies; + + /// + /// 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 RelationalParameterBasedQueryTranslationPostprocessorFactory( + [NotNull] RelationalParameterBasedQueryTranslationPostprocessorDependencies dependencies) + { + Check.NotNull(dependencies, nameof(dependencies)); + + _dependencies = dependencies; + } + + /// + /// 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 RelationalParameterBasedQueryTranslationPostprocessor Create(bool useRelationalNulls) + => new RelationalParameterBasedQueryTranslationPostprocessor(_dependencies, useRelationalNulls); + } +} diff --git a/src/EFCore.Relational/Query/Internal/SqlExpressionOptimizingExpressionVisitor.cs b/src/EFCore.Relational/Query/Internal/SqlExpressionOptimizingExpressionVisitor.cs index 981d69744ba..c1c62760dfe 100644 --- a/src/EFCore.Relational/Query/Internal/SqlExpressionOptimizingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/Internal/SqlExpressionOptimizingExpressionVisitor.cs @@ -11,9 +11,16 @@ namespace Microsoft.EntityFrameworkCore.Query.Internal { + /// + /// 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 class SqlExpressionOptimizingExpressionVisitor : ExpressionVisitor { private readonly bool _useRelationalNulls; + private readonly ISqlExpressionFactory _sqlExpressionFactory; private static bool TryNegate(ExpressionType expressionType, out ExpressionType result) { @@ -35,14 +42,24 @@ private static bool TryNegate(ExpressionType expressionType, out ExpressionType return negated.HasValue; } + /// + /// 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 SqlExpressionOptimizingExpressionVisitor([NotNull] ISqlExpressionFactory sqlExpressionFactory, bool useRelationalNulls) { - SqlExpressionFactory = sqlExpressionFactory; + _sqlExpressionFactory = sqlExpressionFactory; _useRelationalNulls = useRelationalNulls; } - protected virtual ISqlExpressionFactory SqlExpressionFactory { 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. + /// protected override Expression VisitExtension(Expression extensionExpression) { Check.NotNull(extensionExpression, nameof(extensionExpression)); @@ -113,7 +130,7 @@ private Expression VisitSelect(SelectExpression selectExpression) return newExpression; } - protected virtual Expression VisitSqlUnary([NotNull] SqlUnaryExpression sqlUnaryExpression) + private Expression VisitSqlUnary([NotNull] SqlUnaryExpression sqlUnaryExpression) { var newOperand = (SqlExpression)Visit(sqlUnaryExpression.Operand); @@ -143,7 +160,7 @@ private SqlExpression SimplifyUnaryExpression( case SqlConstantExpression constantOperand when constantOperand.Value is bool value: { - return SqlExpressionFactory.Constant(!value, typeMapping); + return _sqlExpressionFactory.Constant(!value, typeMapping); } case InExpression inOperand: @@ -158,11 +175,11 @@ private SqlExpression SimplifyUnaryExpression( //!(a IS NULL) -> a IS NOT NULL case ExpressionType.Equal: - return SqlExpressionFactory.IsNotNull(unaryOperand.Operand); + return _sqlExpressionFactory.IsNotNull(unaryOperand.Operand); //!(a IS NOT NULL) -> a IS NULL case ExpressionType.NotEqual: - return SqlExpressionFactory.IsNull(unaryOperand.Operand); + return _sqlExpressionFactory.IsNull(unaryOperand.Operand); } break; @@ -212,7 +229,7 @@ private SqlExpression SimplifyUnaryExpression( typeMapping); } - return SqlExpressionFactory.MakeUnary(operatorType, operand, type, typeMapping); + return _sqlExpressionFactory.MakeUnary(operatorType, operand, type, typeMapping); } private SqlExpression SimplifyNullNotNullExpression( @@ -228,7 +245,7 @@ private SqlExpression SimplifyNullNotNullExpression( switch (operand) { case SqlConstantExpression constantOperand: - return SqlExpressionFactory.Constant( + return _sqlExpressionFactory.Constant( operatorType == ExpressionType.Equal ? constantOperand.Value == null : constantOperand.Value != null, @@ -236,7 +253,7 @@ private SqlExpression SimplifyNullNotNullExpression( case ColumnExpression columnOperand when !columnOperand.IsNullable: - return SqlExpressionFactory.Constant(operatorType == ExpressionType.NotEqual, typeMapping); + return _sqlExpressionFactory.Constant(operatorType == ExpressionType.NotEqual, typeMapping); case SqlUnaryExpression sqlUnaryOperand: if (sqlUnaryOperand.OperatorType == ExpressionType.Convert @@ -255,7 +272,7 @@ private SqlExpression SimplifyNullNotNullExpression( // (a is not null) is null -> false // (a is null) is not null -> true // (a is not null) is not null -> true - return SqlExpressionFactory.Constant(operatorType == ExpressionType.NotEqual, typeMapping); + return _sqlExpressionFactory.Constant(operatorType == ExpressionType.NotEqual, typeMapping); } break; @@ -304,10 +321,10 @@ when sqlFunctionExpression.IsBuiltIn break; } - return SqlExpressionFactory.MakeUnary(operatorType, operand, type, typeMapping); + return _sqlExpressionFactory.MakeUnary(operatorType, operand, type, typeMapping); } - protected virtual Expression VisitSqlBinary([NotNull] SqlBinaryExpression sqlBinaryExpression) + private Expression VisitSqlBinary([NotNull] SqlBinaryExpression sqlBinaryExpression) { var newLeft = (SqlExpression)Visit(sqlBinaryExpression.Left); var newRight = (SqlExpression)Visit(sqlBinaryExpression.Right); @@ -345,7 +362,7 @@ private SqlExpression SimplifyBinaryExpression( // a is null && a is not null -> false return leftUnary.OperatorType == rightUnary.OperatorType ? (SqlExpression)leftUnary - : SqlExpressionFactory.Constant(operatorType == ExpressionType.OrElse, typeMapping); + : _sqlExpressionFactory.Constant(operatorType == ExpressionType.OrElse, typeMapping); } return SimplifyLogicalSqlBinaryExpression( @@ -391,16 +408,16 @@ private SqlExpression SimplifyBinaryExpression( || left is ColumnExpression columnExpression && !columnExpression.IsNullable) && left.Equals(right)) { - return SqlExpressionFactory.Constant(operatorType == ExpressionType.Equal, typeMapping); + return _sqlExpressionFactory.Constant(operatorType == ExpressionType.Equal, typeMapping); } break; } - return SqlExpressionFactory.MakeBinary(operatorType, left, right, typeMapping); + return _sqlExpressionFactory.MakeBinary(operatorType, left, right, typeMapping); } - protected virtual SqlExpression SimplifyNullComparisonExpression( + private SqlExpression SimplifyNullComparisonExpression( ExpressionType operatorType, [NotNull] SqlExpression left, [NotNull] SqlExpression right, @@ -413,7 +430,7 @@ protected virtual SqlExpression SimplifyNullComparisonExpression( { if (leftNull && rightNull) { - return SqlExpressionFactory.Constant(operatorType == ExpressionType.Equal, typeMapping); + return _sqlExpressionFactory.Constant(operatorType == ExpressionType.Equal, typeMapping); } if (leftNull) @@ -427,7 +444,7 @@ protected virtual SqlExpression SimplifyNullComparisonExpression( } } - return SqlExpressionFactory.MakeBinary(operatorType, left, right, typeMapping); + return _sqlExpressionFactory.MakeBinary(operatorType, left, right, typeMapping); } private SqlExpression SimplifyBoolConstantComparisonExpression( @@ -441,8 +458,8 @@ private SqlExpression SimplifyBoolConstantComparisonExpression( if (leftBoolConstant != null && rightBoolConstant != null) { return operatorType == ExpressionType.Equal - ? SqlExpressionFactory.Constant((bool)leftBoolConstant.Value == (bool)rightBoolConstant.Value, typeMapping) - : SqlExpressionFactory.Constant((bool)leftBoolConstant.Value != (bool)rightBoolConstant.Value, typeMapping); + ? _sqlExpressionFactory.Constant((bool)leftBoolConstant.Value == (bool)rightBoolConstant.Value, typeMapping) + : _sqlExpressionFactory.Constant((bool)leftBoolConstant.Value != (bool)rightBoolConstant.Value, typeMapping); } if (rightBoolConstant != null @@ -479,7 +496,7 @@ private SqlExpression SimplifyBoolConstantComparisonExpression( : right; } - return SqlExpressionFactory.MakeBinary(operatorType, left, right, typeMapping); + return _sqlExpressionFactory.MakeBinary(operatorType, left, right, typeMapping); static bool CanOptimize(SqlExpression operand) => operand is LikeExpression @@ -525,7 +542,7 @@ private SqlExpression SimplifyLogicalSqlBinaryExpression( : left; } - return SqlExpressionFactory.MakeBinary(operatorType, left, right, typeMapping); + return _sqlExpressionFactory.MakeBinary(operatorType, left, right, typeMapping); } } } diff --git a/src/EFCore.Relational/Query/NullabilityBasedSqlProcessingExpressionVisitor.cs b/src/EFCore.Relational/Query/NullabilityBasedSqlProcessingExpressionVisitor.cs index 907a431c79b..01d5e9d6367 100644 --- a/src/EFCore.Relational/Query/NullabilityBasedSqlProcessingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/NullabilityBasedSqlProcessingExpressionVisitor.cs @@ -15,6 +15,8 @@ namespace Microsoft.EntityFrameworkCore.Query { +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + // Whole API surface is going to change. See issue#20204 public class NullabilityBasedSqlProcessingExpressionVisitor : SqlExpressionVisitor { protected virtual bool UseRelationalNulls { get; } @@ -1633,4 +1635,5 @@ private SqlExpression ExpandNegatedNullableNotEqualNonNullable( SqlExpressionFactory.Equal(left, right), leftIsNull)); } +#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member } diff --git a/src/EFCore.Relational/Query/QuerySqlGenerator.cs b/src/EFCore.Relational/Query/QuerySqlGenerator.cs index 26d83ec2fe8..b90b2d031aa 100644 --- a/src/EFCore.Relational/Query/QuerySqlGenerator.cs +++ b/src/EFCore.Relational/Query/QuerySqlGenerator.cs @@ -8,7 +8,6 @@ using System.Text.RegularExpressions; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Diagnostics; -using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Query.SqlExpressions; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Storage.Internal; diff --git a/src/EFCore.Relational/Query/RelationalMethodCallTranslatorProvider.cs b/src/EFCore.Relational/Query/RelationalMethodCallTranslatorProvider.cs index c9b8ace70c6..7f2dd1d856d 100644 --- a/src/EFCore.Relational/Query/RelationalMethodCallTranslatorProvider.cs +++ b/src/EFCore.Relational/Query/RelationalMethodCallTranslatorProvider.cs @@ -31,7 +31,7 @@ public RelationalMethodCallTranslatorProvider([NotNull] RelationalMethodCallTran { new EqualsTranslator(sqlExpressionFactory), new StringMethodTranslator(sqlExpressionFactory), - new CollateTranslator(sqlExpressionFactory), + new CollateTranslator(), new ContainsTranslator(sqlExpressionFactory), new LikeTranslator(sqlExpressionFactory), new EnumHasFlagTranslator(sqlExpressionFactory), diff --git a/src/EFCore.Relational/Query/RelationalParameterBasedQueryTranslationPostprocessor.cs b/src/EFCore.Relational/Query/RelationalParameterBasedQueryTranslationPostprocessor.cs index 1775dbbef18..e8b273856ff 100644 --- a/src/EFCore.Relational/Query/RelationalParameterBasedQueryTranslationPostprocessor.cs +++ b/src/EFCore.Relational/Query/RelationalParameterBasedQueryTranslationPostprocessor.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Query.Internal; using Microsoft.EntityFrameworkCore.Query.SqlExpressions; using Microsoft.EntityFrameworkCore.Utilities; @@ -41,6 +42,7 @@ public virtual (SelectExpression, bool) Optimize( var fromSqlParameterOptimized = new FromSqlParameterApplyingExpressionVisitor( Dependencies.SqlExpressionFactory, + Dependencies.TypeMappingSource, Dependencies.ParameterNameGeneratorFactory.Create(), parametersValues).Visit(sqlExpressionOptimized); diff --git a/src/EFCore.Relational/Query/RelationalParameterBasedQueryTranslationPostprocessorDependencies.cs b/src/EFCore.Relational/Query/RelationalParameterBasedQueryTranslationPostprocessorDependencies.cs index 8fcf327171b..1a840082983 100644 --- a/src/EFCore.Relational/Query/RelationalParameterBasedQueryTranslationPostprocessorDependencies.cs +++ b/src/EFCore.Relational/Query/RelationalParameterBasedQueryTranslationPostprocessorDependencies.cs @@ -3,6 +3,7 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Query.Internal; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Utilities; using Microsoft.Extensions.DependencyInjection; @@ -55,12 +56,15 @@ public sealed class RelationalParameterBasedQueryTranslationPostprocessorDepende [EntityFrameworkInternal] public RelationalParameterBasedQueryTranslationPostprocessorDependencies( [NotNull] ISqlExpressionFactory sqlExpressionFactory, + [NotNull] IRelationalTypeMappingSource typeMappingSource, [NotNull] IParameterNameGeneratorFactory parameterNameGeneratorFactory) { Check.NotNull(sqlExpressionFactory, nameof(sqlExpressionFactory)); + Check.NotNull(typeMappingSource, nameof(typeMappingSource)); Check.NotNull(parameterNameGeneratorFactory, nameof(parameterNameGeneratorFactory)); SqlExpressionFactory = sqlExpressionFactory; + TypeMappingSource = typeMappingSource; ParameterNameGeneratorFactory = parameterNameGeneratorFactory; } @@ -69,6 +73,11 @@ public RelationalParameterBasedQueryTranslationPostprocessorDependencies( /// public ISqlExpressionFactory SqlExpressionFactory { get; } + /// + /// Relational type mapping souce. + /// + public IRelationalTypeMappingSource TypeMappingSource { get; } + /// /// Parameter name generator factory. /// @@ -80,7 +89,15 @@ public RelationalParameterBasedQueryTranslationPostprocessorDependencies( /// A replacement for the current dependency of this type. /// A new parameter object with the given service replaced. public RelationalParameterBasedQueryTranslationPostprocessorDependencies With([NotNull] ISqlExpressionFactory sqlExpressionFactory) - => new RelationalParameterBasedQueryTranslationPostprocessorDependencies(sqlExpressionFactory, ParameterNameGeneratorFactory); + => new RelationalParameterBasedQueryTranslationPostprocessorDependencies(sqlExpressionFactory, TypeMappingSource, ParameterNameGeneratorFactory); + + /// + /// Clones this dependency parameter object with one service replaced. + /// + /// A replacement for the current dependency of this type. + /// A new parameter object with the given service replaced. + public RelationalParameterBasedQueryTranslationPostprocessorDependencies With([NotNull] IRelationalTypeMappingSource typeMappingSource) + => new RelationalParameterBasedQueryTranslationPostprocessorDependencies(SqlExpressionFactory, typeMappingSource, ParameterNameGeneratorFactory); /// /// Clones this dependency parameter object with one service replaced. @@ -88,6 +105,6 @@ public RelationalParameterBasedQueryTranslationPostprocessorDependencies With([N /// A replacement for the current dependency of this type. /// A new parameter object with the given service replaced. public RelationalParameterBasedQueryTranslationPostprocessorDependencies With([NotNull] IParameterNameGeneratorFactory parameterNameGeneratorFactory) - => new RelationalParameterBasedQueryTranslationPostprocessorDependencies(SqlExpressionFactory, parameterNameGeneratorFactory); + => new RelationalParameterBasedQueryTranslationPostprocessorDependencies(SqlExpressionFactory, TypeMappingSource, parameterNameGeneratorFactory); } } diff --git a/src/EFCore.Relational/Query/RelationalParameterBasedQueryTranslationPostprocessorFactory.cs b/src/EFCore.Relational/Query/RelationalParameterBasedQueryTranslationPostprocessorFactory.cs deleted file mode 100644 index f34deb54c46..00000000000 --- a/src/EFCore.Relational/Query/RelationalParameterBasedQueryTranslationPostprocessorFactory.cs +++ /dev/null @@ -1,24 +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.Utilities; - -namespace Microsoft.EntityFrameworkCore.Query -{ - public class RelationalParameterBasedQueryTranslationPostprocessorFactory : IRelationalParameterBasedQueryTranslationPostprocessorFactory - { - private readonly RelationalParameterBasedQueryTranslationPostprocessorDependencies _dependencies; - - public RelationalParameterBasedQueryTranslationPostprocessorFactory( - [NotNull] RelationalParameterBasedQueryTranslationPostprocessorDependencies dependencies) - { - Check.NotNull(dependencies, nameof(dependencies)); - - _dependencies = dependencies; - } - - public virtual RelationalParameterBasedQueryTranslationPostprocessor Create(bool useRelationalNulls) - => new RelationalParameterBasedQueryTranslationPostprocessor(_dependencies, useRelationalNulls); - } -} diff --git a/src/EFCore.Relational/Query/RelationalQueryTranslationPostprocessor.cs b/src/EFCore.Relational/Query/RelationalQueryTranslationPostprocessor.cs index aeecd8bd248..cd7f8f1d5b0 100644 --- a/src/EFCore.Relational/Query/RelationalQueryTranslationPostprocessor.cs +++ b/src/EFCore.Relational/Query/RelationalQueryTranslationPostprocessor.cs @@ -4,7 +4,6 @@ using System; using System.Linq.Expressions; using JetBrains.Annotations; -using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Query.Internal; using Microsoft.EntityFrameworkCore.Utilities; @@ -22,23 +21,17 @@ public RelationalQueryTranslationPostprocessor( Check.NotNull(queryCompilationContext, nameof(queryCompilationContext)); RelationalDependencies = relationalDependencies; - UseRelationalNulls = RelationalOptionsExtension.Extract(queryCompilationContext.ContextOptions).UseRelationalNulls; - SqlExpressionFactory = relationalDependencies.SqlExpressionFactory; } protected virtual RelationalQueryTranslationPostprocessorDependencies RelationalDependencies { get; } - protected virtual ISqlExpressionFactory SqlExpressionFactory { get; } - - protected virtual bool UseRelationalNulls { get; } - public override Expression Process(Expression query) { query = base.Process(query); query = new SelectExpressionProjectionApplyingExpressionVisitor().Visit(query); query = new CollectionJoinApplyingExpressionVisitor().Visit(query); query = new TableAliasUniquifyingExpressionVisitor().Visit(query); - query = new CaseWhenFlatteningExpressionVisitor(SqlExpressionFactory).Visit(query); + query = new CaseWhenFlatteningExpressionVisitor(RelationalDependencies.SqlExpressionFactory).Visit(query); #pragma warning disable 618 query = OptimizeSqlExpression(query); diff --git a/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs index 81a684d578e..786a05ccd3a 100644 --- a/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs @@ -646,7 +646,7 @@ protected override Expression VisitUnary(UnaryExpression unaryExpression) // Introduce explicit cast only if the target type is mapped else we need to client eval if (unaryExpression.Type == typeof(object) - || _sqlExpressionFactory.FindMapping(unaryExpression.Type) != null) + || Dependencies.TypeMappingSource.FindMapping(unaryExpression.Type) != null) { sqlOperand = _sqlExpressionFactory.ApplyDefaultTypeMapping(sqlOperand); diff --git a/src/EFCore.Relational/Query/Internal/RelationalSqlTranslatingExpressionVisitorDependencies.cs b/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitorDependencies.cs similarity index 81% rename from src/EFCore.Relational/Query/Internal/RelationalSqlTranslatingExpressionVisitorDependencies.cs rename to src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitorDependencies.cs index 7bc330f75a8..9a2ff5617de 100644 --- a/src/EFCore.Relational/Query/Internal/RelationalSqlTranslatingExpressionVisitorDependencies.cs +++ b/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitorDependencies.cs @@ -3,6 +3,7 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Utilities; using Microsoft.Extensions.DependencyInjection; @@ -54,23 +55,31 @@ public sealed class RelationalSqlTranslatingExpressionVisitorDependencies [EntityFrameworkInternal] public RelationalSqlTranslatingExpressionVisitorDependencies( [NotNull] ISqlExpressionFactory sqlExpressionFactory, + [NotNull] IRelationalTypeMappingSource typeMappingSource, [NotNull] IMemberTranslatorProvider memberTranslatorProvider, [NotNull] IMethodCallTranslatorProvider methodCallTranslatorProvider) { Check.NotNull(sqlExpressionFactory, nameof(sqlExpressionFactory)); + Check.NotNull(typeMappingSource, nameof(typeMappingSource)); Check.NotNull(memberTranslatorProvider, nameof(memberTranslatorProvider)); Check.NotNull(methodCallTranslatorProvider, nameof(methodCallTranslatorProvider)); SqlExpressionFactory = sqlExpressionFactory; + TypeMappingSource = typeMappingSource; MemberTranslatorProvider = memberTranslatorProvider; MethodCallTranslatorProvider = methodCallTranslatorProvider; } /// - /// The expression factory.. + /// The expression factory. /// public ISqlExpressionFactory SqlExpressionFactory { get; } + /// + /// The relational type mapping souce. + /// + public IRelationalTypeMappingSource TypeMappingSource { get; } + /// /// The member translation provider. /// @@ -88,7 +97,16 @@ public RelationalSqlTranslatingExpressionVisitorDependencies( /// A new parameter object with the given service replaced. public RelationalSqlTranslatingExpressionVisitorDependencies With([NotNull] ISqlExpressionFactory sqlExpressionFactory) => new RelationalSqlTranslatingExpressionVisitorDependencies( - sqlExpressionFactory, MemberTranslatorProvider, MethodCallTranslatorProvider); + sqlExpressionFactory, TypeMappingSource, MemberTranslatorProvider, MethodCallTranslatorProvider); + + /// + /// Clones this dependency parameter object with one service replaced. + /// + /// A replacement for the current dependency of this type. + /// A new parameter object with the given service replaced. + public RelationalSqlTranslatingExpressionVisitorDependencies With([NotNull] IRelationalTypeMappingSource typeMappingSource) + => new RelationalSqlTranslatingExpressionVisitorDependencies( + SqlExpressionFactory, typeMappingSource, MemberTranslatorProvider, MethodCallTranslatorProvider); /// /// Clones this dependency parameter object with one service replaced. @@ -97,7 +115,7 @@ public RelationalSqlTranslatingExpressionVisitorDependencies With([NotNull] ISql /// A new parameter object with the given service replaced. public RelationalSqlTranslatingExpressionVisitorDependencies With([NotNull] IMemberTranslatorProvider memberTranslatorProvider) => new RelationalSqlTranslatingExpressionVisitorDependencies( - SqlExpressionFactory, memberTranslatorProvider, MethodCallTranslatorProvider); + SqlExpressionFactory, TypeMappingSource, memberTranslatorProvider, MethodCallTranslatorProvider); /// /// Clones this dependency parameter object with one service replaced. @@ -107,6 +125,6 @@ public RelationalSqlTranslatingExpressionVisitorDependencies With([NotNull] IMem public RelationalSqlTranslatingExpressionVisitorDependencies With( [NotNull] IMethodCallTranslatorProvider methodCallTranslatorProvider) => new RelationalSqlTranslatingExpressionVisitorDependencies( - SqlExpressionFactory, MemberTranslatorProvider, methodCallTranslatorProvider); + SqlExpressionFactory, TypeMappingSource, MemberTranslatorProvider, methodCallTranslatorProvider); } } diff --git a/src/EFCore.Relational/Query/SqlExpressionFactory.cs b/src/EFCore.Relational/Query/SqlExpressionFactory.cs index 9dfb9de6e51..4a36379bcbd 100644 --- a/src/EFCore.Relational/Query/SqlExpressionFactory.cs +++ b/src/EFCore.Relational/Query/SqlExpressionFactory.cs @@ -8,7 +8,6 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Query.SqlExpressions; @@ -214,16 +213,6 @@ private SqlExpression ApplyTypeMappingOnSqlBinary( resultTypeMapping); } - public virtual RelationalTypeMapping GetTypeMappingForValue(object value) - => _typeMappingSource.GetMappingForValue(value); - - public virtual RelationalTypeMapping FindMapping(Type type) - { - Check.NotNull(type, nameof(type)); - - return _typeMappingSource.FindMapping(type); - } - public virtual SqlBinaryExpression MakeBinary( ExpressionType operatorType, SqlExpression left, SqlExpression right, RelationalTypeMapping typeMapping) { @@ -386,12 +375,12 @@ public virtual SqlFunctionExpression Coalesce(SqlExpression left, SqlExpression ApplyTypeMapping(right, inferredTypeMapping) }; - return SqlFunctionExpression.Create( + return new SqlFunctionExpression( "COALESCE", typeMappedArguments, nullable: true, // COALESCE is handled separately since it's only nullable if *both* arguments are null - argumentsPropagateNullability: new[] { false, false }, + argumentsPropagateNullability: new[] { false, false}, resultType, inferredTypeMapping); } @@ -442,8 +431,7 @@ public virtual SqlUnaryExpression Negate(SqlExpression operand) return MakeUnary(ExpressionType.Negate, operand, operand.Type, operand.TypeMapping); } - public virtual CaseExpression Case( - [NotNull] SqlExpression operand, [CanBeNull] SqlExpression elseResult, [NotNull] params CaseWhenClause[] whenClauses) + public virtual CaseExpression Case(SqlExpression operand, params CaseWhenClause[] whenClauses) { Check.NotNull(operand, nameof(operand)); Check.NotNull(whenClauses, nameof(whenClauses)); @@ -455,8 +443,7 @@ public virtual CaseExpression Case( ?? new[] { operand.Type }.Concat(whenClauses.Select(wc => wc.Test.Type)) .Where(t => t != typeof(object)).Select(t => _typeMappingSource.FindMapping(t)).FirstOrDefault(); - var resultTypeMapping = elseResult?.TypeMapping - ?? whenClauses.Select(wc => wc.Result.TypeMapping).FirstOrDefault(t => t != null); + var resultTypeMapping = whenClauses.Select(wc => wc.Result.TypeMapping).FirstOrDefault(t => t != null); operand = ApplyTypeMapping(operand, operandTypeMapping); @@ -469,17 +456,7 @@ public virtual CaseExpression Case( ApplyTypeMapping(caseWhenClause.Result, resultTypeMapping))); } - elseResult = ApplyTypeMapping(elseResult, resultTypeMapping); - - return new CaseExpression(operand, typeMappedWhenClauses, elseResult); - } - - public virtual CaseExpression Case(SqlExpression operand, params CaseWhenClause[] whenClauses) - { - Check.NotNull(operand, nameof(operand)); - Check.NotNull(whenClauses, nameof(whenClauses)); - - return Case(operand, null, whenClauses); + return new CaseExpression(operand, typeMappedWhenClauses); } public virtual CaseExpression Case(IReadOnlyList whenClauses, SqlExpression elseResult) @@ -537,9 +514,11 @@ public virtual SqlFunctionExpression Function( returnType, typeMapping); + [Obsolete("Use overload that explicitly specifies value for 'nullable' argument.")] public virtual SqlFunctionExpression Function(string name, Type returnType, RelationalTypeMapping typeMapping = null) => Function(name, nullable: true, returnType, typeMapping); + [Obsolete("Use overload that explicitly specifies value for 'nullable' argument.")] public virtual SqlFunctionExpression Function(string schema, string name, Type returnType, RelationalTypeMapping typeMapping = null) => Function(schema, name, nullable: true, returnType, typeMapping); @@ -557,6 +536,7 @@ public virtual SqlFunctionExpression Function( { Check.NotEmpty(name, nameof(name)); Check.NotNull(arguments, nameof(arguments)); + Check.NotNull(argumentsPropagateNullability, nameof(argumentsPropagateNullability)); Check.NotNull(returnType, nameof(returnType)); var typeMappedArguments = new List(); @@ -566,13 +546,7 @@ public virtual SqlFunctionExpression Function( typeMappedArguments.Add(ApplyDefaultTypeMapping(argument)); } - return SqlFunctionExpression.Create( - name, - typeMappedArguments, - nullable, - argumentsPropagateNullability, - returnType, - typeMapping); + return new SqlFunctionExpression(name, typeMappedArguments, nullable, argumentsPropagateNullability, returnType, typeMapping); } public virtual SqlFunctionExpression Function( @@ -586,6 +560,7 @@ public virtual SqlFunctionExpression Function( { Check.NotEmpty(name, nameof(name)); Check.NotNull(arguments, nameof(arguments)); + Check.NotNull(argumentsPropagateNullability, nameof(argumentsPropagateNullability)); Check.NotNull(returnType, nameof(returnType)); var typeMappedArguments = new List(); @@ -594,14 +569,7 @@ public virtual SqlFunctionExpression Function( typeMappedArguments.Add(ApplyDefaultTypeMapping(argument)); } - return SqlFunctionExpression.Create( - schema, - name, - typeMappedArguments, - nullable, - argumentsPropagateNullability, - returnType, - typeMapping); + return new SqlFunctionExpression(schema, name, typeMappedArguments, nullable, argumentsPropagateNullability, returnType, typeMapping); } public virtual SqlFunctionExpression Function( @@ -614,8 +582,10 @@ public virtual SqlFunctionExpression Function( Type returnType, RelationalTypeMapping typeMapping = null) { + Check.NotNull(instance, nameof(instance)); Check.NotEmpty(name, nameof(name)); Check.NotNull(arguments, nameof(arguments)); + Check.NotNull(argumentsPropagateNullability, nameof(argumentsPropagateNullability)); Check.NotNull(returnType, nameof(returnType)); instance = ApplyDefaultTypeMapping(instance); @@ -625,15 +595,7 @@ public virtual SqlFunctionExpression Function( typeMappedArguments.Add(ApplyDefaultTypeMapping(argument)); } - return SqlFunctionExpression.Create( - instance, - name, - typeMappedArguments, - nullable, - instancePropagatesNullability, - argumentsPropagateNullability, - returnType, - typeMapping); + return new SqlFunctionExpression(instance, name, typeMappedArguments, nullable, instancePropagatesNullability, argumentsPropagateNullability, returnType, typeMapping); } public virtual SqlFunctionExpression Function(string name, bool nullable, Type returnType, RelationalTypeMapping typeMapping = null) @@ -641,7 +603,7 @@ public virtual SqlFunctionExpression Function(string name, bool nullable, Type r Check.NotEmpty(name, nameof(name)); Check.NotNull(returnType, nameof(returnType)); - return SqlFunctionExpression.CreateNiladic(name, nullable, returnType, typeMapping); + return new SqlFunctionExpression(name, nullable, returnType, typeMapping); } public virtual SqlFunctionExpression Function(string schema, string name, bool nullable, Type returnType, RelationalTypeMapping typeMapping = null) @@ -650,7 +612,7 @@ public virtual SqlFunctionExpression Function(string schema, string name, bool n Check.NotEmpty(name, nameof(name)); Check.NotNull(returnType, nameof(returnType)); - return SqlFunctionExpression.CreateNiladic(schema, name, nullable, returnType, typeMapping); + return new SqlFunctionExpression(schema, name, nullable, returnType, typeMapping); } public virtual SqlFunctionExpression Function( @@ -661,16 +623,11 @@ public virtual SqlFunctionExpression Function( Type returnType, RelationalTypeMapping typeMapping = null) { + Check.NotNull(instance, nameof(instance)); Check.NotEmpty(name, nameof(name)); Check.NotNull(returnType, nameof(returnType)); - return SqlFunctionExpression.CreateNiladic( - ApplyDefaultTypeMapping(instance), - name, - nullable, - instancePropagatesNullability, - returnType, - typeMapping); + return new SqlFunctionExpression(ApplyDefaultTypeMapping(instance), name, nullable, instancePropagatesNullability, returnType, typeMapping); } public virtual ExistsExpression Exists(SelectExpression subquery, bool negated) @@ -971,5 +928,11 @@ private EntityProjectionExpression GetMappedEntityProjectionExpression(SelectExp private SqlExpression IsNotNull(IProperty property, EntityProjectionExpression entityProjection) => IsNotNull(entityProjection.BindProperty(property)); + + [Obsolete("Use IRelationalTypeMappingSource directly.")] + public virtual RelationalTypeMapping GetTypeMappingForValue(object value) => _typeMappingSource.GetMappingForValue(value); + + [Obsolete("Use IRelationalTypeMappingSource directly.")] + public virtual RelationalTypeMapping FindMapping(Type type) => _typeMappingSource.FindMapping(Check.NotNull(type, nameof(type))); } } diff --git a/src/EFCore.Relational/Query/SqlExpressions/CaseExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/CaseExpression.cs index 5abb4412d1c..fab98a1519e 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/CaseExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/CaseExpression.cs @@ -16,28 +16,22 @@ public class CaseExpression : SqlExpression public CaseExpression( [NotNull] SqlExpression operand, - [NotNull] IReadOnlyList whenClauses) - : this(operand, whenClauses, null) + [NotNull] IReadOnlyList whenClauses, + [CanBeNull] SqlExpression elseResult = null) + : base(Check.NotEmpty(whenClauses, nameof(whenClauses))[0].Result.Type, whenClauses[0].Result.TypeMapping) { Check.NotNull(operand, nameof(operand)); - } - public CaseExpression( - [NotNull] IReadOnlyList whenClauses, - [CanBeNull] SqlExpression elseResult) - : this(null, whenClauses, elseResult) - { + Operand = operand; + _whenClauses.AddRange(whenClauses); + ElseResult = elseResult; } public CaseExpression( - [CanBeNull] SqlExpression operand, [NotNull] IReadOnlyList whenClauses, - [CanBeNull] SqlExpression elseResult) - : base(whenClauses[0].Result.Type, whenClauses[0].Result.TypeMapping) + [CanBeNull] SqlExpression elseResult = null) + : base(Check.NotEmpty(whenClauses, nameof(whenClauses))[0].Result.Type, whenClauses[0].Result.TypeMapping) { - Check.NotNull(whenClauses, nameof(whenClauses)); - - Operand = operand; _whenClauses.AddRange(whenClauses); ElseResult = elseResult; } @@ -80,10 +74,12 @@ protected override Expression VisitChildren(ExpressionVisitor visitor) public virtual CaseExpression Update( [CanBeNull] SqlExpression operand, - [CanBeNull] IReadOnlyList whenClauses, + [NotNull] IReadOnlyList whenClauses, [CanBeNull] SqlExpression elseResult) => operand != Operand || !whenClauses.SequenceEqual(WhenClauses) || elseResult != ElseResult - ? new CaseExpression(operand, whenClauses, elseResult) + ? (Operand == null + ? new CaseExpression(whenClauses, elseResult) + : new CaseExpression(operand, whenClauses, elseResult)) : this; public override void Print(ExpressionPrinter expressionPrinter) diff --git a/src/EFCore.Relational/Query/SqlExpressions/CollateExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/CollateExpression.cs index 50f82a9ae39..5fde80ed8cc 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/CollateExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/CollateExpression.cs @@ -4,16 +4,13 @@ using System; using System.Linq.Expressions; using JetBrains.Annotations; -using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Utilities; namespace Microsoft.EntityFrameworkCore.Query.SqlExpressions { public class CollateExpression : SqlExpression { - public CollateExpression( - [NotNull] SqlExpression operand, - [NotNull] string collation) + public CollateExpression([NotNull] SqlExpression operand, [NotNull] string collation) : base(operand.Type, operand.TypeMapping) { Check.NotNull(operand, nameof(operand)); diff --git a/src/EFCore.Relational/Query/SqlExpressions/ProjectionExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/ProjectionExpression.cs index 09dfd1fa14a..abc827a9616 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/ProjectionExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/ProjectionExpression.cs @@ -8,9 +8,10 @@ namespace Microsoft.EntityFrameworkCore.Query.SqlExpressions { - public class ProjectionExpression : Expression, IPrintableExpression + // Class is sealed because there are no public/protected constructors. Can be unsealed if this is changed. + public sealed class ProjectionExpression : Expression, IPrintableExpression { - public ProjectionExpression([NotNull] SqlExpression expression, [NotNull] string alias) + internal ProjectionExpression([NotNull] SqlExpression expression, [NotNull] string alias) { Check.NotNull(expression, nameof(expression)); Check.NotNull(alias, nameof(alias)); @@ -19,8 +20,8 @@ public ProjectionExpression([NotNull] SqlExpression expression, [NotNull] string Alias = alias; } - public virtual string Alias { get; } - public virtual SqlExpression Expression { get; } + public string Alias { get; } + public SqlExpression Expression { get; } public override Type Type => Expression.Type; public sealed override ExpressionType NodeType => ExpressionType.Extension; @@ -32,7 +33,7 @@ protected override Expression VisitChildren(ExpressionVisitor visitor) return Update((SqlExpression)visitor.Visit(Expression)); } - public virtual ProjectionExpression Update([NotNull] SqlExpression expression) + public ProjectionExpression Update([NotNull] SqlExpression expression) { Check.NotNull(expression, nameof(expression)); @@ -41,7 +42,7 @@ public virtual ProjectionExpression Update([NotNull] SqlExpression expression) : this; } - public virtual void Print(ExpressionPrinter expressionPrinter) + public void Print(ExpressionPrinter expressionPrinter) { Check.NotNull(expressionPrinter, nameof(expressionPrinter)); diff --git a/src/EFCore.Relational/Query/SqlExpressions/SqlFunctionExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/SqlFunctionExpression.cs index 9cba45154c7..6e7c2230d53 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/SqlFunctionExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/SqlFunctionExpression.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using System.Linq.Expressions; using JetBrains.Annotations; @@ -13,12 +14,277 @@ namespace Microsoft.EntityFrameworkCore.Query.SqlExpressions { public class SqlFunctionExpression : SqlExpression { - public static SqlFunctionExpression CreateNiladic( + public SqlFunctionExpression( + [NotNull] string functionName, + bool nullable, + [NotNull] Type type, + [CanBeNull] RelationalTypeMapping typeMapping) + : this(instance: null, schema: null, functionName, nullable, instancePropagatesNullability: null, builtIn: true, type, typeMapping) + { + } + + public SqlFunctionExpression( + [NotNull] string schema, + [NotNull] string functionName, + bool nullable, + [NotNull] Type type, + [CanBeNull] RelationalTypeMapping typeMapping) + : this(instance: null, Check.NotEmpty(schema, nameof(schema)), functionName, nullable, instancePropagatesNullability: null, builtIn: false, type, typeMapping) + { + } + + public SqlFunctionExpression( + [NotNull] SqlExpression instance, + [NotNull] string functionName, + bool nullable, + bool instancePropagatesNullability, + [NotNull] Type type, + [CanBeNull] RelationalTypeMapping typeMapping) + : this(Check.NotNull(instance, nameof(instance)), schema: null, functionName, nullable, instancePropagatesNullability, builtIn: true, type, typeMapping) + { + } + + private SqlFunctionExpression( + [CanBeNull] SqlExpression instance, + [CanBeNull] string schema, [NotNull] string name, + bool nullable, + bool? instancePropagatesNullability, + bool builtIn, + [NotNull] Type type, + [CanBeNull] RelationalTypeMapping typeMapping) + : this(instance, schema, name, niladic: true, arguments: null, nullable, instancePropagatesNullability, argumentsPropagateNullability: null, builtIn, type, typeMapping) + { + } + + public SqlFunctionExpression( + [NotNull] string functionName, + [NotNull] IEnumerable arguments, + bool nullable, + [NotNull] IEnumerable argumentsPropagateNullability, + [NotNull] Type type, + [CanBeNull] RelationalTypeMapping typeMapping) + : this(instance: null, schema: null, functionName, arguments, nullable, instancePropagatesNullability: null, argumentsPropagateNullability, builtIn: true, type, typeMapping) + { + } + + public SqlFunctionExpression( + [CanBeNull] string schema, + [NotNull] string functionName, + [NotNull] IEnumerable arguments, + bool nullable, + [NotNull] IEnumerable argumentsPropagateNullability, [NotNull] Type type, [CanBeNull] RelationalTypeMapping typeMapping) - => CreateNiladic(name, nullable: true, type, typeMapping); + : this(instance: null, Check.NullButNotEmpty(schema, nameof(schema)), functionName, arguments, nullable, instancePropagatesNullability: null, argumentsPropagateNullability, builtIn: false, type, typeMapping) + { + } + + public SqlFunctionExpression( + [NotNull] SqlExpression instance, + [NotNull] string functionName, + [NotNull] IEnumerable arguments, + bool nullable, + bool instancePropagatesNullability, + [NotNull] IEnumerable argumentsPropagateNullability, + [NotNull] Type type, + [CanBeNull] RelationalTypeMapping typeMapping) + : this(Check.NotNull(instance, nameof(instance)), schema: null, functionName, arguments, nullable, instancePropagatesNullability, argumentsPropagateNullability, builtIn: true, type, typeMapping) + { + } + + private SqlFunctionExpression( + [CanBeNull] SqlExpression instance, + [CanBeNull] string schema, + [NotNull] string name, + [NotNull] IEnumerable arguments, + bool nullable, + bool? instancePropagatesNullability, + [NotNull] IEnumerable argumentsPropagateNullability, + bool builtIn, + [NotNull] Type type, + [CanBeNull] RelationalTypeMapping typeMapping) + : this(instance, schema, name, niladic: false, Check.NotNull(arguments, nameof(arguments)), nullable, instancePropagatesNullability, Check.NotNull(argumentsPropagateNullability, nameof(argumentsPropagateNullability)) , builtIn, type, typeMapping) + { + } + + private SqlFunctionExpression( + [CanBeNull] SqlExpression instance, + [CanBeNull] string schema, + [NotNull] string name, + bool niladic, + [CanBeNull] IEnumerable arguments, + bool nullable, + bool? instancePropagatesNullability, + [CanBeNull] IEnumerable argumentsPropagateNullability, + bool builtIn, + [NotNull] Type type, + [CanBeNull] RelationalTypeMapping typeMapping) + : base(type, typeMapping) + { + Check.NotEmpty(name, nameof(name)); + Check.NotNull(type, nameof(type)); + + Instance = instance; + Name = name; + Schema = schema; + IsNiladic = niladic; + IsBuiltIn = builtIn; + Arguments = arguments?.ToList(); + IsNullable = nullable; + InstancePropagatesNullability = instancePropagatesNullability; + ArgumentsPropagateNullability = argumentsPropagateNullability?.ToList(); + } + + public virtual string Name { get; } + public virtual string Schema { get; } + public virtual bool IsNiladic { get; } + public virtual bool IsBuiltIn { get; } + public virtual IReadOnlyList Arguments { get; } + public virtual SqlExpression Instance { get; } + + public virtual bool IsNullable { get; private set; } + + public virtual bool? InstancePropagatesNullability { get; private set; } + + public virtual IReadOnlyList ArgumentsPropagateNullability { get; private set; } + + protected override Expression VisitChildren(ExpressionVisitor visitor) + { + Check.NotNull(visitor, nameof(visitor)); + + var changed = false; + var instance = (SqlExpression)visitor.Visit(Instance); + changed |= instance != Instance; + + SqlExpression[] arguments = default; + if (!IsNiladic) + { + arguments = new SqlExpression[Arguments.Count]; + for (var i = 0; i < arguments.Length; i++) + { + arguments[i] = (SqlExpression)visitor.Visit(Arguments[i]); + changed |= arguments[i] != Arguments[i]; + } + } + + return changed + ? new SqlFunctionExpression( + instance, + Schema, + Name, + IsNiladic, + arguments, + IsNullable, + InstancePropagatesNullability, + ArgumentsPropagateNullability, + IsBuiltIn, + Type, + TypeMapping) + : this; + } + + public virtual SqlFunctionExpression ApplyTypeMapping([CanBeNull] RelationalTypeMapping typeMapping) + => new SqlFunctionExpression( + Instance, + Schema, + Name, + IsNiladic, + Arguments, + IsNullable, + InstancePropagatesNullability, + ArgumentsPropagateNullability, + IsBuiltIn, + Type, + typeMapping ?? TypeMapping); + + public virtual SqlFunctionExpression Update([CanBeNull] SqlExpression instance, [CanBeNull] IReadOnlyList arguments) + { + return instance != Instance || !arguments?.SequenceEqual(Arguments) == true + ? new SqlFunctionExpression( + instance, + Schema, + Name, + IsNiladic, + arguments, + IsNullable, + InstancePropagatesNullability, + ArgumentsPropagateNullability, + IsBuiltIn, + Type, + TypeMapping) + : this; + } + + public override void Print(ExpressionPrinter expressionPrinter) + { + Check.NotNull(expressionPrinter, nameof(expressionPrinter)); + + if (!string.IsNullOrEmpty(Schema)) + { + expressionPrinter.Append(Schema).Append(".").Append(Name); + } + else + { + if (Instance != null) + { + expressionPrinter.Visit(Instance); + expressionPrinter.Append("."); + } + + expressionPrinter.Append(Name); + } + if (!IsNiladic) + { + expressionPrinter.Append("("); + expressionPrinter.VisitCollection(Arguments); + expressionPrinter.Append(")"); + } + } + + public override bool Equals(object obj) + => obj != null + && (ReferenceEquals(this, obj) + || obj is SqlFunctionExpression sqlFunctionExpression + && Equals(sqlFunctionExpression)); + + private bool Equals(SqlFunctionExpression sqlFunctionExpression) + => base.Equals(sqlFunctionExpression) + && string.Equals(Name, sqlFunctionExpression.Name) + && string.Equals(Schema, sqlFunctionExpression.Schema) + && ((Instance == null && sqlFunctionExpression.Instance == null) + || (Instance != null && Instance.Equals(sqlFunctionExpression.Instance))) + && Arguments.SequenceEqual(sqlFunctionExpression.Arguments); + + public override int GetHashCode() + { + var hash = new HashCode(); + hash.Add(base.GetHashCode()); + hash.Add(Name); + hash.Add(IsNiladic); + hash.Add(Schema); + hash.Add(Instance); + for (var i = 0; i < Arguments.Count; i++) + { + hash.Add(Arguments[i]); + } + + return hash.ToHashCode(); + } + + #region ObsoleteMethods + + [Obsolete("Use new SqlFunctionExpression(...) with appropriate arguments.")] + [EditorBrowsable(EditorBrowsableState.Never)] + public static SqlFunctionExpression CreateNiladic( + [NotNull] string name, + [NotNull] Type type, + [CanBeNull] RelationalTypeMapping typeMapping) + => CreateNiladic(name, nullable: true, type, typeMapping); + + [Obsolete("Use new SqlFunctionExpression(...) with appropriate arguments.")] + [EditorBrowsable(EditorBrowsableState.Never)] public static SqlFunctionExpression CreateNiladic( [NotNull] string schema, [NotNull] string name, @@ -26,7 +292,8 @@ public static SqlFunctionExpression CreateNiladic( [CanBeNull] RelationalTypeMapping typeMapping) => CreateNiladic(schema, name, nullable: true, type, typeMapping); - [Obsolete("Use constructor that explicitly specifies value for 'instancePropagatesNullability' argument.")] + [Obsolete("Use new SqlFunctionExpression(...) with appropriate arguments.")] + [EditorBrowsable(EditorBrowsableState.Never)] public static SqlFunctionExpression CreateNiladic( [NotNull] SqlExpression instance, [NotNull] string name, @@ -34,7 +301,8 @@ public static SqlFunctionExpression CreateNiladic( [CanBeNull] RelationalTypeMapping typeMapping) => CreateNiladic(instance, name, nullable: true, instancePropagatesNullability: false, type, typeMapping); - [Obsolete("Use constructor that explicitly specifies value for 'instancePropagatesNullability' and 'argumentsPropagateNullability' arguments.")] + [Obsolete("Use new SqlFunctionExpression(...) with appropriate arguments.")] + [EditorBrowsable(EditorBrowsableState.Never)] public static SqlFunctionExpression Create( [NotNull] SqlExpression instance, [NotNull] string name, @@ -51,7 +319,8 @@ public static SqlFunctionExpression Create( type, typeMapping); - [Obsolete("Use constructor that explicitly specifies value for 'argumentsPropagateNullability' argument.")] + [Obsolete("Use new SqlFunctionExpression(...) with appropriate arguments.")] + [EditorBrowsable(EditorBrowsableState.Never)] public static SqlFunctionExpression Create( [NotNull] string name, [NotNull] IEnumerable arguments, @@ -59,7 +328,8 @@ public static SqlFunctionExpression Create( [CanBeNull] RelationalTypeMapping typeMapping) => Create(name, arguments, nullable: true, argumentsPropagateNullability: arguments.Select(a => false), type, typeMapping); - [Obsolete("Use constructor that explicitly specifies value for 'argumentsPropagateNullability' argument.")] + [Obsolete("Use new SqlFunctionExpression(...) with appropriate arguments.")] + [EditorBrowsable(EditorBrowsableState.Never)] public static SqlFunctionExpression Create( [CanBeNull] string schema, [NotNull] string name, @@ -68,6 +338,8 @@ public static SqlFunctionExpression Create( [CanBeNull] RelationalTypeMapping typeMapping) => Create(schema, name, arguments, nullable: true, argumentsPropagateNullability: arguments.Select(a => false), type, typeMapping); + [Obsolete("Use new SqlFunctionExpression(...) with appropriate arguments.")] + [EditorBrowsable(EditorBrowsableState.Never)] public static SqlFunctionExpression CreateNiladic( [NotNull] string name, bool nullable, @@ -91,6 +363,8 @@ public static SqlFunctionExpression CreateNiladic( typeMapping); } + [Obsolete("Use new SqlFunctionExpression(...) with appropriate arguments.")] + [EditorBrowsable(EditorBrowsableState.Never)] public static SqlFunctionExpression CreateNiladic( [NotNull] string schema, [NotNull] string name, @@ -116,6 +390,8 @@ public static SqlFunctionExpression CreateNiladic( typeMapping); } + [Obsolete("Use new SqlFunctionExpression(...) with appropriate arguments.")] + [EditorBrowsable(EditorBrowsableState.Never)] public static SqlFunctionExpression CreateNiladic( [NotNull] SqlExpression instance, [NotNull] string name, @@ -142,6 +418,8 @@ public static SqlFunctionExpression CreateNiladic( typeMapping); } + [Obsolete("Use new SqlFunctionExpression(...) with appropriate arguments.")] + [EditorBrowsable(EditorBrowsableState.Never)] public static SqlFunctionExpression Create( [NotNull] SqlExpression instance, [NotNull] string name, @@ -172,6 +450,8 @@ public static SqlFunctionExpression Create( typeMapping); } + [Obsolete("Use new SqlFunctionExpression(...) with appropriate arguments.")] + [EditorBrowsable(EditorBrowsableState.Never)] public static SqlFunctionExpression Create( [NotNull] string name, [NotNull] IEnumerable arguments, @@ -198,6 +478,8 @@ public static SqlFunctionExpression Create( typeMapping); } + [Obsolete("Use new SqlFunctionExpression(...) with appropriate arguments.")] + [EditorBrowsable(EditorBrowsableState.Never)] public static SqlFunctionExpression Create( [CanBeNull] string schema, [NotNull] string name, @@ -225,164 +507,6 @@ public static SqlFunctionExpression Create( typeMapping); } - public SqlFunctionExpression( - [CanBeNull] SqlExpression instance, - [CanBeNull] string schema, - [NotNull] string name, - bool niladic, - [CanBeNull] IEnumerable arguments, - bool nullable, - bool? instancePropagatesNullability, - [CanBeNull] IEnumerable argumentsPropagateNullability, - bool builtIn, - [NotNull] Type type, - [CanBeNull] RelationalTypeMapping typeMapping) - : base(type, typeMapping) - { - Check.NotEmpty(name, nameof(name)); - Check.NotNull(type, nameof(type)); - - Instance = instance; - Name = name; - Schema = schema; - IsNiladic = niladic; - IsBuiltIn = builtIn; - Arguments = (arguments ?? Array.Empty()).ToList(); - IsNullable = nullable; - InstancePropagatesNullability = instancePropagatesNullability; - ArgumentsPropagateNullability = (argumentsPropagateNullability ?? Array.Empty()).ToList(); - } - - public virtual string Name { get; } - public virtual string Schema { get; } - public virtual bool IsNiladic { get; } - public virtual bool IsBuiltIn { get; } - public virtual IReadOnlyList Arguments { get; } - public virtual SqlExpression Instance { get; } - - public virtual bool IsNullable { get; private set; } - - public virtual bool? InstancePropagatesNullability { get; private set; } - - public virtual IReadOnlyList ArgumentsPropagateNullability { get; private set; } - - protected override Expression VisitChildren(ExpressionVisitor visitor) - { - Check.NotNull(visitor, nameof(visitor)); - - var changed = false; - var instance = (SqlExpression)visitor.Visit(Instance); - changed |= instance != Instance; - var arguments = new SqlExpression[Arguments.Count]; - for (var i = 0; i < arguments.Length; i++) - { - arguments[i] = (SqlExpression)visitor.Visit(Arguments[i]); - changed |= arguments[i] != Arguments[i]; - } - - return changed - ? new SqlFunctionExpression( - instance, - Schema, - Name, - IsNiladic, - arguments, - IsNullable, - InstancePropagatesNullability, - ArgumentsPropagateNullability, - IsBuiltIn, - Type, - TypeMapping) - : this; - } - - public virtual SqlFunctionExpression ApplyTypeMapping([CanBeNull] RelationalTypeMapping typeMapping) - => new SqlFunctionExpression( - Instance, - Schema, - Name, - IsNiladic, - Arguments, - IsNullable, - InstancePropagatesNullability, - ArgumentsPropagateNullability, - IsBuiltIn, - Type, - typeMapping ?? TypeMapping); - - public virtual SqlFunctionExpression Update([CanBeNull] SqlExpression instance, [CanBeNull] IReadOnlyList arguments) - { - return instance != Instance || !arguments.SequenceEqual(Arguments) - ? new SqlFunctionExpression( - instance, - Schema, - Name, - IsNiladic, - arguments, - IsNullable, - InstancePropagatesNullability, - ArgumentsPropagateNullability, - IsBuiltIn, - Type, - TypeMapping) - : this; - } - - public override void Print(ExpressionPrinter expressionPrinter) - { - Check.NotNull(expressionPrinter, nameof(expressionPrinter)); - - if (!string.IsNullOrEmpty(Schema)) - { - expressionPrinter.Append(Schema).Append(".").Append(Name); - } - else - { - if (Instance != null) - { - expressionPrinter.Visit(Instance); - expressionPrinter.Append("."); - } - - expressionPrinter.Append(Name); - } - - if (!IsNiladic) - { - expressionPrinter.Append("("); - expressionPrinter.VisitCollection(Arguments); - expressionPrinter.Append(")"); - } - } - - public override bool Equals(object obj) - => obj != null - && (ReferenceEquals(this, obj) - || obj is SqlFunctionExpression sqlFunctionExpression - && Equals(sqlFunctionExpression)); - - private bool Equals(SqlFunctionExpression sqlFunctionExpression) - => base.Equals(sqlFunctionExpression) - && string.Equals(Name, sqlFunctionExpression.Name) - && string.Equals(Schema, sqlFunctionExpression.Schema) - && ((Instance == null && sqlFunctionExpression.Instance == null) - || (Instance != null && Instance.Equals(sqlFunctionExpression.Instance))) - && Arguments.SequenceEqual(sqlFunctionExpression.Arguments); - - public override int GetHashCode() - { - var hash = new HashCode(); - hash.Add(base.GetHashCode()); - hash.Add(Name); - hash.Add(IsNiladic); - hash.Add(Schema); - hash.Add(Instance); - for (var i = 0; i < Arguments.Count; i++) - { - hash.Add(Arguments[i]); - } - - return hash.ToHashCode(); - } + #endregion } } diff --git a/src/EFCore.SqlServer/Query/Internal/SearchConditionConvertingExpressionVisitor.cs b/src/EFCore.SqlServer/Query/Internal/SearchConditionConvertingExpressionVisitor.cs index ade8952c423..48553ffebb3 100644 --- a/src/EFCore.SqlServer/Query/Internal/SearchConditionConvertingExpressionVisitor.cs +++ b/src/EFCore.SqlServer/Query/Internal/SearchConditionConvertingExpressionVisitor.cs @@ -428,10 +428,14 @@ protected override Expression VisitSqlFunction(SqlFunctionExpression sqlFunction var parentSearchCondition = _isSearchCondition; _isSearchCondition = false; var instance = (SqlExpression)Visit(sqlFunctionExpression.Instance); - var arguments = new SqlExpression[sqlFunctionExpression.Arguments.Count]; - for (var i = 0; i < arguments.Length; i++) + SqlExpression[] arguments = default; + if (!sqlFunctionExpression.IsNiladic) { - arguments[i] = (SqlExpression)Visit(sqlFunctionExpression.Arguments[i]); + arguments = new SqlExpression[sqlFunctionExpression.Arguments.Count]; + for (var i = 0; i < arguments.Length; i++) + { + arguments[i] = (SqlExpression)Visit(sqlFunctionExpression.Arguments[i]); + } } _isSearchCondition = parentSearchCondition; diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerDateTimeMemberTranslator.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerDateTimeMemberTranslator.cs index 97218f35f02..fa068e137e8 100644 --- a/src/EFCore.SqlServer/Query/Internal/SqlServerDateTimeMemberTranslator.cs +++ b/src/EFCore.SqlServer/Query/Internal/SqlServerDateTimeMemberTranslator.cs @@ -7,6 +7,7 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Query; using Microsoft.EntityFrameworkCore.Query.SqlExpressions; +using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Utilities; namespace Microsoft.EntityFrameworkCore.SqlServer.Query.Internal @@ -33,6 +34,7 @@ private static readonly Dictionary _datePartMapping }; private readonly ISqlExpressionFactory _sqlExpressionFactory; + private readonly IRelationalTypeMappingSource _typeMappingSource; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -40,9 +42,14 @@ private static readonly Dictionary _datePartMapping /// 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 SqlServerDateTimeMemberTranslator([NotNull] ISqlExpressionFactory sqlExpressionFactory) + public SqlServerDateTimeMemberTranslator( + [NotNull] ISqlExpressionFactory sqlExpressionFactory, [NotNull] IRelationalTypeMappingSource typeMappingSource) { + Check.NotNull(sqlExpressionFactory, nameof(sqlExpressionFactory)); + Check.NotNull(typeMappingSource, nameof(typeMappingSource)); + _sqlExpressionFactory = sqlExpressionFactory; + _typeMappingSource = typeMappingSource; } /// @@ -84,7 +91,7 @@ public virtual SqlExpression Translate(SqlExpression instance, MemberInfo member returnType, declaringType == typeof(DateTime) ? instance.TypeMapping - : _sqlExpressionFactory.FindMapping(typeof(DateTime))); + : _typeMappingSource.FindMapping(typeof(DateTime))); case nameof(DateTime.TimeOfDay): return _sqlExpressionFactory.Convert(instance, returnType); diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerMemberTranslatorProvider.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerMemberTranslatorProvider.cs index 9916a781f3e..d83de282ca1 100644 --- a/src/EFCore.SqlServer/Query/Internal/SqlServerMemberTranslatorProvider.cs +++ b/src/EFCore.SqlServer/Query/Internal/SqlServerMemberTranslatorProvider.cs @@ -3,6 +3,8 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Query; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.EntityFrameworkCore.Utilities; namespace Microsoft.EntityFrameworkCore.SqlServer.Query.Internal { @@ -20,15 +22,19 @@ public class SqlServerMemberTranslatorProvider : RelationalMemberTranslatorProvi /// 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 SqlServerMemberTranslatorProvider([NotNull] RelationalMemberTranslatorProviderDependencies dependencies) + public SqlServerMemberTranslatorProvider( + [NotNull] RelationalMemberTranslatorProviderDependencies dependencies, + [NotNull] IRelationalTypeMappingSource typeMappingSource) : base(dependencies) { + Check.NotNull(typeMappingSource, nameof(typeMappingSource)); + var sqlExpressionFactory = dependencies.SqlExpressionFactory; AddTranslators( new IMemberTranslator[] { - new SqlServerDateTimeMemberTranslator(sqlExpressionFactory), + new SqlServerDateTimeMemberTranslator(sqlExpressionFactory, typeMappingSource), new SqlServerStringMemberTranslator(sqlExpressionFactory), new SqlServerTimeSpanMemberTranslator(sqlExpressionFactory) }); diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerQuerySqlGenerator.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerQuerySqlGenerator.cs index c12d794fef9..e839cf38d8f 100644 --- a/src/EFCore.SqlServer/Query/Internal/SqlServerQuerySqlGenerator.cs +++ b/src/EFCore.SqlServer/Query/Internal/SqlServerQuerySqlGenerator.cs @@ -93,14 +93,21 @@ protected override Expression VisitSqlFunction(SqlFunctionExpression sqlFunction if (!sqlFunctionExpression.IsBuiltIn && string.IsNullOrEmpty(sqlFunctionExpression.Schema)) { - sqlFunctionExpression = SqlFunctionExpression.Create( - schema: "dbo", - sqlFunctionExpression.Name, - sqlFunctionExpression.Arguments, - sqlFunctionExpression.IsNullable, - sqlFunctionExpression.ArgumentsPropagateNullability, - sqlFunctionExpression.Type, - sqlFunctionExpression.TypeMapping); + sqlFunctionExpression = sqlFunctionExpression.IsNiladic + ? new SqlFunctionExpression( + schema: "dbo", + sqlFunctionExpression.Name, + sqlFunctionExpression.IsNullable, + sqlFunctionExpression.Type, + sqlFunctionExpression.TypeMapping) + : new SqlFunctionExpression( + schema: "dbo", + sqlFunctionExpression.Name, + sqlFunctionExpression.Arguments, + sqlFunctionExpression.IsNullable, + sqlFunctionExpression.ArgumentsPropagateNullability, + 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 729b8e93e40..8c3d0dd1feb 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/UdfDbFunctionTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/UdfDbFunctionTestBase.cs @@ -221,24 +221,14 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .HasTranslation(args => new SqlFragmentExpression("'Two'")); var isDateMethodInfo = typeof(UDFSqlContext).GetMethod(nameof(IsDateStatic)); modelBuilder.HasDbFunction(isDateMethodInfo) - .HasTranslation(args => SqlFunctionExpression.Create( - "IsDate", - args, - nullable: true, - argumentsPropagateNullability: args.Select(a => true).ToList(), - isDateMethodInfo.ReturnType, - null)); + .HasTranslation(args => new SqlFunctionExpression( + "IsDate", args, nullable: true, argumentsPropagateNullability: args.Select(a => true).ToList(), isDateMethodInfo.ReturnType, null)); var methodInfo = typeof(UDFSqlContext).GetMethod(nameof(MyCustomLengthStatic)); modelBuilder.HasDbFunction(methodInfo) - .HasTranslation(args => SqlFunctionExpression.Create( - "len", - args, - nullable: true, - argumentsPropagateNullability: args.Select(a => true).ToList(), - methodInfo.ReturnType, - null)); + .HasTranslation(args => new SqlFunctionExpression( + "len", args, nullable: true, argumentsPropagateNullability: args.Select(a => true).ToList(), methodInfo.ReturnType, null)); modelBuilder.HasDbFunction(typeof(UDFSqlContext).GetMethod(nameof(AddValues), new[] { typeof(int), typeof(int) })); @@ -255,7 +245,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .HasName("GetReportingPeriodStartDate"); var isDateMethodInfo2 = typeof(UDFSqlContext).GetMethod(nameof(IsDateInstance)); modelBuilder.HasDbFunction(isDateMethodInfo2) - .HasTranslation(args => SqlFunctionExpression.Create( + .HasTranslation(args => new SqlFunctionExpression( "IsDate", args, nullable: true, @@ -268,7 +258,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) var methodInfo2 = typeof(UDFSqlContext).GetMethod(nameof(MyCustomLengthInstance)); modelBuilder.HasDbFunction(methodInfo2) - .HasTranslation(args => SqlFunctionExpression.Create( + .HasTranslation(args => new SqlFunctionExpression( "len", args, nullable: true, @@ -308,7 +298,8 @@ protected override void Seed(DbContext context) var order11 = new Order { - Name = "Order11", OrderDate = new DateTime(2000, 1, 20), + Name = "Order11", + OrderDate = new DateTime(2000, 1, 20), Items = new List { new LineItem { Quantity = 5, Product = product1}, @@ -316,7 +307,10 @@ protected override void Seed(DbContext context) } }; - var order12 = new Order { Name = "Order12", OrderDate = new DateTime(2000, 2, 21), + var order12 = new Order + { + Name = "Order12", + OrderDate = new DateTime(2000, 2, 21), Items = new List { new LineItem { Quantity = 1, Product = product1}, @@ -325,14 +319,20 @@ protected override void Seed(DbContext context) } }; - var order13 = new Order { Name = "Order13", OrderDate = new DateTime(2001, 3, 20), + var order13 = new Order + { + Name = "Order13", + OrderDate = new DateTime(2001, 3, 20), Items = new List { new LineItem { Quantity = 50, Product = product4}, } }; - var order21 = new Order { Name = "Order21", OrderDate = new DateTime(2000, 4, 21), + var order21 = new Order + { + Name = "Order21", + OrderDate = new DateTime(2000, 4, 21), Items = new List { new LineItem { Quantity = 1, Product = product1}, @@ -341,7 +341,10 @@ protected override void Seed(DbContext context) } }; - var order22 = new Order { Name = "Order22", OrderDate = new DateTime(2000, 5, 20), + var order22 = new Order + { + Name = "Order22", + OrderDate = new DateTime(2000, 5, 20), Items = new List { new LineItem { Quantity = 34, Product = product3}, @@ -349,7 +352,10 @@ protected override void Seed(DbContext context) } }; - var order31 = new Order { Name = "Order31", OrderDate = new DateTime(2001, 6, 21), + var order31 = new Order + { + Name = "Order31", + OrderDate = new DateTime(2001, 6, 21), Items = new List { new LineItem { Quantity = 5, Product = product5} @@ -441,7 +447,8 @@ public virtual void Scalar_Function_ClientEval_Method_As_Translateable_Method_Pa where c.Id == 1 select new { - c.FirstName, OrderCount = UDFSqlContext.CustomerOrderCountStatic(UDFSqlContext.AddFiveStatic(c.Id - 5)) + c.FirstName, + OrderCount = UDFSqlContext.CustomerOrderCountStatic(UDFSqlContext.AddFiveStatic(c.Id - 5)) }).Single()); } @@ -1383,12 +1390,12 @@ public virtual void QF_Select_Correlated_Direct_With_Function_Query_Parameter_Co using (var context = CreateContext()) { var cust = (from c in context.Customers - where c.Id == 1 - select new - { - c.Id, - Orders = context.GetOrdersWithMultipleProducts(context.AddValues(c.Id, 1)).ToList() - }).ToList(); + where c.Id == 1 + select new + { + c.Id, + Orders = context.GetOrdersWithMultipleProducts(context.AddValues(c.Id, 1)).ToList() + }).ToList(); Assert.Single(cust); @@ -1430,11 +1437,11 @@ public virtual void QF_Select_Correlated_Subquery_In_Anonymous_Nested_With_QF() using (var context = CreateContext()) { var results = (from o in context.Orders - join osub in (from c in context.Customers - from a in context.GetOrdersWithMultipleProducts(c.Id) - select a.OrderId - ) on o.Id equals osub - select new { o.CustomerId, o.OrderDate }).ToList(); + join osub in (from c in context.Customers + from a in context.GetOrdersWithMultipleProducts(c.Id) + select a.OrderId + ) on o.Id equals osub + select new { o.CustomerId, o.OrderDate }).ToList(); Assert.Equal(4, results.Count); @@ -1557,7 +1564,7 @@ public virtual void QF_Correlated_Select_In_Anonymous() { using (var context = CreateContext()) { - var cust = (from c in context.Customers + var cust = (from c in context.Customers orderby c.Id select new { @@ -1916,11 +1923,11 @@ orderby c.Id } } - #endregion + #endregion - private void AssertTranslationFailed(Action testCode) - => Assert.Contains( - CoreStrings.TranslationFailed("").Substring(21), - Assert.Throws(testCode).Message); + private void AssertTranslationFailed(Action testCode) + => Assert.Contains( + CoreStrings.TranslationFailed("").Substring(21), + Assert.Throws(testCode).Message); } } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/SpatialQuerySqlServerFixture.cs b/test/EFCore.SqlServer.FunctionalTests/Query/SpatialQuerySqlServerFixture.cs index 76108c92395..0989f326a0d 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/SpatialQuerySqlServerFixture.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/SpatialQuerySqlServerFixture.cs @@ -36,7 +36,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con modelBuilder.HasDbFunction( typeof(GeoExtensions).GetMethod(nameof(GeoExtensions.Distance)), b => b.HasTranslation( - e => SqlFunctionExpression.Create( + e => new SqlFunctionExpression( instance: e.First(), "STDistance", arguments: e.Skip(1), diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/SpatialQuerySqliteFixture.cs b/test/EFCore.Sqlite.FunctionalTests/Query/SpatialQuerySqliteFixture.cs index e460b0cf713..7edd5674768 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/SpatialQuerySqliteFixture.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/SpatialQuerySqliteFixture.cs @@ -38,7 +38,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con modelBuilder.HasDbFunction( typeof(GeoExtensions).GetMethod(nameof(GeoExtensions.Distance)), b => b.HasTranslation( - e => SqlFunctionExpression.Create( + e => new SqlFunctionExpression( "Distance", arguments: e, nullable: true,