From dc559cf2d8b7bbe2f7b5e58411fda4aae5cb0e4b Mon Sep 17 00:00:00 2001 From: Rafael Almeida Date: Fri, 3 Jan 2020 15:15:51 -0300 Subject: [PATCH] Sqlite: Support translation of ToString * Resolve: #17223 --- .../SqliteMethodCallTranslatorProvider.cs | 3 +- .../SqliteObjectToStringTranslator.cs | 55 +++++++++++++++++++ .../NorthwindMiscellaneousQuerySqliteTest.cs | 41 +++++++++++++- 3 files changed, 96 insertions(+), 3 deletions(-) create mode 100644 src/EFCore.Sqlite.Core/Query/Internal/SqliteObjectToStringTranslator.cs diff --git a/src/EFCore.Sqlite.Core/Query/Internal/SqliteMethodCallTranslatorProvider.cs b/src/EFCore.Sqlite.Core/Query/Internal/SqliteMethodCallTranslatorProvider.cs index f3c8d14fd9d..2aa849f0e39 100644 --- a/src/EFCore.Sqlite.Core/Query/Internal/SqliteMethodCallTranslatorProvider.cs +++ b/src/EFCore.Sqlite.Core/Query/Internal/SqliteMethodCallTranslatorProvider.cs @@ -19,7 +19,8 @@ public SqliteMethodCallTranslatorProvider([NotNull] RelationalMethodCallTranslat new SqliteByteArrayMethodTranslator(sqlExpressionFactory), new SqliteDateTimeAddTranslator(sqlExpressionFactory), new SqliteMathTranslator(sqlExpressionFactory), - new SqliteStringMethodTranslator(sqlExpressionFactory) + new SqliteObjectToStringTranslator(sqlExpressionFactory), + new SqliteStringMethodTranslator(sqlExpressionFactory), }); } } diff --git a/src/EFCore.Sqlite.Core/Query/Internal/SqliteObjectToStringTranslator.cs b/src/EFCore.Sqlite.Core/Query/Internal/SqliteObjectToStringTranslator.cs new file mode 100644 index 00000000000..792d30c269b --- /dev/null +++ b/src/EFCore.Sqlite.Core/Query/Internal/SqliteObjectToStringTranslator.cs @@ -0,0 +1,55 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Reflection; +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Query; +using Microsoft.EntityFrameworkCore.Query.SqlExpressions; +using Microsoft.EntityFrameworkCore.Utilities; + +namespace Microsoft.EntityFrameworkCore.Sqlite.Query.Internal +{ + public class SqliteObjectToStringTranslator : IMethodCallTranslator + { + private static readonly HashSet _typeMapping = new HashSet + { + typeof(int), + typeof(long), + typeof(DateTime), + typeof(Guid), + typeof(byte), + typeof(byte[]), + typeof(double), + typeof(DateTimeOffset), + typeof(char), + typeof(short), + typeof(float), + typeof(decimal), + typeof(TimeSpan), + typeof(uint), + typeof(ushort), + typeof(ulong), + typeof(sbyte), + }; + + private readonly ISqlExpressionFactory _sqlExpressionFactory; + + public SqliteObjectToStringTranslator([NotNull] ISqlExpressionFactory sqlExpressionFactory) + => _sqlExpressionFactory = sqlExpressionFactory; + + public virtual SqlExpression Translate(SqlExpression instance, MethodInfo method, IReadOnlyList arguments) + { + Check.NotNull(method, nameof(method)); + Check.NotNull(arguments, nameof(arguments)); + + return method.Name == nameof(ToString) + && arguments.Count == 0 + && instance != null + && _typeMapping.Contains(instance.Type.UnwrapNullableType()) + ? _sqlExpressionFactory.Convert(instance, typeof(string)) + : null; + } + } +} diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindMiscellaneousQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindMiscellaneousQuerySqliteTest.cs index dd2fd6d83bf..80df794c021 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindMiscellaneousQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindMiscellaneousQuerySqliteTest.cs @@ -21,8 +21,15 @@ public NorthwindMiscellaneousQuerySqliteTest(NorthwindQuerySqliteFixture AssertTranslationFailed(() => base.Query_expression_with_to_string_and_contains(async)); + public override async Task Query_expression_with_to_string_and_contains(bool async) + { + await base.Query_expression_with_to_string_and_contains(async); + + AssertSql( + @"SELECT ""o"".""CustomerID"" +FROM ""Orders"" AS ""o"" +WHERE ""o"".""OrderDate"" IS NOT NULL AND (('10' = '') OR (instr(CAST(""o"".""EmployeeID"" AS TEXT), '10') > 0))"); + } public override async Task Take_Skip(bool async) { @@ -236,6 +243,36 @@ LIMIT @__p_0 ) AS ""t"""); } + public override async Task Select_expression_other_to_string(bool async) + { + await base.Select_expression_other_to_string(async); + + AssertSql( + @"SELECT CAST(""o"".""OrderDate"" AS TEXT) AS ""ShipName"" +FROM ""Orders"" AS ""o"" +WHERE ""o"".""OrderDate"" IS NOT NULL"); + } + + public override async Task Select_expression_long_to_string(bool async) + { + await base.Select_expression_long_to_string(async); + + AssertSql( + @"SELECT CAST(CAST(""o"".""OrderID"" AS INTEGER) AS TEXT) AS ""ShipName"" +FROM ""Orders"" AS ""o"" +WHERE ""o"".""OrderDate"" IS NOT NULL"); + } + + public override async Task Select_expression_int_to_string(bool async) + { + await base.Select_expression_int_to_string(async); + + AssertSql( + @"SELECT CAST(""o"".""OrderID"" AS TEXT) AS ""ShipName"" +FROM ""Orders"" AS ""o"" +WHERE ""o"".""OrderDate"" IS NOT NULL"); + } + public override Task Complex_nested_query_doesnt_try_binding_to_grandparent_when_parent_returns_complex_result(bool async) => null;