diff --git a/src/EFCore.Design/EFCore.Design.csproj b/src/EFCore.Design/EFCore.Design.csproj index 5ed7805d640..17f9c6c3a33 100644 --- a/src/EFCore.Design/EFCore.Design.csproj +++ b/src/EFCore.Design/EFCore.Design.csproj @@ -8,6 +8,7 @@ true true true + EF1003 diff --git a/src/EFCore.Relational/EFCore.Relational.csproj b/src/EFCore.Relational/EFCore.Relational.csproj index 9725aabf412..77feb85648d 100644 --- a/src/EFCore.Relational/EFCore.Relational.csproj +++ b/src/EFCore.Relational/EFCore.Relational.csproj @@ -8,6 +8,7 @@ Microsoft.EntityFrameworkCore true true + $(NoWarn);EF1003 diff --git a/src/EFCore.Relational/Metadata/IRelationalModel.cs b/src/EFCore.Relational/Metadata/IRelationalModel.cs index 0900bddabdf..2526098074e 100644 --- a/src/EFCore.Relational/Metadata/IRelationalModel.cs +++ b/src/EFCore.Relational/Metadata/IRelationalModel.cs @@ -66,6 +66,13 @@ IEnumerable Sequences /// The table with a given name or if no table with the given name is defined. ITable? FindTable(string name, string? schema); + /// + /// Gets the default table with the given name. Returns if no table with the given name is defined. + /// + /// The name of the table. + /// The default table with a given name or if no table with the given name is defined. + TableBase? FindDefaultTable(string name); + /// /// Gets the view with the given name. Returns if no view with the given name is defined. /// diff --git a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs index 4359aa432c8..d39a5713d3a 100644 --- a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs +++ b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs @@ -91,33 +91,28 @@ public override bool IsReadOnly /// public virtual ITable? FindTable(string name, string? schema) - => Tables.TryGetValue((name, schema), out var table) - ? table - : null; + => Tables.GetValueOrDefault((name, schema)); + + // TODO: Confirm that this makes sense + /// + public virtual TableBase? FindDefaultTable(string name) + => DefaultTables.GetValueOrDefault(name); /// public virtual IView? FindView(string name, string? schema) - => Views.TryGetValue((name, schema), out var view) - ? view - : null; + => Views.GetValueOrDefault((name, schema)); /// public virtual ISqlQuery? FindQuery(string name) - => Queries.TryGetValue(name, out var query) - ? query - : null; + => Queries.GetValueOrDefault(name); /// public virtual IStoreFunction? FindFunction(string name, string? schema, IReadOnlyList parameters) - => Functions.TryGetValue((name, schema, parameters), out var function) - ? function - : null; + => Functions.GetValueOrDefault((name, schema, parameters)); /// public virtual IStoreStoredProcedure? FindStoredProcedure(string name, string? schema) - => StoredProcedures.TryGetValue((name, schema), out var storedProcedure) - ? storedProcedure - : null; + => StoredProcedures.GetValueOrDefault((name, schema)); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore.Relational/Query/IRelationalQuotableExpression.cs b/src/EFCore.Relational/Query/IRelationalQuotableExpression.cs new file mode 100644 index 00000000000..f8e59c412c8 --- /dev/null +++ b/src/EFCore.Relational/Query/IRelationalQuotableExpression.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.EntityFrameworkCore.Query; + +/// +/// Represents an expression that is quotable, that is, capable of returning an expression that, when evaluated, would construct an +/// expression identical to this one. Used to generate code for precompiled queries, which reconstructs this expression. +/// +[Experimental("EF1003")] +public interface IRelationalQuotableExpression +{ + /// + /// Quotes the expression; that is, returns an expression that, when evaluated, would construct an expression identical to this + /// one. Used to generate code for precompiled queries, which reconstructs this expression. + /// + Expression Quote(); +} diff --git a/src/EFCore.Relational/Query/ISqlExpressionFactory.cs b/src/EFCore.Relational/Query/ISqlExpressionFactory.cs index fabab2bb19d..ecb12337e04 100644 --- a/src/EFCore.Relational/Query/ISqlExpressionFactory.cs +++ b/src/EFCore.Relational/Query/ISqlExpressionFactory.cs @@ -443,7 +443,7 @@ SqlFunctionExpression NiladicFunction( /// A value. /// The associated with the expression. /// An expression representing a constant in a SQL tree. - SqlConstantExpression Constant(object? value, RelationalTypeMapping? typeMapping = null); + SqlConstantExpression Constant(object value, RelationalTypeMapping? typeMapping = null); /// /// Creates a new which represents a constant in a SQL tree. diff --git a/src/EFCore.Relational/Query/Internal/FromSqlParameterExpandingExpressionVisitor.cs b/src/EFCore.Relational/Query/Internal/FromSqlParameterExpandingExpressionVisitor.cs index 182b1d54c55..bafd0d3dfe7 100644 --- a/src/EFCore.Relational/Query/Internal/FromSqlParameterExpandingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/Internal/FromSqlParameterExpandingExpressionVisitor.cs @@ -180,7 +180,9 @@ object ProcessConstantValue(object? existingConstantValue) } return _sqlExpressionFactory.Constant( - existingConstantValue, _typeMappingSource.GetMappingForValue(existingConstantValue)); + existingConstantValue, + existingConstantValue?.GetType() ?? typeof(object), + _typeMappingSource.GetMappingForValue(existingConstantValue)); } } } diff --git a/src/EFCore.Relational/Query/Internal/GetValueOrDefaultTranslator.cs b/src/EFCore.Relational/Query/Internal/GetValueOrDefaultTranslator.cs index 15d039e1d90..48f311e2cb5 100644 --- a/src/EFCore.Relational/Query/Internal/GetValueOrDefaultTranslator.cs +++ b/src/EFCore.Relational/Query/Internal/GetValueOrDefaultTranslator.cs @@ -45,7 +45,7 @@ public GetValueOrDefaultTranslator(ISqlExpressionFactory sqlExpressionFactory) return _sqlExpressionFactory.Coalesce( instance, arguments.Count == 0 - ? new SqlConstantExpression(method.ReturnType.GetDefaultValueConstant(), null) + ? new SqlConstantExpression(method.ReturnType.GetDefaultValue(), method.ReturnType, typeMapping: null) : arguments[0], instance.TypeMapping); } diff --git a/src/EFCore.Relational/Query/Internal/TpcTablesExpression.cs b/src/EFCore.Relational/Query/Internal/TpcTablesExpression.cs index 3f37ba90e6d..834a14e080a 100644 --- a/src/EFCore.Relational/Query/Internal/TpcTablesExpression.cs +++ b/src/EFCore.Relational/Query/Internal/TpcTablesExpression.cs @@ -138,6 +138,10 @@ protected override TpcTablesExpression WithAnnotations(IReadOnlyDictionary new(newAlias, EntityType, SelectExpressions, DiscriminatorColumn, DiscriminatorValues, Annotations); + /// + public override Expression Quote() + => throw new UnreachableException("TpcTablesExpression is a temporary tree representation and should never be quoted"); + /// public override TableExpressionBase Clone(string? alias, ExpressionVisitor cloningExpressionVisitor) { diff --git a/src/EFCore.Relational/Query/PathSegment.cs b/src/EFCore.Relational/Query/PathSegment.cs index a8237b74682..9e27268b407 100644 --- a/src/EFCore.Relational/Query/PathSegment.cs +++ b/src/EFCore.Relational/Query/PathSegment.cs @@ -14,8 +14,10 @@ namespace Microsoft.EntityFrameworkCore.Query; /// not used in application code. /// /// -public readonly struct PathSegment +public readonly struct PathSegment : IRelationalQuotableExpression { + private static ConstructorInfo? _pathSegmentPropertyConstructor, _pathSegmentArrayIndexConstructor; + /// /// Creates a new struct representing JSON property access. /// @@ -46,6 +48,21 @@ public PathSegment(SqlExpression arrayIndex) /// public SqlExpression? ArrayIndex { get; } + /// + public Expression Quote() + => this switch + { + { PropertyName: string propertyName } + => Expression.New( + _pathSegmentPropertyConstructor ??= typeof(PathSegment).GetConstructor([typeof(string)])!, + Expression.Constant(propertyName)), + { ArrayIndex: SqlExpression arrayIndex } + => Expression.New( + _pathSegmentArrayIndexConstructor ??= typeof(PathSegment).GetConstructor([typeof(SqlExpression)])!, + arrayIndex.Quote()), + _ => throw new UnreachableException() + }; + /// public override string ToString() => PropertyName diff --git a/src/EFCore.Relational/Query/RelationalExpressionQuotingUtilities.cs b/src/EFCore.Relational/Query/RelationalExpressionQuotingUtilities.cs new file mode 100644 index 00000000000..699ad327ab3 --- /dev/null +++ b/src/EFCore.Relational/Query/RelationalExpressionQuotingUtilities.cs @@ -0,0 +1,150 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics.CodeAnalysis; +using Microsoft.EntityFrameworkCore.Metadata.Internal; +using Microsoft.EntityFrameworkCore.Query.SqlExpressions; +using static System.Linq.Expressions.Expression; + +namespace Microsoft.EntityFrameworkCore.Query; + +/// +/// Utilities used for implementing . +/// +[Experimental("EF1003")] +public static class RelationalExpressionQuotingUtilities +{ + private static readonly ParameterExpression RelationalModelParameter + = Parameter(typeof(RelationalModel), "relationalModel"); + private static readonly ParameterExpression RelationalTypeMappingSourceParameter + = Parameter(typeof(RelationalTypeMappingSource), "relationalTypeMappingSource"); + + private static readonly MethodInfo RelationalModelFindTableMethod + = typeof(RelationalModel).GetMethod(nameof(RelationalModel.FindTable), [typeof(string), typeof(string)])!; + + private static readonly MethodInfo RelationalModelFindDefaultTableMethod + = typeof(RelationalModel).GetMethod(nameof(RelationalModel.FindDefaultTable), [typeof(string)])!; + + private static readonly MethodInfo RelationalModelFindViewMethod + = typeof(RelationalModel).GetMethod(nameof(RelationalModel.FindView), [typeof(string), typeof(string)])!; + + private static readonly MethodInfo RelationalModelFindQueryMethod + = typeof(RelationalModel).GetMethod(nameof(RelationalModel.FindQuery), [typeof(string)])!; + + private static readonly MethodInfo RelationalModelFindFunctionMethod + = typeof(RelationalModel).GetMethod( + nameof(RelationalModel.FindFunction), [typeof(string), typeof(string), typeof(IReadOnlyList)])!; + + private static ConstructorInfo? _annotationConstructor; + private static ConstructorInfo? _dictionaryConstructor; + private static MethodInfo? _dictionaryAddMethod; + private static MethodInfo? _hashSetAddMethod; + + private static readonly MethodInfo RelationalTypeMappingSourceFindMappingMethod + = typeof(RelationalTypeMappingSource) + .GetMethod( + nameof(RelationalTypeMappingSource.FindMapping), + [ + typeof(Type), typeof(string), typeof(bool), typeof(bool), typeof(int), typeof(bool), typeof(bool), typeof(int), + typeof(int) + ])!; + + /// + /// If is , returns a with a + /// value. Otherwise, calls and returns the result. + /// + public static Expression VisitOrNull(T? expression) where T : IRelationalQuotableExpression + => expression is null ? Constant(null, typeof(T)) : expression.Quote(); + + /// + /// Quotes a relational type mapping. + /// + public static Expression QuoteTypeMapping(RelationalTypeMapping? typeMapping) + => typeMapping is null + ? Constant(null, typeof(RelationalTypeMapping)) + : Call( + RelationalTypeMappingSourceParameter, + RelationalTypeMappingSourceFindMappingMethod, + Constant(typeMapping.ClrType, typeof(Type)), + Constant(typeMapping.StoreType, typeof(string)), + Constant(false), // TODO: keyOrIndex not accessible + Constant(typeMapping.IsUnicode, typeof(bool?)), + Constant(typeMapping.Size, typeof(int?)), + Constant(false, typeof(bool?)), // TODO: rowversion not accessible + Constant(typeMapping.IsFixedLength, typeof(bool?)), + Constant(typeMapping.Precision, typeof(int?)), + Constant(typeMapping.Scale, typeof(int?))); + + /// + /// Quotes an . + /// + public static Expression QuoteTableBase(ITableBase tableBase) + => tableBase switch + { + ITable table + => Call( + RelationalModelParameter, + RelationalModelFindTableMethod, + Constant(table.Name, typeof(string)), + Constant(table.Schema, typeof(string))), + + TableBase table + => Call( + RelationalModelParameter, + RelationalModelFindDefaultTableMethod, + Constant(table.Name, typeof(string))), + + IView view + => Call( + RelationalModelParameter, + RelationalModelFindViewMethod, + Constant(view.Name, typeof(string)), + Constant(view.Schema, typeof(string))), + + ISqlQuery query + => Call( + RelationalModelParameter, + RelationalModelFindQueryMethod, + Constant(query.Name, typeof(string))), + + IStoreFunction function + => Call( + RelationalModelParameter, + RelationalModelFindFunctionMethod, + Constant(function.Name, typeof(string)), + Constant(function.Schema, typeof(string)), + NewArrayInit(typeof(string), function.Parameters.Select(p => Constant(p.StoreType)))), + + IStoreStoredProcedure => throw new UnreachableException(), + + _ => throw new UnreachableException() + }; + + /// + /// Quotes a set of string tags. + /// + public static Expression QuoteTags(ISet tags) + => ListInit( + New(typeof(HashSet)), + tags.Select( + t => ElementInit( + _hashSetAddMethod ??= typeof(HashSet).GetMethod(nameof(HashSet.Add))!, + Constant(t)))); + + /// + /// Quotes the annotations on a . + /// + public static Expression QuoteAnnotations(IReadOnlyDictionary? annotations) + => annotations is null or { Count: 0 } + ? Constant(null, typeof(IReadOnlyDictionary)) + : ListInit( + New(_dictionaryConstructor ??= typeof(IDictionary).GetConstructor([])!), + annotations.Select( + a => ElementInit( + _dictionaryAddMethod ??= typeof(Dictionary).GetMethod("Add")!, + Constant(a.Key), + New( + _annotationConstructor ??= typeof(Annotation).GetConstructor([typeof(string), typeof(object)])!, + Constant(a.Key), + Constant(a.Value))))); +} diff --git a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.CreateSelect.cs b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.CreateSelect.cs index 6b722f5f5fe..11fd715eced 100644 --- a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.CreateSelect.cs +++ b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.CreateSelect.cs @@ -430,10 +430,13 @@ private void AddEntitySelectConditions(SelectExpression selectExpression, IEntit var concreteEntityTypes = entityType.GetConcreteDerivedTypesInclusive().ToList(); var predicate = concreteEntityTypes.Count == 1 ? (SqlExpression)_sqlExpressionFactory.Equal( - discriminatorColumn, _sqlExpressionFactory.Constant(concreteEntityTypes[0].GetDiscriminatorValue())) + discriminatorColumn, + _sqlExpressionFactory.Constant(concreteEntityTypes[0].GetDiscriminatorValue(), discriminatorColumn.Type)) : _sqlExpressionFactory.In( discriminatorColumn, - concreteEntityTypes.Select(et => _sqlExpressionFactory.Constant(et.GetDiscriminatorValue())).ToArray()); + concreteEntityTypes + .Select(et => _sqlExpressionFactory.Constant(et.GetDiscriminatorValue(), discriminatorColumn.Type)) + .ToArray()); selectExpression.ApplyPredicate(predicate); diff --git a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs index 19bee3f349f..ccbdb80cf56 100644 --- a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs @@ -2482,7 +2482,7 @@ protected virtual ValuesExpression ApplyTypeMappingsOnValuesExpression(ValuesExp newRowValues[i] = new RowValueExpression(newValues); } - return new ValuesExpression(valuesExpression.Alias, newRowValues, newColumnNames, valuesExpression.GetAnnotations()); + return new ValuesExpression(valuesExpression.Alias, newRowValues, newColumnNames); } } } diff --git a/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs index 9d854645ae7..85db91718ea 100644 --- a/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs @@ -556,10 +556,10 @@ SqlExpression GeneratePredicateTpt(StructuralTypeProjectionExpression projection return match ? _sqlExpressionFactory.Equal( discriminatorColumn, - _sqlExpressionFactory.Constant(derivedType.GetDiscriminatorValue())) + _sqlExpressionFactory.Constant(derivedType.GetDiscriminatorValue()!)) : _sqlExpressionFactory.NotEqual( discriminatorColumn, - _sqlExpressionFactory.Constant(derivedType.GetDiscriminatorValue())); + _sqlExpressionFactory.Constant(derivedType.GetDiscriminatorValue()!)); } return QueryCompilationContext.NotTranslatedExpression; @@ -623,7 +623,7 @@ protected override Expression VisitConditional(ConditionalExpression conditional /// protected override Expression VisitConstant(ConstantExpression constantExpression) - => new SqlConstantExpression(constantExpression, null); + => new SqlConstantExpression(constantExpression.Value, constantExpression.Type, typeMapping: null); /// protected override Expression VisitExtension(Expression extensionExpression) @@ -1150,10 +1150,12 @@ SqlExpression GeneratePredicateTpt(StructuralTypeProjectionExpression entityProj return concreteEntityTypes.Count == 1 ? _sqlExpressionFactory.Equal( discriminatorColumn, - _sqlExpressionFactory.Constant(concreteEntityTypes[0].GetDiscriminatorValue())) + _sqlExpressionFactory.Constant(concreteEntityTypes[0].GetDiscriminatorValue(), discriminatorColumn.Type)) : _sqlExpressionFactory.In( discriminatorColumn, - concreteEntityTypes.Select(et => _sqlExpressionFactory.Constant(et.GetDiscriminatorValue())).ToArray()); + concreteEntityTypes + .Select(et => _sqlExpressionFactory.Constant(et.GetDiscriminatorValue(), discriminatorColumn.Type)) + .ToArray()); } return _sqlExpressionFactory.Constant(true); @@ -1326,7 +1328,7 @@ private SqlExpression BindProperty(StructuralTypeReferenceExpression typeReferen if (allRequiredNonPkProperties.Count > 0) { condition = allRequiredNonPkProperties.Select(p => projection.BindProperty(p)) - .Select(c => (SqlExpression)_sqlExpressionFactory.NotEqual(c, _sqlExpressionFactory.Constant(null))) + .Select(c => (SqlExpression)_sqlExpressionFactory.NotEqual(c, _sqlExpressionFactory.Constant(null, c.Type))) .Aggregate((a, b) => _sqlExpressionFactory.AndAlso(a, b)); } @@ -1336,7 +1338,7 @@ private SqlExpression BindProperty(StructuralTypeReferenceExpression typeReferen // If all non principal shared properties are nullable then we need additional condition var atLeastOneNonNullValueInNullableColumnsCondition = nonPrincipalSharedNonPkProperties .Select(p => projection.BindProperty(p)) - .Select(c => (SqlExpression)_sqlExpressionFactory.NotEqual(c, _sqlExpressionFactory.Constant(null))) + .Select(c => (SqlExpression)_sqlExpressionFactory.NotEqual(c, _sqlExpressionFactory.Constant(null, c.Type))) .Aggregate((a, b) => _sqlExpressionFactory.OrElse(a, b)); condition = condition == null @@ -1668,12 +1670,11 @@ private static bool TryEvaluateToConstant(Expression expression, [NotNullWhen(tr if (CanEvaluate(expression)) { sqlConstantExpression = new SqlConstantExpression( - Expression.Constant( - Expression.Lambda>(Expression.Convert(expression, typeof(object))) - .Compile(preferInterpretation: true) - .Invoke(), - expression.Type), - null); + Expression.Lambda>(Expression.Convert(expression, typeof(object))) + .Compile(preferInterpretation: true) + .Invoke(), + expression.Type, + typeMapping: null); return true; } diff --git a/src/EFCore.Relational/Query/SqlExpressionFactory.cs b/src/EFCore.Relational/Query/SqlExpressionFactory.cs index 0dbfacce2d1..df83d3a581d 100644 --- a/src/EFCore.Relational/Query/SqlExpressionFactory.cs +++ b/src/EFCore.Relational/Query/SqlExpressionFactory.cs @@ -693,12 +693,12 @@ public virtual SqlFragmentExpression Fragment(string sql) => new(sql); /// - public virtual SqlConstantExpression Constant(object? value, RelationalTypeMapping? typeMapping = null) - => new(Expression.Constant(value), typeMapping); + public virtual SqlConstantExpression Constant(object value, RelationalTypeMapping? typeMapping = null) + => new(value, typeMapping); /// public virtual SqlConstantExpression Constant(object? value, Type type, RelationalTypeMapping? typeMapping = null) - => new(Expression.Constant(value, type), typeMapping); + => new(value, type, typeMapping); /// public virtual bool TryCreateLeast( diff --git a/src/EFCore.Relational/Query/SqlExpressions/AtTimeZoneExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/AtTimeZoneExpression.cs index f4b75295467..33cf6bef843 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/AtTimeZoneExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/AtTimeZoneExpression.cs @@ -14,6 +14,8 @@ namespace Microsoft.EntityFrameworkCore.Query.SqlExpressions; /// public class AtTimeZoneExpression : SqlExpression { + private static ConstructorInfo? _quotingConstructor; + /// /// Creates a new instance of the class. /// @@ -63,6 +65,16 @@ public virtual AtTimeZoneExpression Update(SqlExpression operand, SqlExpression ? new AtTimeZoneExpression(operand, timeZone, Type, TypeMapping) : this; + /// + public override Expression Quote() + => New( + _quotingConstructor ??= typeof(AtTimeZoneExpression).GetConstructor( + [typeof(SqlExpression), typeof(SqlExpression), typeof(Type), typeof(RelationalTypeMapping)])!, + Operand.Quote(), + TimeZone.Quote(), + Constant(Type), + RelationalExpressionQuotingUtilities.QuoteTypeMapping(TypeMapping)); + /// protected override void Print(ExpressionPrinter expressionPrinter) { diff --git a/src/EFCore.Relational/Query/SqlExpressions/CaseExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/CaseExpression.cs index a5acf8d930a..58e898aae9f 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/CaseExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/CaseExpression.cs @@ -16,6 +16,10 @@ public class CaseExpression : SqlExpression { private readonly List _whenClauses = []; + private static ConstructorInfo? _quotingConstructorWithOperand; + private static ConstructorInfo? _quotingConstructorWithoutOperand; + private static ConstructorInfo? _caseWhenClauseQuotingConstructor; + /// /// Creates a new instance of the class which represents a simple CASE expression. /// @@ -114,6 +118,32 @@ public virtual CaseExpression Update( : new CaseExpression(operand, whenClauses, elseResult)) : this; + /// + public override Expression Quote() + { + var whenClauses = NewArrayInit( + typeof(CaseWhenClause), + initializers: WhenClauses + .Select(c => New( + _caseWhenClauseQuotingConstructor ??= + typeof(CaseWhenClause).GetConstructor([typeof(SqlExpression), typeof(SqlExpression)])!, + c.Test.Quote(), + c.Result.Quote()))); + + return Operand is null + ? New( + _quotingConstructorWithoutOperand ??= + typeof(CaseExpression).GetConstructor([typeof(IReadOnlyList), typeof(SqlExpression)])!, + whenClauses, + RelationalExpressionQuotingUtilities.VisitOrNull(ElseResult)) + : New( + _quotingConstructorWithOperand ??= typeof(CaseExpression).GetConstructor( + [typeof(SqlExpression), typeof(IReadOnlyList), typeof(SqlExpression)])!, + Operand.Quote(), + whenClauses, + RelationalExpressionQuotingUtilities.VisitOrNull(ElseResult)); + } + /// protected override void Print(ExpressionPrinter expressionPrinter) { diff --git a/src/EFCore.Relational/Query/SqlExpressions/CollateExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/CollateExpression.cs index 375493d173a..578c4ab2bc7 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/CollateExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/CollateExpression.cs @@ -14,6 +14,8 @@ namespace Microsoft.EntityFrameworkCore.Query.SqlExpressions; /// public class CollateExpression : SqlExpression { + private static ConstructorInfo? _quotingConstructor; + /// /// Creates a new instance of the class. /// @@ -51,6 +53,13 @@ public virtual CollateExpression Update(SqlExpression operand) ? new CollateExpression(operand, Collation) : this; + /// + public override Expression Quote() + => New( + _quotingConstructor ??= typeof(CollateExpression).GetConstructor([typeof(SqlExpression), typeof(string)])!, + Operand.Quote(), + Constant(Collation)); + /// protected override void Print(ExpressionPrinter expressionPrinter) { diff --git a/src/EFCore.Relational/Query/SqlExpressions/ColumnExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/ColumnExpression.cs index 06a79f6697a..6e22fe372f6 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/ColumnExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/ColumnExpression.cs @@ -15,6 +15,8 @@ namespace Microsoft.EntityFrameworkCore.Query.SqlExpressions; [DebuggerDisplay("{TableAlias}.{Name}")] public class ColumnExpression : SqlExpression { + private static ConstructorInfo? _quotingConstructor; + /// /// Creates a new instance of the class. /// @@ -70,6 +72,17 @@ public virtual ColumnExpression MakeNullable() public virtual SqlExpression ApplyTypeMapping(RelationalTypeMapping? typeMapping) => new ColumnExpression(Name, TableAlias, Type, typeMapping, IsNullable); + /// + public override Expression Quote() + => New( + _quotingConstructor ??= typeof(ColumnExpression).GetConstructor( + [typeof(string), typeof(string), typeof(Type), typeof(RelationalTypeMapping), typeof(bool)])!, + Constant(Name), + Constant(TableAlias), + Constant(Type), + RelationalExpressionQuotingUtilities.QuoteTypeMapping(TypeMapping), + Constant(IsNullable)); + /// protected override void Print(ExpressionPrinter expressionPrinter) => expressionPrinter.Append(TableAlias).Append(".").Append(Name); diff --git a/src/EFCore.Relational/Query/SqlExpressions/CrossApplyExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/CrossApplyExpression.cs index 6b13a8fe4dc..0eed1b4b1ec 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/CrossApplyExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/CrossApplyExpression.cs @@ -14,6 +14,8 @@ namespace Microsoft.EntityFrameworkCore.Query.SqlExpressions; /// public class CrossApplyExpression : JoinExpressionBase { + private static ConstructorInfo? _quotingConstructor; + /// /// Creates a new instance of the class. /// @@ -47,6 +49,15 @@ public override CrossApplyExpression Update(TableExpressionBase table) protected override CrossApplyExpression WithAnnotations(IReadOnlyDictionary annotations) => new(Table, annotations); + /// + public override Expression Quote() + => New( + _quotingConstructor ??= + typeof(CrossApplyExpression).GetConstructor( + [typeof(TableExpressionBase), typeof(IReadOnlyDictionary)])!, + Table.Quote(), + RelationalExpressionQuotingUtilities.QuoteAnnotations(Annotations)); + /// protected override void Print(ExpressionPrinter expressionPrinter) { diff --git a/src/EFCore.Relational/Query/SqlExpressions/CrossJoinExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/CrossJoinExpression.cs index 9b5a58de124..f60bdaf4385 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/CrossJoinExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/CrossJoinExpression.cs @@ -14,6 +14,8 @@ namespace Microsoft.EntityFrameworkCore.Query.SqlExpressions; /// public class CrossJoinExpression : JoinExpressionBase { + private static ConstructorInfo? _quotingConstructor; + /// /// Creates a new instance of the class. /// @@ -47,6 +49,15 @@ public override CrossJoinExpression Update(TableExpressionBase table) protected override CrossJoinExpression WithAnnotations(IReadOnlyDictionary annotations) => new(Table, Annotations); + /// + public override Expression Quote() + => New( + _quotingConstructor ??= + typeof(CrossJoinExpression).GetConstructor( + [typeof(TableExpressionBase), typeof(IReadOnlyDictionary)])!, + Table.Quote(), + RelationalExpressionQuotingUtilities.QuoteAnnotations(Annotations)); + /// protected override void Print(ExpressionPrinter expressionPrinter) { diff --git a/src/EFCore.Relational/Query/SqlExpressions/DeleteExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/DeleteExpression.cs index 80bb41ebbc2..9a87151efe4 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/DeleteExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/DeleteExpression.cs @@ -11,8 +11,10 @@ namespace Microsoft.EntityFrameworkCore.Query.SqlExpressions; /// This type is typically used by database providers (and other extensions). It is generally not used in application code. /// /// -public sealed class DeleteExpression : Expression, IPrintableExpression +public sealed class DeleteExpression : Expression, IRelationalQuotableExpression, IPrintableExpression { + private static ConstructorInfo? _quotingConstructor; + /// /// Creates a new instance of the class. /// @@ -80,6 +82,19 @@ public DeleteExpression Update(TableExpression table, SelectExpression selectExp ? this : new DeleteExpression(table, selectExpression, Tags); + /// + public Expression Quote() + => New( + _quotingConstructor ??= typeof(DeleteExpression).GetConstructor( + [ + typeof(TableExpression), + typeof(SelectExpression), + typeof(ISet) + ])!, + Table.Quote(), + SelectExpression.Quote(), + RelationalExpressionQuotingUtilities.QuoteTags(Tags)); + /// public void Print(ExpressionPrinter expressionPrinter) { @@ -107,4 +122,5 @@ private bool Equals(DeleteExpression deleteExpression) /// public override int GetHashCode() => HashCode.Combine(Table, SelectExpression); + } diff --git a/src/EFCore.Relational/Query/SqlExpressions/DistinctExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/DistinctExpression.cs index e59e28c04c3..bdcca6199dc 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/DistinctExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/DistinctExpression.cs @@ -14,6 +14,8 @@ namespace Microsoft.EntityFrameworkCore.Query.SqlExpressions; /// public class DistinctExpression : SqlExpression { + private static ConstructorInfo? _quotingConstructor; + /// /// Creates a new instance of the class. /// @@ -54,6 +56,12 @@ public virtual DistinctExpression Update(SqlExpression operand) : this; } + /// + public override Expression Quote() + => New( + _quotingConstructor ??= typeof(DistinctExpression).GetConstructor([typeof(SqlExpression)])!, + Operand.Quote()); + /// protected override void Print(ExpressionPrinter expressionPrinter) { diff --git a/src/EFCore.Relational/Query/SqlExpressions/ExceptExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/ExceptExpression.cs index cb752a97bfd..06388fcdcfa 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/ExceptExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/ExceptExpression.cs @@ -14,6 +14,8 @@ namespace Microsoft.EntityFrameworkCore.Query.SqlExpressions; /// public class ExceptExpression : SetOperationBase { + private static ConstructorInfo? _quotingConstructor; + /// /// Creates a new instance of the class. /// @@ -77,6 +79,23 @@ public override TableExpressionBase Clone(string? alias, ExpressionVisitor cloni public override ExceptExpression WithAlias(string newAlias) => new(newAlias, Source1, Source2, IsDistinct); + /// + public override Expression Quote() + => New( + _quotingConstructor ??= typeof(ExceptExpression).GetConstructor( + [ + typeof(string), + typeof(SelectExpression), + typeof(SelectExpression), + typeof(bool), + typeof(IReadOnlyDictionary) + ])!, + Constant(Alias, typeof(string)), + Source1.Quote(), + Source2.Quote(), + Constant(IsDistinct), + RelationalExpressionQuotingUtilities.QuoteAnnotations(Annotations)); + /// protected override void Print(ExpressionPrinter expressionPrinter) { diff --git a/src/EFCore.Relational/Query/SqlExpressions/ExistsExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/ExistsExpression.cs index c3108c0aa8c..a94b187b8ed 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/ExistsExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/ExistsExpression.cs @@ -14,6 +14,8 @@ namespace Microsoft.EntityFrameworkCore.Query.SqlExpressions; /// public class ExistsExpression : SqlExpression { + private static ConstructorInfo? _quotingConstructor; + /// /// Creates a new instance of the class. /// @@ -49,6 +51,14 @@ public virtual ExistsExpression Update(SelectExpression subquery) ? new ExistsExpression(subquery, TypeMapping) : this; + /// + public override Expression Quote() + => New( + _quotingConstructor ??= + typeof(ExistsExpression).GetConstructor([typeof(SelectExpression), typeof(bool), typeof(RelationalTypeMapping)])!, + Subquery.Quote(), + RelationalExpressionQuotingUtilities.QuoteTypeMapping(TypeMapping)); + /// protected override void Print(ExpressionPrinter expressionPrinter) { diff --git a/src/EFCore.Relational/Query/SqlExpressions/FromSqlExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/FromSqlExpression.cs index 97ac89511dd..c71e7b97736 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/FromSqlExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/FromSqlExpression.cs @@ -16,6 +16,9 @@ namespace Microsoft.EntityFrameworkCore.Query.SqlExpressions; /// public class FromSqlExpression : TableExpressionBase, ITableBasedExpression { + private static ConstructorInfo? _quotingConstructor; + private static MethodInfo? _constantExpressionFactoryMethod, _parameterExpressionFactoryMethod; + /// /// Creates a new instance of the class. /// @@ -49,7 +52,14 @@ public FromSqlExpression(string alias, string sql, Expression arguments) { } - private FromSqlExpression( + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + public FromSqlExpression( string alias, ITableBase? tableBase, string sql, @@ -102,6 +112,38 @@ protected override FromSqlExpression WithAnnotations(IReadOnlyDictionary new(newAlias, Table, Sql, Arguments, Annotations); + /// + public override Expression Quote() + { + _constantExpressionFactoryMethod ??= typeof(Expression).GetMethod(nameof(Constant), [typeof(object)])!; + + return New( + _quotingConstructor ??= typeof(FromSqlExpression).GetConstructor( + [ + typeof(string), typeof(ITableBase), typeof(string), typeof(Expression), typeof(IReadOnlyDictionary) + ])!, + Constant(Alias, typeof(string)), + Table is null ? Constant(null, typeof(ITableBase)) : RelationalExpressionQuotingUtilities.QuoteTableBase(Table), + Constant(Sql), + Arguments switch + { + ConstantExpression { Value: object[] arguments } + => NewArrayInit( + typeof(object), + arguments.Select(a => (Expression)Call(_constantExpressionFactoryMethod, Constant(a))).ToArray()), + + ParameterExpression parameter + when parameter.Type == typeof(object[]) + => Call( + _parameterExpressionFactoryMethod ??= typeof(Expression).GetMethod(nameof(Parameter), [typeof(Type), typeof(string)])!, + Constant(typeof(object[])), + Constant(parameter.Name, typeof(string))), + + _ => throw new UnreachableException() // TODO: Confirm + }, + RelationalExpressionQuotingUtilities.QuoteAnnotations(Annotations)); + } + /// protected override Expression VisitChildren(ExpressionVisitor visitor) => this; diff --git a/src/EFCore.Relational/Query/SqlExpressions/InExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/InExpression.cs index d8b4198bbc0..a07f3bc7c76 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/InExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/InExpression.cs @@ -14,6 +14,10 @@ namespace Microsoft.EntityFrameworkCore.Query.SqlExpressions; /// public class InExpression : SqlExpression { + private static ConstructorInfo? _quotingConstructorWithSubquery; + private static ConstructorInfo? _quotingConstructorWithValues; + private static ConstructorInfo? _quotingConstructorWithValuesParameter; + /// /// Creates a new instance of the class, representing a SQL IN expression with a subquery. /// @@ -194,6 +198,34 @@ public virtual InExpression Update( : new InExpression(item, subquery, values, valuesParameter, TypeMapping); } + /// + public override Expression Quote() + => this switch + { + { Subquery: not null } => New( + _quotingConstructorWithSubquery ??= typeof(InExpression).GetConstructor( + [typeof(SqlExpression), typeof(SelectExpression), typeof(RelationalTypeMapping)])!, + Item.Quote(), + Subquery.Quote(), + RelationalExpressionQuotingUtilities.QuoteTypeMapping(TypeMapping)), + + { Values: not null } => New( + _quotingConstructorWithValues ??= typeof(InExpression).GetConstructor( + [typeof(SqlExpression), typeof(IReadOnlyList), typeof(RelationalTypeMapping)])!, + Item.Quote(), + NewArrayInit(typeof(SqlExpression), initializers: Values.Select(v => v.Quote())), + RelationalExpressionQuotingUtilities.QuoteTypeMapping(TypeMapping)), + + { ValuesParameter: not null } => New( + _quotingConstructorWithValuesParameter ??= typeof(InExpression).GetConstructor( + [typeof(SqlExpression), typeof(SqlParameterExpression), typeof(RelationalTypeMapping)])!, + Item.Quote(), + ValuesParameter.Quote(), + RelationalExpressionQuotingUtilities.QuoteTypeMapping(TypeMapping)), + + _ => throw new UnreachableException() + }; + /// protected override void Print(ExpressionPrinter expressionPrinter) { diff --git a/src/EFCore.Relational/Query/SqlExpressions/InnerJoinExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/InnerJoinExpression.cs index 5373bee4a00..d21c4a1f834 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/InnerJoinExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/InnerJoinExpression.cs @@ -14,6 +14,8 @@ namespace Microsoft.EntityFrameworkCore.Query.SqlExpressions; /// public class InnerJoinExpression : PredicateJoinExpressionBase { + private static ConstructorInfo? _quotingConstructor; + /// /// Creates a new instance of the class. /// @@ -61,6 +63,16 @@ public override InnerJoinExpression Update(TableExpressionBase table) protected override InnerJoinExpression WithAnnotations(IReadOnlyDictionary annotations) => new(Table, JoinPredicate, IsPrunable, annotations); + /// + public override Expression Quote() + => New( + _quotingConstructor ??= typeof(InnerJoinExpression).GetConstructor( + [typeof(TableExpressionBase), typeof(SqlExpression), typeof(bool), typeof(IReadOnlyDictionary)])!, + Table.Quote(), + JoinPredicate.Quote(), + Constant(IsPrunable), + RelationalExpressionQuotingUtilities.QuoteAnnotations(Annotations)); + /// protected override void Print(ExpressionPrinter expressionPrinter) { diff --git a/src/EFCore.Relational/Query/SqlExpressions/IntersectExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/IntersectExpression.cs index 1ce1c89b91f..2a3327e66a2 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/IntersectExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/IntersectExpression.cs @@ -14,6 +14,8 @@ namespace Microsoft.EntityFrameworkCore.Query.SqlExpressions; /// public class IntersectExpression : SetOperationBase { + private static ConstructorInfo? _quotingConstructor; + /// /// Creates a new instance of the class. /// @@ -77,6 +79,17 @@ public override TableExpressionBase Clone(string? alias, ExpressionVisitor cloni public override IntersectExpression WithAlias(string newAlias) => new(newAlias, Source1, Source2, IsDistinct); + /// + public override Expression Quote() + => New( + _quotingConstructor ??= typeof(IntersectExpression).GetConstructor( + [typeof(string), typeof(SelectExpression), typeof(SelectExpression), typeof(bool), typeof(IReadOnlyDictionary)])!, + Constant(Alias, typeof(string)), + Source1.Quote(), + Source2.Quote(), + Constant(IsDistinct), + RelationalExpressionQuotingUtilities.QuoteAnnotations(Annotations)); + /// protected override void Print(ExpressionPrinter expressionPrinter) { diff --git a/src/EFCore.Relational/Query/SqlExpressions/JsonScalarExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/JsonScalarExpression.cs index d44cc71b35d..f3a7642fe8e 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/JsonScalarExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/JsonScalarExpression.cs @@ -14,6 +14,8 @@ namespace Microsoft.EntityFrameworkCore.Query.SqlExpressions; /// public class JsonScalarExpression : SqlExpression { + private static ConstructorInfo? _quotingConstructor; + /// /// Creates a new instance of the class. /// @@ -123,6 +125,17 @@ public virtual JsonScalarExpression Update(SqlExpression json) ? new JsonScalarExpression(json, Path, Type, TypeMapping!, IsNullable) : this; + /// + public override Expression Quote() + => New( + _quotingConstructor ??= typeof(JsonScalarExpression).GetConstructor( + [typeof(SqlExpression), typeof(IReadOnlyList), typeof(Type), typeof(RelationalTypeMapping), typeof(bool)])!, + Json.Quote(), + NewArrayInit(typeof(PathSegment), initializers: Path.Select(s => s.Quote())), + Constant(Type), + RelationalExpressionQuotingUtilities.QuoteTypeMapping(TypeMapping), + Constant(IsNullable)); + /// protected override void Print(ExpressionPrinter expressionPrinter) { diff --git a/src/EFCore.Relational/Query/SqlExpressions/LeftJoinExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/LeftJoinExpression.cs index 1fc5dbef91a..e359be4355a 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/LeftJoinExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/LeftJoinExpression.cs @@ -14,6 +14,8 @@ namespace Microsoft.EntityFrameworkCore.Query.SqlExpressions; /// public class LeftJoinExpression : PredicateJoinExpressionBase { + private static ConstructorInfo? _quotingConstructor; + /// /// Creates a new instance of the class. /// @@ -61,6 +63,15 @@ public override LeftJoinExpression Update(TableExpressionBase table) protected override LeftJoinExpression WithAnnotations(IReadOnlyDictionary annotations) => new(Table, JoinPredicate, IsPrunable, annotations); + /// + public override Expression Quote() + => New( + _quotingConstructor ??= typeof(LeftJoinExpression).GetConstructor([typeof(TableExpressionBase), typeof(SqlExpression), typeof(bool), typeof(IReadOnlyDictionary)])!, + Table.Quote(), + JoinPredicate.Quote(), + Constant(IsPrunable), + RelationalExpressionQuotingUtilities.QuoteAnnotations(Annotations)); + /// protected override void Print(ExpressionPrinter expressionPrinter) { diff --git a/src/EFCore.Relational/Query/SqlExpressions/LikeExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/LikeExpression.cs index ba07749ac40..977eda682f0 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/LikeExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/LikeExpression.cs @@ -14,6 +14,8 @@ namespace Microsoft.EntityFrameworkCore.Query.SqlExpressions; /// public class LikeExpression : SqlExpression { + private static ConstructorInfo? _quotingConstructor; + /// /// Creates a new instance of the class. /// @@ -74,6 +76,16 @@ public virtual LikeExpression Update( ? new LikeExpression(match, pattern, escapeChar, TypeMapping) : this; + /// + public override Expression Quote() + => New( + _quotingConstructor ??= typeof(LikeExpression).GetConstructor( + [typeof(SqlExpression), typeof(SqlExpression), typeof(SqlExpression), typeof(RelationalTypeMapping)])!, + Match.Quote(), + Pattern.Quote(), + RelationalExpressionQuotingUtilities.VisitOrNull(EscapeChar), + RelationalExpressionQuotingUtilities.QuoteTypeMapping(TypeMapping)); + /// protected override void Print(ExpressionPrinter expressionPrinter) { diff --git a/src/EFCore.Relational/Query/SqlExpressions/OrderingExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/OrderingExpression.cs index d0aaffb1893..ad59d1151e1 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/OrderingExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/OrderingExpression.cs @@ -13,8 +13,10 @@ namespace Microsoft.EntityFrameworkCore.Query.SqlExpressions; /// /// [DebuggerDisplay("{Microsoft.EntityFrameworkCore.Query.ExpressionPrinter.Print(this), nq}")] -public class OrderingExpression : Expression, IPrintableExpression +public class OrderingExpression : Expression, IRelationalQuotableExpression, IPrintableExpression { + private static ConstructorInfo? _quotingConstructor; + /// /// Creates a new instance of the class. /// @@ -59,6 +61,14 @@ public virtual OrderingExpression Update(SqlExpression expression) ? new OrderingExpression(expression, IsAscending) : this; + + /// + public Expression Quote() + => New( + _quotingConstructor ??= typeof(OrderingExpression).GetConstructor([typeof(SqlExpression), typeof(bool)])!, + Expression.Quote(), + Constant(IsAscending)); + /// void IPrintableExpression.Print(ExpressionPrinter expressionPrinter) { diff --git a/src/EFCore.Relational/Query/SqlExpressions/OuterApplyExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/OuterApplyExpression.cs index 6a64ce346d8..91e319011f9 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/OuterApplyExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/OuterApplyExpression.cs @@ -14,6 +14,8 @@ namespace Microsoft.EntityFrameworkCore.Query.SqlExpressions; /// public class OuterApplyExpression : JoinExpressionBase { + private static ConstructorInfo? _quotingConstructor; + /// /// Creates a new instance of the class. /// @@ -47,6 +49,13 @@ public override OuterApplyExpression Update(TableExpressionBase table) protected override OuterApplyExpression WithAnnotations(IReadOnlyDictionary annotations) => new(Table, annotations); + /// + public override Expression Quote() + => New( + _quotingConstructor ??= typeof(OuterApplyExpression).GetConstructor([typeof(TableExpressionBase), typeof(IReadOnlyDictionary)])!, + Table.Quote(), + RelationalExpressionQuotingUtilities.QuoteAnnotations(Annotations)); + /// protected override void Print(ExpressionPrinter expressionPrinter) { diff --git a/src/EFCore.Relational/Query/SqlExpressions/ProjectionExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/ProjectionExpression.cs index 12af630fd38..28c1c6bc33e 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/ProjectionExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/ProjectionExpression.cs @@ -13,9 +13,18 @@ namespace Microsoft.EntityFrameworkCore.Query.SqlExpressions; /// github.com/dotnet/efcore. /// [DebuggerDisplay("{Microsoft.EntityFrameworkCore.Query.ExpressionPrinter.Print(this), nq}")] -public sealed class ProjectionExpression : Expression, IPrintableExpression +public sealed class ProjectionExpression : Expression, IRelationalQuotableExpression, IPrintableExpression { - internal ProjectionExpression(SqlExpression expression, string alias) + private static ConstructorInfo? _quotingConstructor; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + public ProjectionExpression(SqlExpression expression, string alias) { Expression = expression; Alias = alias; @@ -54,6 +63,13 @@ public ProjectionExpression Update(SqlExpression expression) ? new ProjectionExpression(expression, Alias) : this; + /// + public Expression Quote() + => New( + _quotingConstructor ??= typeof(ProjectionExpression).GetConstructor([typeof(SqlExpression), typeof(string)])!, + Expression.Quote(), + Constant(Alias)); + /// void IPrintableExpression.Print(ExpressionPrinter expressionPrinter) { diff --git a/src/EFCore.Relational/Query/SqlExpressions/RowNumberExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/RowNumberExpression.cs index 771382a5b92..228a0cde79e 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/RowNumberExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/RowNumberExpression.cs @@ -14,6 +14,8 @@ namespace Microsoft.EntityFrameworkCore.Query.SqlExpressions; /// public class RowNumberExpression : SqlExpression { + private static ConstructorInfo? _quotingConstructor; + /// /// Creates a new instance of the class. /// @@ -81,6 +83,14 @@ public virtual RowNumberExpression Update( ? this : new RowNumberExpression(partitions, orderings, TypeMapping); + /// + public override Expression Quote() + => New( + _quotingConstructor ??= typeof(RowNumberExpression).GetConstructor( + [typeof(IReadOnlyList), typeof(IReadOnlyList), typeof(RelationalTypeMapping)])!, + NewArrayInit(typeof(SqlExpression), initializers: Orderings.Select(o => o.Quote())), + RelationalExpressionQuotingUtilities.QuoteTypeMapping(TypeMapping)); + /// protected override void Print(ExpressionPrinter expressionPrinter) { diff --git a/src/EFCore.Relational/Query/SqlExpressions/RowValueExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/RowValueExpression.cs index 3ae8b29dc15..9291d3e4c9e 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/RowValueExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/RowValueExpression.cs @@ -14,6 +14,8 @@ namespace Microsoft.EntityFrameworkCore.Query.SqlExpressions; /// public class RowValueExpression : SqlExpression { + private static ConstructorInfo? _quotingConstructor; + /// /// The values of this row. /// @@ -47,6 +49,12 @@ public virtual RowValueExpression Update(IReadOnlyList values) ? this : new RowValueExpression(values); + /// + public override Expression Quote() + => New( + _quotingConstructor ??= typeof(RowValueExpression).GetConstructor([typeof(IReadOnlyList)])!, + NewArrayInit(typeof(SqlExpression), Values.Select(v => v.Quote()))); + /// protected override void Print(ExpressionPrinter expressionPrinter) { diff --git a/src/EFCore.Relational/Query/SqlExpressions/ScalarSubqueryExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/ScalarSubqueryExpression.cs index 334d8a682e8..cf8cb38d6d4 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/ScalarSubqueryExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/ScalarSubqueryExpression.cs @@ -14,6 +14,8 @@ namespace Microsoft.EntityFrameworkCore.Query.SqlExpressions; /// public class ScalarSubqueryExpression : SqlExpression { + private static ConstructorInfo? _quotingConstructor; + /// /// Creates a new instance of the class. /// @@ -70,6 +72,12 @@ public virtual ScalarSubqueryExpression Update(SelectExpression subquery) ? new ScalarSubqueryExpression(subquery) : this; + /// + public override Expression Quote() + => New( + _quotingConstructor ??= typeof(ScalarSubqueryExpression).GetConstructor([typeof(SelectExpression)])!, + Subquery.Quote()); + /// protected override void Print(ExpressionPrinter expressionPrinter) { diff --git a/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs index 7d13924e0e5..c5655df1846 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs @@ -50,7 +50,16 @@ public sealed partial class SelectExpression : TableExpressionBase // Pushdown should null it out as if GroupBy was present was pushed down. private List<(ColumnExpression Column, ValueComparer Comparer)>? _preGroupByIdentifier; - private SelectExpression( + private static ConstructorInfo? _quotingConstructor; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + public SelectExpression( string? alias, List tables, List groupBy, @@ -677,7 +686,7 @@ static void UpdateLimit(SelectExpression selectExpression) { if (selectExpression.Limit is SqlConstantExpression { Value: 2 } limitConstantExpression) { - selectExpression.Limit = new SqlConstantExpression(Constant(1), limitConstantExpression.TypeMapping); + selectExpression.Limit = new SqlConstantExpression(1, limitConstantExpression.TypeMapping); } } } @@ -2228,7 +2237,7 @@ static bool IsNullableProjection(ProjectionExpression projectionExpression) public void ApplyDefaultIfEmpty(ISqlExpressionFactory sqlExpressionFactory) { var nullSqlExpression = sqlExpressionFactory.ApplyDefaultTypeMapping( - new SqlConstantExpression(Constant(null, typeof(string)), null)); + new SqlConstantExpression(null, typeof(string), null)); var dummySelectExpression = CreateImmutable( _sqlAliasManager.GenerateTableAlias("empty"), @@ -2822,7 +2831,7 @@ private void AddJoin( limit = offset is SqlConstantExpression offsetConstant && limit is SqlConstantExpression limitConstant ? new SqlConstantExpression( - Constant((int)offsetConstant.Value! + (int)limitConstant.Value!), + (int)offsetConstant.Value! + (int)limitConstant.Value!, limit.TypeMapping) : new SqlBinaryExpression(ExpressionType.Add, offset, limit, limit.Type, limit.TypeMapping); } @@ -4200,6 +4209,39 @@ public override SelectExpression WithAlias(string newAlias) }; } + /// + public override Expression Quote() + => New( + _quotingConstructor ??= typeof(SelectExpression).GetConstructor( + [ + typeof(string), // alias + typeof(IReadOnlyList), // tables + typeof(SqlExpression), // predicate + typeof(IReadOnlyList), // groupby + typeof(SqlExpression), // having + typeof(IReadOnlyList), // projections + typeof(bool), // distinct + typeof(IReadOnlyList), // orderings + typeof(SqlExpression), // limit + typeof(SqlExpression), // offset + typeof(IReadOnlySet), // tags + typeof(IReadOnlyDictionary) // annotations + ])!, + Constant(Alias, typeof(string)), + NewArrayInit( + typeof(TableExpressionBase), + initializers: Tables.Select(t => t.Quote())), + RelationalExpressionQuotingUtilities.VisitOrNull(Predicate), + NewArrayInit(typeof(SqlExpression), initializers: GroupBy.Select(g => g.Quote())), + RelationalExpressionQuotingUtilities.VisitOrNull(Having), + NewArrayInit(typeof(ProjectionExpression), initializers: Projection.Select(p => p.Quote())), + Constant(IsDistinct), + NewArrayInit(typeof(OrderingExpression), initializers: Orderings.Select(o => o.Quote())), + RelationalExpressionQuotingUtilities.VisitOrNull(Limit), + RelationalExpressionQuotingUtilities.VisitOrNull(Offset), + RelationalExpressionQuotingUtilities.QuoteTags(Tags), + RelationalExpressionQuotingUtilities.QuoteAnnotations(Annotations)); + /// protected override void Print(ExpressionPrinter expressionPrinter) { diff --git a/src/EFCore.Relational/Query/SqlExpressions/SqlBinaryExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/SqlBinaryExpression.cs index b61fded1edb..d9fc939ff46 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/SqlBinaryExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/SqlBinaryExpression.cs @@ -38,6 +38,8 @@ public class SqlBinaryExpression : SqlExpression //ExpressionType.LeftShift, }; + private static ConstructorInfo? _quotingConstructor; + internal static bool IsValidOperator(ExpressionType operatorType) => AllowedOperators.Contains(operatorType); @@ -105,6 +107,17 @@ public virtual SqlBinaryExpression Update(SqlExpression left, SqlExpression righ ? new SqlBinaryExpression(OperatorType, left, right, Type, TypeMapping) : this; + /// + public override Expression Quote() + => New( + _quotingConstructor ??= typeof(SqlBinaryExpression).GetConstructor( + [typeof(ExpressionType), typeof(SqlExpression), typeof(SqlExpression), typeof(Type), typeof(RelationalTypeMapping)])!, + Constant(OperatorType), + Left.Quote(), + Right.Quote(), + Constant(Type), + RelationalExpressionQuotingUtilities.QuoteTypeMapping(TypeMapping)); + /// protected override void Print(ExpressionPrinter expressionPrinter) { diff --git a/src/EFCore.Relational/Query/SqlExpressions/SqlConstantExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/SqlConstantExpression.cs index bc7d4db06cd..c386b7c98e2 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/SqlConstantExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/SqlConstantExpression.cs @@ -16,24 +16,43 @@ namespace Microsoft.EntityFrameworkCore.Query.SqlExpressions; /// public class SqlConstantExpression : SqlExpression { - private readonly ConstantExpression _constantExpression; + private static ConstructorInfo? _quotingConstructor; + + /// + /// Creates a new instance of the class. + /// + /// An to set the property equal to. + /// The of the expression. + /// The associated with the expression. + public SqlConstantExpression(object? value, Type type, RelationalTypeMapping? typeMapping) + : base(type.UnwrapNullableType(), typeMapping) + => Value = value; + + /// + /// Creates a new instance of the class. + /// + /// An to set the property equal to. + /// The associated with the expression. + public SqlConstantExpression(object value, RelationalTypeMapping? typeMapping) + : this(value, value.GetType(), typeMapping) + { + } /// /// Creates a new instance of the class. /// /// A . /// The associated with the expression. + [Obsolete("Call the constructor accepting a value (and possibly a Type) instead")] public SqlConstantExpression(ConstantExpression constantExpression, RelationalTypeMapping? typeMapping) : base(constantExpression.Type.UnwrapNullableType(), typeMapping) { - _constantExpression = constantExpression; } /// /// The constant value. /// - public virtual object? Value - => _constantExpression.Value; + public virtual object? Value { get; } /// /// Applies supplied type mapping to this expression. @@ -41,12 +60,24 @@ public virtual object? Value /// A relational type mapping to apply. /// A new expression which has supplied type mapping. public virtual SqlExpression ApplyTypeMapping(RelationalTypeMapping? typeMapping) - => new SqlConstantExpression(_constantExpression, typeMapping); + => new SqlConstantExpression(Value, Type, typeMapping); /// protected override Expression VisitChildren(ExpressionVisitor visitor) => this; + /// + public override Expression Quote() + => New( + _quotingConstructor ??= typeof(SqlConstantExpression) + .GetConstructor([typeof(object), typeof(Type), typeof(RelationalTypeMapping)])!, + Type.IsValueType + ? Convert( + Constant(Value, Type), typeof(object)) + : Constant(Value, Type), + Constant(Type), + RelationalExpressionQuotingUtilities.QuoteTypeMapping(TypeMapping)); + /// protected override void Print(ExpressionPrinter expressionPrinter) => Print(Value, expressionPrinter); diff --git a/src/EFCore.Relational/Query/SqlExpressions/SqlExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/SqlExpression.cs index 670ec871fe2..8c373550b99 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/SqlExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/SqlExpression.cs @@ -13,7 +13,7 @@ namespace Microsoft.EntityFrameworkCore.Query.SqlExpressions; /// /// [DebuggerDisplay("{Microsoft.EntityFrameworkCore.Query.ExpressionPrinter.Print(this), nq}")] -public abstract class SqlExpression : Expression, IPrintableExpression +public abstract class SqlExpression : Expression, IRelationalQuotableExpression, IPrintableExpression { /// /// Creates a new instance of the class. @@ -44,6 +44,9 @@ protected override Expression VisitChildren(ExpressionVisitor visitor) public sealed override ExpressionType NodeType => ExpressionType.Extension; + /// + public abstract Expression Quote(); + /// /// Creates a printable string representation of the given expression using . /// diff --git a/src/EFCore.Relational/Query/SqlExpressions/SqlFragmentExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/SqlFragmentExpression.cs index bc867cc8b92..b939517ac79 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/SqlFragmentExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/SqlFragmentExpression.cs @@ -14,6 +14,8 @@ namespace Microsoft.EntityFrameworkCore.Query.SqlExpressions; /// public class SqlFragmentExpression : SqlExpression { + private static ConstructorInfo? _quotingConstructor; + /// /// Creates a new instance of the class. /// @@ -33,6 +35,12 @@ public SqlFragmentExpression(string sql) protected override Expression VisitChildren(ExpressionVisitor visitor) => this; + /// + public override Expression Quote() + => New( + _quotingConstructor ??= typeof(SqlFragmentExpression).GetConstructor([typeof(string)])!, + Constant(Sql)); // TODO: The new type mapping once that's merged + /// protected override void Print(ExpressionPrinter expressionPrinter) => expressionPrinter.Append(Sql); diff --git a/src/EFCore.Relational/Query/SqlExpressions/SqlFunctionExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/SqlFunctionExpression.cs index 0b4f646d447..29ef99a1016 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/SqlFunctionExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/SqlFunctionExpression.cs @@ -16,6 +16,8 @@ namespace Microsoft.EntityFrameworkCore.Query.SqlExpressions; /// public class SqlFunctionExpression : SqlExpression { + private static ConstructorInfo? _quotingConstructor; + /// /// Creates a new instance of the class which represents a built-in niladic function. /// @@ -328,6 +330,31 @@ public virtual SqlFunctionExpression Update(SqlExpression? instance, IReadOnlyLi TypeMapping) : this; + /// + public override Expression Quote() + => New( + _quotingConstructor ??= typeof(SqlFunctionExpression).GetConstructor( + [ + typeof(SqlExpression), typeof(string), typeof(string), typeof(bool), typeof(IEnumerable), + typeof(bool), typeof(bool), typeof(IEnumerable), typeof(bool), typeof(Type), typeof(RelationalTypeMapping) + ])!, + RelationalExpressionQuotingUtilities.VisitOrNull(Instance), + Constant(Schema, typeof(string)), + Constant(Name), + Constant(IsNiladic), + Arguments is null + ? Constant(null, typeof(IEnumerable)) + : NewArrayInit(typeof(SqlExpression), initializers: Arguments.Select(a => a.Quote())), + Constant(IsNullable), + Constant(InstancePropagatesNullability, typeof(bool?)), + ArgumentsPropagateNullability is null + ? Constant(null, typeof(IEnumerable)) + : NewArrayInit( + typeof(bool), initializers: ArgumentsPropagateNullability.Select(n => Constant(n))), + Constant(IsBuiltIn), + Constant(Type), + RelationalExpressionQuotingUtilities.QuoteTypeMapping(TypeMapping)); + /// protected override void Print(ExpressionPrinter expressionPrinter) { diff --git a/src/EFCore.Relational/Query/SqlExpressions/SqlParameterExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/SqlParameterExpression.cs index d53f50bbae6..40469671761 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/SqlParameterExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/SqlParameterExpression.cs @@ -8,6 +8,8 @@ namespace Microsoft.EntityFrameworkCore.Query.SqlExpressions; /// public sealed class SqlParameterExpression : SqlExpression { + private static ConstructorInfo? _quotingConstructor; + /// /// Creates a new instance of the class. /// @@ -48,6 +50,15 @@ public SqlExpression ApplyTypeMapping(RelationalTypeMapping? typeMapping) protected override Expression VisitChildren(ExpressionVisitor visitor) => this; + /// + public override Expression Quote() + => New( + _quotingConstructor ??= typeof(SqlParameterExpression).GetConstructor( + [typeof(string), typeof(Type), typeof(RelationalTypeMapping) ])!, // TODO: There's a dead IsNullable there... + Constant(Name, typeof(string)), + Constant(Type), + RelationalExpressionQuotingUtilities.QuoteTypeMapping(TypeMapping)); + /// protected override void Print(ExpressionPrinter expressionPrinter) => expressionPrinter.Append("@" + Name); diff --git a/src/EFCore.Relational/Query/SqlExpressions/SqlUnaryExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/SqlUnaryExpression.cs index 8b2b34f5d69..30950d8c916 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/SqlUnaryExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/SqlUnaryExpression.cs @@ -14,6 +14,8 @@ namespace Microsoft.EntityFrameworkCore.Query.SqlExpressions; /// public class SqlUnaryExpression : SqlExpression { + private static ConstructorInfo? _quotingConstructor; + private static readonly ISet AllowedOperators = new HashSet { ExpressionType.Equal, @@ -76,6 +78,16 @@ public virtual SqlUnaryExpression Update(SqlExpression operand) ? new SqlUnaryExpression(OperatorType, operand, Type, TypeMapping) : this; + /// + public override Expression Quote() + => New( + _quotingConstructor ??= typeof(SqlUnaryExpression).GetConstructor( + [typeof(ExpressionType), typeof(SqlExpression), typeof(Type), typeof(RelationalTypeMapping)])!, + Constant(OperatorType), + Operand.Quote(), + Constant(Type), + RelationalExpressionQuotingUtilities.QuoteTypeMapping(TypeMapping)); + /// protected override void Print(ExpressionPrinter expressionPrinter) { diff --git a/src/EFCore.Relational/Query/SqlExpressions/TableExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/TableExpression.cs index 7db678bfa20..828af3f201d 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/TableExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/TableExpression.cs @@ -15,12 +15,24 @@ namespace Microsoft.EntityFrameworkCore.Query.SqlExpressions; /// public sealed class TableExpression : TableExpressionBase, ITableBasedExpression { - internal TableExpression(string alias, ITableBase table) + private static ConstructorInfo? _quotingConstructor; + + /// + /// Creates a new instance of the class. + /// + public TableExpression(string alias, ITableBase table) : this(alias, table, annotations: null) { } - private TableExpression(string alias, ITableBase table, IReadOnlyDictionary? annotations) + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + public TableExpression(string alias, ITableBase table, IReadOnlyDictionary? annotations) : base(alias, annotations) { Name = table.Name; @@ -53,6 +65,15 @@ public override string Alias ITableBase ITableBasedExpression.Table => Table; + /// + public override Expression Quote() + => New( + _quotingConstructor ??= typeof(TableExpression) + .GetConstructor([typeof(string), typeof(ITableBase), typeof(IReadOnlyDictionary)])!, + Constant(Alias, typeof(string)), + RelationalExpressionQuotingUtilities.QuoteTableBase(Table), + RelationalExpressionQuotingUtilities.QuoteAnnotations(Annotations)); + /// protected override void Print(ExpressionPrinter expressionPrinter) { diff --git a/src/EFCore.Relational/Query/SqlExpressions/TableExpressionBase.cs b/src/EFCore.Relational/Query/SqlExpressions/TableExpressionBase.cs index fea3f911f98..f2ba6f94510 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/TableExpressionBase.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/TableExpressionBase.cs @@ -15,7 +15,7 @@ namespace Microsoft.EntityFrameworkCore.Query.SqlExpressions; /// /// [DebuggerDisplay("{Microsoft.EntityFrameworkCore.Query.ExpressionPrinter.Print(this), nq}")] -public abstract class TableExpressionBase : Expression, IPrintableExpression +public abstract class TableExpressionBase : Expression, IRelationalQuotableExpression, IPrintableExpression { /// /// An indexed collection of annotations associated with this table expression. @@ -85,6 +85,9 @@ public sealed override ExpressionType NodeType /// The alias to apply to the returned . public abstract TableExpressionBase WithAlias(string newAlias); + /// + public abstract Expression Quote(); + /// /// Creates a printable string representation of the given expression using . /// diff --git a/src/EFCore.Relational/Query/SqlExpressions/TableValuedFunctionExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/TableValuedFunctionExpression.cs index 996be3e476e..784e00ac2a4 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/TableValuedFunctionExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/TableValuedFunctionExpression.cs @@ -16,6 +16,8 @@ namespace Microsoft.EntityFrameworkCore.Query.SqlExpressions; /// public class TableValuedFunctionExpression : TableExpressionBase, ITableBasedExpression { + private static ConstructorInfo? _quotingConstructor1, _quotingConstructor2; + /// /// Creates a new instance of the class. /// @@ -153,6 +155,22 @@ protected override TableValuedFunctionExpression WithAnnotations(IReadOnlyDictio public override TableValuedFunctionExpression WithAlias(string newAlias) => new(newAlias, Name, Schema, IsBuiltIn, Arguments, Annotations); + /// + public override Expression Quote() + => StoreFunction is null + ? New( + _quotingConstructor1 ??= typeof(TableValuedFunctionExpression).GetConstructor( + [typeof(string), typeof(string), typeof(IReadOnlyList)])!, + Constant(Alias, typeof(string)), + Constant(Name, typeof(string)), + NewArrayInit(typeof(SqlExpression), Arguments.Select(v => v.Quote()))) + : New( + _quotingConstructor2 ??= typeof(TableValuedFunctionExpression).GetConstructor( + [typeof(string), typeof(IStoreFunction), typeof(IReadOnlyList)])!, + Constant(Alias, typeof(string)), + RelationalExpressionQuotingUtilities.QuoteTableBase(StoreFunction), + NewArrayInit(typeof(SqlExpression), Arguments.Select(v => v.Quote()))); + /// protected override void Print(ExpressionPrinter expressionPrinter) { diff --git a/src/EFCore.Relational/Query/SqlExpressions/UnionExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/UnionExpression.cs index 7e49663ca00..16db24710f0 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/UnionExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/UnionExpression.cs @@ -14,6 +14,8 @@ namespace Microsoft.EntityFrameworkCore.Query.SqlExpressions; /// public class UnionExpression : SetOperationBase { + private static ConstructorInfo? _quotingConstructor; + /// /// Creates a new instance of the class. /// @@ -77,6 +79,17 @@ public override TableExpressionBase Clone(string? alias, ExpressionVisitor cloni public override UnionExpression WithAlias(string newAlias) => new(newAlias, Source1, Source2, IsDistinct); + /// + public override Expression Quote() + => New( + _quotingConstructor ??= typeof(UnionExpression).GetConstructor( + [typeof(string), typeof(SelectExpression), typeof(SelectExpression), typeof(bool), typeof(IReadOnlyDictionary)])!, + Constant(Alias, typeof(string)), + Source1.Quote(), + Source2.Quote(), + Constant(IsDistinct), + RelationalExpressionQuotingUtilities.QuoteAnnotations(Annotations)); + /// protected override void Print(ExpressionPrinter expressionPrinter) { diff --git a/src/EFCore.Relational/Query/SqlExpressions/UpdateExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/UpdateExpression.cs index 3a59decd327..598f8be6df9 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/UpdateExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/UpdateExpression.cs @@ -12,8 +12,11 @@ namespace Microsoft.EntityFrameworkCore.Query.SqlExpressions; /// not used in application code. /// /// -public sealed class UpdateExpression : Expression, IPrintableExpression +public sealed class UpdateExpression : Expression, IRelationalQuotableExpression, IPrintableExpression { + private static ConstructorInfo? _quotingConstructor; + private static ConstructorInfo? _columnValueSetterQuotingConstructor; + /// /// Creates a new instance of the class. /// @@ -119,6 +122,26 @@ public UpdateExpression Update(SelectExpression selectExpression, IReadOnlyList< ? new UpdateExpression(Table, selectExpression, columnValueSetters, Tags) : this; + /// + public Expression Quote() + => New( + _quotingConstructor ??= typeof(UpdateExpression).GetConstructor( + [ + typeof(TableExpression), typeof(SelectExpression), typeof(IReadOnlyList), typeof(ISet) + ])!, + Table.Quote(), + SelectExpression.Quote(), + NewArrayInit( + typeof(ColumnValueSetter), + ColumnValueSetters + .Select( + s => New( + _columnValueSetterQuotingConstructor ??= + typeof(ColumnValueSetter).GetConstructor([typeof(ColumnExpression), typeof(SqlExpression)])!, + s.Column.Quote(), + s.Value.Quote()))), + RelationalExpressionQuotingUtilities.QuoteTags(Tags)); + /// public void Print(ExpressionPrinter expressionPrinter) { diff --git a/src/EFCore.Relational/Query/SqlExpressions/ValuesExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/ValuesExpression.cs index 86d44334360..f8a03726bff 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/ValuesExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/ValuesExpression.cs @@ -16,6 +16,8 @@ namespace Microsoft.EntityFrameworkCore.Query.SqlExpressions; /// public class ValuesExpression : TableExpressionBase { + private static ConstructorInfo? _quotingConstructor; + /// /// The row values for this table. /// @@ -32,13 +34,11 @@ public class ValuesExpression : TableExpressionBase /// A string alias for the table source. /// The row values for this table. /// The names of the columns contained in this table. - /// A collection of annotations associated with this expression. public ValuesExpression( string? alias, IReadOnlyList rowValues, - IReadOnlyList columnNames, - IEnumerable? annotations = null) - : base(alias, annotations) + IReadOnlyList columnNames) + : base(alias, annotations: (IReadOnlyDictionary?)null) { Check.DebugAssert( rowValues.All(rv => rv.Values.Count == columnNames.Count), @@ -48,7 +48,14 @@ public ValuesExpression( ColumnNames = columnNames; } - private ValuesExpression( + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + public ValuesExpression( string? alias, IReadOnlyList rowValues, IReadOnlyList columnNames, @@ -93,6 +100,21 @@ protected override ValuesExpression WithAnnotations(IReadOnlyDictionary new(newAlias, RowValues, ColumnNames, Annotations); + /// + public override Expression Quote() + => New( + _quotingConstructor ??= typeof(ValuesExpression).GetConstructor( + [ + typeof(string), + typeof(IReadOnlyList), + typeof(IReadOnlyList), + typeof(IReadOnlyDictionary) + ])!, + Constant(Alias, typeof(string)), + NewArrayInit(typeof(RowValueExpression), RowValues.Select(rv => rv.Quote())), + NewArrayInit(typeof(string), ColumnNames.Select(Constant)), + RelationalExpressionQuotingUtilities.QuoteAnnotations(Annotations)); + /// public override TableExpressionBase Clone(string? alias, ExpressionVisitor cloningExpressionVisitor) { diff --git a/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs b/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs index edf9de630db..2f8e60bb01f 100644 --- a/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs +++ b/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs @@ -557,7 +557,7 @@ protected virtual SqlExpression VisitCase(CaseExpression caseExpression, bool al // - if there is no Else block, return null if (whenClauses.Count == 0) { - return elseResult ?? _sqlExpressionFactory.Constant(null, caseExpression.TypeMapping); + return elseResult ?? _sqlExpressionFactory.Constant(null, caseExpression.Type, caseExpression.TypeMapping); } // if there is only one When clause and it's test evaluates to 'true' AND there is no else block, simply return the result @@ -925,7 +925,7 @@ InExpression ProcessInExpressionValues( continue; } - processedValues.Add(_sqlExpressionFactory.Constant(value, typeMapping)); + processedValues.Add(_sqlExpressionFactory.Constant(value, value?.GetType() ?? typeof(object), typeMapping)); } } else @@ -1415,7 +1415,7 @@ protected virtual SqlExpression VisitSqlParameter( nullable = ParameterValues[sqlParameterExpression.Name] == null; return nullable - ? _sqlExpressionFactory.Constant(null, sqlParameterExpression.TypeMapping) + ? _sqlExpressionFactory.Constant(null, sqlParameterExpression.Type, sqlParameterExpression.TypeMapping) : sqlParameterExpression; } diff --git a/src/EFCore.SqlServer/EFCore.SqlServer.csproj b/src/EFCore.SqlServer/EFCore.SqlServer.csproj index c18784020b6..bd0f62b62b0 100644 --- a/src/EFCore.SqlServer/EFCore.SqlServer.csproj +++ b/src/EFCore.SqlServer/EFCore.SqlServer.csproj @@ -9,6 +9,7 @@ true $(PackageTags);SQL Server true + EF1003 diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerAggregateFunctionExpression.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerAggregateFunctionExpression.cs index 3bd412e60e4..5522799e3c6 100644 --- a/src/EFCore.SqlServer/Query/Internal/SqlServerAggregateFunctionExpression.cs +++ b/src/EFCore.SqlServer/Query/Internal/SqlServerAggregateFunctionExpression.cs @@ -13,6 +13,8 @@ namespace Microsoft.EntityFrameworkCore.SqlServer.Query.Internal; /// public class SqlServerAggregateFunctionExpression : SqlExpression { + private static ConstructorInfo? _quotingConstructor; + /// /// 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 @@ -173,6 +175,35 @@ public virtual SqlServerAggregateFunctionExpression Update( Type, TypeMapping); + // string name, + // IReadOnlyList arguments, + // IReadOnlyList orderings, + // bool nullable, + // IEnumerable argumentsPropagateNullability, + // Type type, + // RelationalTypeMapping? typeMapping) + + /// + public override Expression Quote() + => New( + _quotingConstructor ??= typeof(SqlServerAggregateFunctionExpression).GetConstructor( + [ + typeof(string), typeof(IReadOnlyList), typeof(IReadOnlyList), typeof(bool), + typeof(IEnumerable), typeof(Type), typeof(RelationalTypeMapping) + ])!, + Constant(Name), + Arguments is null + ? Constant(null, typeof(IEnumerable)) + : NewArrayInit(typeof(SqlExpression), initializers: Arguments.Select(a => a.Quote())), + NewArrayInit(typeof(OrderingExpression), Orderings.Select(o => o.Quote())), + Constant(IsNullable), + ArgumentsPropagateNullability is null + ? Constant(null, typeof(IEnumerable)) + : NewArrayInit( + typeof(bool), initializers: ArgumentsPropagateNullability.Select(n => Constant(n))), + Constant(Type), + RelationalExpressionQuotingUtilities.QuoteTypeMapping(TypeMapping)); + /// protected override void Print(ExpressionPrinter expressionPrinter) { diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerObjectToStringTranslator.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerObjectToStringTranslator.cs index 10d9b5da5bd..6d345fd4cc3 100644 --- a/src/EFCore.SqlServer/Query/Internal/SqlServerObjectToStringTranslator.cs +++ b/src/EFCore.SqlServer/Query/Internal/SqlServerObjectToStringTranslator.cs @@ -88,7 +88,7 @@ public SqlServerObjectToStringTranslator(ISqlExpressionFactory sqlExpressionFact _sqlExpressionFactory.Equal(instance, _sqlExpressionFactory.Constant(true)), _sqlExpressionFactory.Constant(true.ToString())) }, - _sqlExpressionFactory.Constant(null)); + _sqlExpressionFactory.Constant(null, typeof(string))); } return _sqlExpressionFactory.Case( diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerOpenJsonExpression.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerOpenJsonExpression.cs index c1bb4bacf62..d38442ea81e 100644 --- a/src/EFCore.SqlServer/Query/Internal/SqlServerOpenJsonExpression.cs +++ b/src/EFCore.SqlServer/Query/Internal/SqlServerOpenJsonExpression.cs @@ -22,6 +22,8 @@ namespace Microsoft.EntityFrameworkCore.SqlServer.Query.Internal; /// public class SqlServerOpenJsonExpression : TableValuedFunctionExpression { + private static ConstructorInfo? _quotingConstructor, _columnInfoQuotingConstructor; + /// /// 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 @@ -172,6 +174,40 @@ public override TableExpressionBase Clone(string? alias, ExpressionVisitor cloni public override SqlServerOpenJsonExpression WithAlias(string newAlias) => new(newAlias, JsonExpression, Path, ColumnInfos); + /// + public override Expression Quote() + => New( + _quotingConstructor ??= typeof(SqlServerOpenJsonExpression).GetConstructor( + [ + typeof(string), + typeof(SqlExpression), + typeof(IReadOnlyList), + typeof(IReadOnlyList) + ])!, + Constant(Alias, typeof(string)), + JsonExpression.Quote(), + Path is null + ? Constant(null, typeof(IReadOnlyList)) + : NewArrayInit(typeof(PathSegment), Path.Select(s => s.Quote())), + ColumnInfos is null + ? Constant(null, typeof(IReadOnlyList)) + : NewArrayInit( + typeof(ColumnInfo), ColumnInfos.Select( + ci => New( + _columnInfoQuotingConstructor ??= typeof(ColumnInfo).GetConstructor( + [ + typeof(string), + typeof(RelationalTypeMapping), + typeof(IReadOnlyList), + typeof(bool) + ])!, + Constant(ci.Name), + RelationalExpressionQuotingUtilities.QuoteTypeMapping(ci.TypeMapping), + ci.Path is null + ? Constant(null, typeof(IReadOnlyList)) + : NewArrayInit(typeof(PathSegment), ci.Path.Select(s => s.Quote())), + Constant(ci.AsJson))))); + /// /// 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 diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerSqlTranslatingExpressionVisitor.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerSqlTranslatingExpressionVisitor.cs index 345a3910bf5..c3cb7f851b2 100644 --- a/src/EFCore.SqlServer/Query/Internal/SqlServerSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.SqlServer/Query/Internal/SqlServerSqlTranslatingExpressionVisitor.cs @@ -227,7 +227,9 @@ bool TryTranslateStartsEndsWithContains( // simple LIKE translation = patternConstant.Value switch { - null => _sqlExpressionFactory.Like(translatedInstance, _sqlExpressionFactory.Constant(null, stringTypeMapping)), + null => _sqlExpressionFactory.Like( + translatedInstance, + _sqlExpressionFactory.Constant(null, typeof(string), stringTypeMapping)), // In .NET, all strings start with/end with/contain the empty string, but SQL LIKE return false for empty patterns. // Return % which always matches instead. diff --git a/src/EFCore.Sqlite.Core/EFCore.Sqlite.Core.csproj b/src/EFCore.Sqlite.Core/EFCore.Sqlite.Core.csproj index 6597351343a..4b82a88a403 100644 --- a/src/EFCore.Sqlite.Core/EFCore.Sqlite.Core.csproj +++ b/src/EFCore.Sqlite.Core/EFCore.Sqlite.Core.csproj @@ -10,6 +10,7 @@ true $(PackageTags);SQLite true + EF1003 diff --git a/src/EFCore.Sqlite.Core/Query/Internal/SqliteObjectToStringTranslator.cs b/src/EFCore.Sqlite.Core/Query/Internal/SqliteObjectToStringTranslator.cs index cf2e933130d..2911c5b004f 100644 --- a/src/EFCore.Sqlite.Core/Query/Internal/SqliteObjectToStringTranslator.cs +++ b/src/EFCore.Sqlite.Core/Query/Internal/SqliteObjectToStringTranslator.cs @@ -85,7 +85,7 @@ public SqliteObjectToStringTranslator(ISqlExpressionFactory sqlExpressionFactory _sqlExpressionFactory.Equal(instance, _sqlExpressionFactory.Constant(true)), _sqlExpressionFactory.Constant(true.ToString())) }, - _sqlExpressionFactory.Constant(null)); + _sqlExpressionFactory.Constant(null, typeof(string))); } return _sqlExpressionFactory.Case( diff --git a/src/EFCore.Sqlite.Core/Query/Internal/SqliteQuerySqlGenerator.cs b/src/EFCore.Sqlite.Core/Query/Internal/SqliteQuerySqlGenerator.cs index 3868ed9ae49..f92325f70b2 100644 --- a/src/EFCore.Sqlite.Core/Query/Internal/SqliteQuerySqlGenerator.cs +++ b/src/EFCore.Sqlite.Core/Query/Internal/SqliteQuerySqlGenerator.cs @@ -80,7 +80,7 @@ protected override void GenerateLimitOffset(SelectExpression selectExpression) Visit( selectExpression.Limit - ?? new SqlConstantExpression(Expression.Constant(-1), selectExpression.Offset!.TypeMapping)); + ?? new SqlConstantExpression(-1, selectExpression.Offset!.TypeMapping)); if (selectExpression.Offset != null) { diff --git a/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs b/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs index 6c8b084f756..5698691db62 100644 --- a/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs @@ -292,7 +292,9 @@ bool TryTranslateStartsEndsWith( // simple LIKE translation = patternConstant.Value switch { - null => _sqlExpressionFactory.Like(translatedInstance, _sqlExpressionFactory.Constant(null, stringTypeMapping)), + null => _sqlExpressionFactory.Like( + translatedInstance, + _sqlExpressionFactory.Constant(null, typeof(string), stringTypeMapping)), // In .NET, all strings start with/end with/contain the empty string, but SQL LIKE return false for empty patterns. // Return % which always matches instead. diff --git a/src/EFCore.Sqlite.Core/Query/SqlExpressions/Internal/GlobExpression.cs b/src/EFCore.Sqlite.Core/Query/SqlExpressions/Internal/GlobExpression.cs index 1901cd67b4a..7f852959e9b 100644 --- a/src/EFCore.Sqlite.Core/Query/SqlExpressions/Internal/GlobExpression.cs +++ b/src/EFCore.Sqlite.Core/Query/SqlExpressions/Internal/GlobExpression.cs @@ -13,6 +13,8 @@ namespace Microsoft.EntityFrameworkCore.Sqlite.Query.SqlExpressions.Internal; /// public class GlobExpression : SqlExpression { + private static ConstructorInfo? _quotingConstructor; + /// /// 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 @@ -76,6 +78,15 @@ public virtual GlobExpression Update(SqlExpression match, SqlExpression pattern) ? new GlobExpression(match, pattern, TypeMapping) : this; + /// + public override Expression Quote() + => New( + _quotingConstructor ??= typeof(GlobExpression).GetConstructor( + [typeof(SqlExpression), typeof(SqlExpression), typeof(RelationalTypeMapping)])!, + Match.Quote(), + Pattern.Quote(), + RelationalExpressionQuotingUtilities.QuoteTypeMapping(TypeMapping)); + /// /// 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 diff --git a/src/EFCore.Sqlite.Core/Query/SqlExpressions/Internal/JsonEachExpression.cs b/src/EFCore.Sqlite.Core/Query/SqlExpressions/Internal/JsonEachExpression.cs index 741e85bdefc..d2cfa45526e 100644 --- a/src/EFCore.Sqlite.Core/Query/SqlExpressions/Internal/JsonEachExpression.cs +++ b/src/EFCore.Sqlite.Core/Query/SqlExpressions/Internal/JsonEachExpression.cs @@ -21,6 +21,8 @@ namespace Microsoft.EntityFrameworkCore.Sqlite.Query.SqlExpressions.Internal; /// public class JsonEachExpression : TableValuedFunctionExpression { + private static ConstructorInfo? _quotingConstructor; + /// /// 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 @@ -146,6 +148,21 @@ public override TableExpressionBase Clone(string? alias, ExpressionVisitor cloni public override JsonEachExpression WithAlias(string newAlias) => new(newAlias, JsonExpression, Path); + /// + public override Expression Quote() + => New( + _quotingConstructor ??= typeof(JsonEachExpression).GetConstructor( + [ + typeof(string), + typeof(SqlExpression), + typeof(IReadOnlyList) + ])!, + Constant(Alias, typeof(string)), + JsonExpression.Quote(), + Path is null + ? Constant(null, typeof(IReadOnlyList)) + : NewArrayInit(typeof(PathSegment), Path.Select(s => s.Quote()))); + /// /// 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 diff --git a/src/EFCore.Sqlite.Core/Query/SqlExpressions/Internal/RegexpExpression.cs b/src/EFCore.Sqlite.Core/Query/SqlExpressions/Internal/RegexpExpression.cs index 101cced8c7c..69aa51cb6cc 100644 --- a/src/EFCore.Sqlite.Core/Query/SqlExpressions/Internal/RegexpExpression.cs +++ b/src/EFCore.Sqlite.Core/Query/SqlExpressions/Internal/RegexpExpression.cs @@ -13,6 +13,8 @@ namespace Microsoft.EntityFrameworkCore.Sqlite.Query.SqlExpressions.Internal; /// public class RegexpExpression : SqlExpression { + private static ConstructorInfo? _quotingConstructor; + /// /// 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 @@ -76,6 +78,15 @@ public virtual RegexpExpression Update(SqlExpression match, SqlExpression patter ? new RegexpExpression(match, pattern, TypeMapping) : this; + /// + public override Expression Quote() + => New( + _quotingConstructor ??= typeof(RegexpExpression).GetConstructor( + [typeof(SqlExpression), typeof(SqlExpression), typeof(RelationalTypeMapping)])!, + Match.Quote(), + Pattern.Quote(), + RelationalExpressionQuotingUtilities.QuoteTypeMapping(TypeMapping)); + /// /// 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 diff --git a/test/EFCore.Relational.Specification.Tests/Query/UdfDbFunctionTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/UdfDbFunctionTestBase.cs index 9f5874e89cc..3c36b6b72af 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/UdfDbFunctionTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/UdfDbFunctionTestBase.cs @@ -292,9 +292,9 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) args.First(), new[] { - new SqlConstantExpression(Expression.Constant(abc[0]), typeMapping: null), - new SqlConstantExpression(Expression.Constant(abc[1]), typeMapping: null), - new SqlConstantExpression(Expression.Constant(abc[2]), typeMapping: null) + new SqlConstantExpression(abc[0], typeMapping: null), + new SqlConstantExpression(abc[1], typeMapping: null), + new SqlConstantExpression(abc[2], typeMapping: null) }, // args.First().TypeMapping) typeMapping: null)); @@ -306,15 +306,15 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) args.First(), new[] { - new SqlConstantExpression(Expression.Constant(abc[0]), args.First().TypeMapping), - new SqlConstantExpression(Expression.Constant(abc[1]), args.First().TypeMapping), - new SqlConstantExpression(Expression.Constant(abc[2]), args.First().TypeMapping) + new SqlConstantExpression(abc[0], args.First().TypeMapping), + new SqlConstantExpression(abc[1], args.First().TypeMapping), + new SqlConstantExpression(abc[2], args.First().TypeMapping) }, typeMapping: null), new[] { - new SqlConstantExpression(Expression.Constant(trueFalse[0]), typeMapping: null), - new SqlConstantExpression(Expression.Constant(trueFalse[1]), typeMapping: null) + new SqlConstantExpression(trueFalse[0], typeMapping: null), + new SqlConstantExpression(trueFalse[1], typeMapping: null) }, typeMapping: null)); diff --git a/test/EFCore.Relational.Tests/Infrastructure/RelationalEventIdTest.cs b/test/EFCore.Relational.Tests/Infrastructure/RelationalEventIdTest.cs index b86ccd379e0..2c5943573bb 100644 --- a/test/EFCore.Relational.Tests/Infrastructure/RelationalEventIdTest.cs +++ b/test/EFCore.Relational.Tests/Infrastructure/RelationalEventIdTest.cs @@ -126,6 +126,9 @@ public FakeSqlExpression() { } + public override Expression Quote() + => throw new NotSupportedException(); + protected override void Print(ExpressionPrinter expressionPrinter) => expressionPrinter.Append("FakeSqlExpression"); }