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");
}