From b533695713dcf71a763718f879775784ee8a1a10 Mon Sep 17 00:00:00 2001 From: Christopher Jolly Date: Thu, 29 Sep 2022 22:37:05 +0800 Subject: [PATCH] SqlExpressionFactory can't be used inside SqlGenerator so create the expressions manually. Fixes queries with conversions --- .../Sql/Internal/JetQuerySqlGenerator.cs | 112 ++++++++++++------ 1 file changed, 73 insertions(+), 39 deletions(-) diff --git a/src/EFCore.Jet/Query/Sql/Internal/JetQuerySqlGenerator.cs b/src/EFCore.Jet/Query/Sql/Internal/JetQuerySqlGenerator.cs index 4f29e70..cddd0f5 100644 --- a/src/EFCore.Jet/Query/Sql/Internal/JetQuerySqlGenerator.cs +++ b/src/EFCore.Jet/Query/Sql/Internal/JetQuerySqlGenerator.cs @@ -2,17 +2,21 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using EntityFrameworkCore.Jet.Data; using System.Linq; using System.Linq.Expressions; using EntityFrameworkCore.Jet.Infrastructure.Internal; -using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Storage; using EntityFrameworkCore.Jet.Utilities; +using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Query; using Microsoft.EntityFrameworkCore.Query.SqlExpressions; +using ExpressionExtensions = Microsoft.EntityFrameworkCore.Internal.ExpressionExtensions; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Metadata.Internal; namespace EntityFrameworkCore.Jet.Query.Sql.Internal { @@ -24,36 +28,40 @@ namespace EntityFrameworkCore.Jet.Query.Sql.Internal { private static readonly Dictionary _convertMappings = new Dictionary { - {nameof(Boolean), "CBOOL"}, - {nameof(Byte), "CBYTE"}, - {nameof(SByte), "CINT"}, - {nameof(Int16), "CINT"}, - {nameof(Int32), "CLNG"}, - {nameof(Single), "CSNG"}, - {nameof(Double), "CDBL"}, - {nameof(Decimal), "CCUR"}, - {nameof(DateTime), "CDATE"}, + { nameof(Boolean), "CBOOL" }, + { nameof(Byte), "CBYTE" }, + { nameof(SByte), "CINT" }, + { nameof(Int16), "CINT" }, + { nameof(Int32), "CLNG" }, + { nameof(Single), "CSNG" }, + { nameof(Double), "CDBL" }, + { nameof(Decimal), "CCUR" }, + { nameof(DateTime), "CDATE" }, }; private readonly ITypeMappingSource _typeMappingSource; private readonly IJetOptions _options; + private readonly ISqlGenerationHelper _sqlGenerationHelper; + //private readonly JetSqlExpressionFactory _sqlExpressionFactory; private CoreTypeMapping _boolTypeMapping; - /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public JetQuerySqlGenerator( - [NotNull] QuerySqlGeneratorDependencies dependencies, - ITypeMappingSource typeMappingSource, + [JetBrains.Annotations.NotNull] QuerySqlGeneratorDependencies dependencies, + //ISqlExpressionFactory sqlExpressionFactory, + [JetBrains.Annotations.NotNull] ITypeMappingSource typeMappingSource, IJetOptions options) : base(dependencies) { + //_sqlExpressionFactory = (JetSqlExpressionFactory)sqlExpressionFactory; _typeMappingSource = typeMappingSource; _options = options; _sqlGenerationHelper = dependencies.SqlGenerationHelper; _boolTypeMapping = _typeMappingSource.FindMapping(typeof(bool)); + //_jetSqlExpressionFactory = jetSqlExpressionFactory; } protected override Expression VisitSelect(SelectExpression selectExpression) @@ -65,7 +73,7 @@ namespace EntityFrameworkCore.Jet.Query.Sql.Internal if (IsNonComposedSetOperation(selectExpression)) { // Naked set operation - GenerateSetOperation((SetOperationBase) selectExpression.Tables[0]); + GenerateSetOperation((SetOperationBase)selectExpression.Tables[0]); return selectExpression; } @@ -112,7 +120,8 @@ namespace EntityFrameworkCore.Jet.Query.Sql.Internal 0, selectExpression .Tables - .Count(t => !(t is CrossJoinExpression || t is CrossApplyExpression)) - maxTablesWithoutBrackets))); + .Count(t => !(t is CrossJoinExpression || t is CrossApplyExpression)) - + maxTablesWithoutBrackets))); for (var index = 0; index < selectExpression.Tables.Count; index++) { @@ -120,13 +129,14 @@ namespace EntityFrameworkCore.Jet.Query.Sql.Internal var isApplyExpression = tableExpression is CrossApplyExpression || tableExpression is OuterApplyExpression; - + var isCrossExpression = tableExpression is CrossJoinExpression || tableExpression is CrossApplyExpression; if (isApplyExpression) { - throw new InvalidOperationException("Jet does not support APPLY statements. Switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync() if needed."); + throw new InvalidOperationException( + "Jet does not support APPLY statements. Switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync() if needed."); } if (index > 0) @@ -202,7 +212,8 @@ namespace EntityFrameworkCore.Jet.Query.Sql.Internal && selectExpression.Projection.Count == setOperation.Source1.Projection.Count && selectExpression.Projection.Select( (pe, index) => pe.Expression is ColumnExpression column - && string.Equals(column.Table.Alias, setOperation.Alias, StringComparison.OrdinalIgnoreCase) + && string.Equals(column.Table.Alias, setOperation.Alias, + StringComparison.OrdinalIgnoreCase) && string.Equals( column.Name, setOperation.Source1.Projection[index] .Alias, StringComparison.OrdinalIgnoreCase)) @@ -236,14 +247,14 @@ namespace EntityFrameworkCore.Jet.Query.Sql.Internal { // Jet uses the value -1 as True, so ordering by a boolean expression will first list the True values // before the False values, which is the opposite of what .NET and other DBMS do, which are using 1 as True. - + if (orderingExpression.Expression.TypeMapping == _boolTypeMapping) { orderingExpression = new OrderingExpression( orderingExpression.Expression, !orderingExpression.IsAscending); } - + return base.VisitOrdering(orderingExpression); } @@ -252,19 +263,29 @@ namespace EntityFrameworkCore.Jet.Query.Sql.Internal { Check.NotNull(sqlBinaryExpression, nameof(sqlBinaryExpression)); - /*if (sqlBinaryExpression.OperatorType == ExpressionType.Coalesce) + if (sqlBinaryExpression.OperatorType == ExpressionType.Coalesce) { - Visit( - _sqlExpressionFactory.Case( - new[] - { - new CaseWhenClause( - _sqlExpressionFactory.IsNull(sqlBinaryExpression.Left), - sqlBinaryExpression.Right) - }, - sqlBinaryExpression.Left)); + //Visit( + /*_sqlExpressionFactory.Case( + new[] + { + new CaseWhenClause( + _sqlExpressionFactory.IsNull(sqlBinaryExpression.Left), + sqlBinaryExpression.Right) + }, + sqlBinaryExpression.Left)); + return sqlBinaryExpression;*/ + + SqlConstantExpression nullcons = new SqlConstantExpression(Expression.Constant(null), RelationalTypeMapping.NullMapping); + SqlUnaryExpression isnullexp = new SqlUnaryExpression(ExpressionType.Equal, sqlBinaryExpression.Left, typeof(bool), null); + List whenclause = new List + { + new CaseWhenClause(isnullexp, sqlBinaryExpression.Right) + }; + CaseExpression caseexp = new CaseExpression(whenclause, sqlBinaryExpression.Left); + Visit(caseexp); return sqlBinaryExpression; - }*/ + } return base.VisitSqlBinary(sqlBinaryExpression); } @@ -279,23 +300,37 @@ namespace EntityFrameworkCore.Jet.Query.Sql.Internal var typeMapping = convertExpression.TypeMapping; if (typeMapping == null) - throw new InvalidOperationException(RelationalStrings.UnsupportedType(convertExpression.Type.ShortDisplayName())); + throw new InvalidOperationException( + RelationalStrings.UnsupportedType(convertExpression.Type.ShortDisplayName())); // We are explicitly converting to the target type (convertExpression.Type) and not the CLR type of the // accociated type mapping. This allows for conversions on the database side (e.g. CDBL()) but handling // of the returned value using a different (unaligned) type mapping (e.g. date/time related ones). if (_convertMappings.TryGetValue(convertExpression.Type.Name, out var function)) { - /*Visit( + /* Visit( _sqlExpressionFactory.NullChecked( convertExpression.Operand, _sqlExpressionFactory.Function( function, - new[] {convertExpression.Operand}, + new[] { convertExpression.Operand }, false, - new[] {false}, + new[] { false }, typeMapping.ClrType))); */ + SqlExpression checksqlexp = convertExpression.Operand; + + SqlFunctionExpression notnullsqlexp = new SqlFunctionExpression(function, new SqlExpression[] { convertExpression.Operand }, + false, new[] { false }, typeMapping.ClrType,null); + + SqlConstantExpression nullcons = new SqlConstantExpression(Expression.Constant(null),RelationalTypeMapping.NullMapping); + SqlUnaryExpression isnullexp = new SqlUnaryExpression(ExpressionType.Equal, checksqlexp, typeof(bool), null); + List whenclause = new List + { + new CaseWhenClause(isnullexp, nullcons) + }; + CaseExpression caseexp = new CaseExpression(whenclause, notnullsqlexp); + Visit(caseexp); return convertExpression; } @@ -325,7 +360,7 @@ namespace EntityFrameworkCore.Jet.Query.Sql.Internal return likeExpression; } - protected override string GetOperator([NotNull] SqlBinaryExpression binaryExpression) + protected override string GetOperator([JetBrains.Annotations.NotNull] SqlBinaryExpression binaryExpression) => binaryExpression.OperatorType switch { ExpressionType.Add when binaryExpression.Type == typeof(string) => " & ", @@ -357,7 +392,8 @@ namespace EntityFrameworkCore.Jet.Query.Sql.Internal } else { - throw new InvalidOperationException("Jet does not support skipping rows. Switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync() if needed."); + throw new InvalidOperationException( + "Jet does not support skipping rows. Switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync() if needed."); } } @@ -441,7 +477,5 @@ namespace EntityFrameworkCore.Jet.Query.Sql.Internal protected override Expression VisitRowNumber(RowNumberExpression rowNumberExpression) => throw new InvalidOperationException(CoreStrings.TranslationFailed(rowNumberExpression)); - - } } \ No newline at end of file