Skip to content

Commit e5425e5

Browse files
authored
Add EF.Functions.IsNumeric (#23232)
Co-authored-by: HuyLuong <[email protected]> Resolves #23114
1 parent 68d54dd commit e5425e5

File tree

4 files changed

+147
-0
lines changed

4 files changed

+147
-0
lines changed

src/EFCore.SqlServer/Extensions/SqlServerDbFunctionsExtensions.cs

+12
Original file line numberDiff line numberDiff line change
@@ -1075,5 +1075,17 @@ public static TimeSpan TimeFromParts(
10751075
[CanBeNull] this DbFunctions _,
10761076
Guid? arg)
10771077
=> throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(DataLength)));
1078+
1079+
/// <summary>
1080+
/// Validate if the given string is a valid numeric.
1081+
/// Corresponds to the SQL Server's ISNUMERIC(expression).
1082+
/// </summary>
1083+
/// <param name="_">The DbFunctions instance.</param>
1084+
/// <param name="expression">Expression to validate</param>
1085+
/// <returns>true for valid numeric and false otherwise.</returns>
1086+
public static bool IsNumeric(
1087+
[CanBeNull] this DbFunctions _,
1088+
[NotNull] string expression)
1089+
=> throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(IsNumeric)));
10781090
}
10791091
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Reflection;
7+
using JetBrains.Annotations;
8+
using Microsoft.EntityFrameworkCore.Diagnostics;
9+
using Microsoft.EntityFrameworkCore.Query;
10+
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
11+
using Microsoft.EntityFrameworkCore.Utilities;
12+
13+
#nullable enable
14+
15+
namespace Microsoft.EntityFrameworkCore.SqlServer.Query.Internal
16+
{
17+
/// <summary>
18+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
19+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
20+
/// any release. You should only use it directly in your code with extreme caution and knowing that
21+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
22+
/// </summary>
23+
public class SqlServerIsNumericFunctionTranslator : IMethodCallTranslator
24+
{
25+
private readonly ISqlExpressionFactory _sqlExpressionFactory;
26+
27+
private static readonly MethodInfo _methodInfo = typeof(SqlServerDbFunctionsExtensions)
28+
.GetRequiredRuntimeMethod(nameof(SqlServerDbFunctionsExtensions.IsNumeric), new[] { typeof(DbFunctions), typeof(string) });
29+
30+
/// <summary>
31+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
32+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
33+
/// any release. You should only use it directly in your code with extreme caution and knowing that
34+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
35+
/// </summary>
36+
public SqlServerIsNumericFunctionTranslator([NotNull] ISqlExpressionFactory sqlExpressionFactory)
37+
=> _sqlExpressionFactory = sqlExpressionFactory;
38+
39+
/// <summary>
40+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
41+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
42+
/// any release. You should only use it directly in your code with extreme caution and knowing that
43+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
44+
/// </summary>
45+
public virtual SqlExpression? Translate(
46+
SqlExpression? instance,
47+
MethodInfo method,
48+
IReadOnlyList<SqlExpression> arguments,
49+
IDiagnosticsLogger<DbLoggerCategory.Query> logger)
50+
{
51+
Check.NotNull(method, nameof(method));
52+
Check.NotNull(arguments, nameof(arguments));
53+
Check.NotNull(logger, nameof(logger));
54+
55+
return _methodInfo.Equals(method)
56+
? _sqlExpressionFactory.Equal(
57+
_sqlExpressionFactory.Function(
58+
"ISNUMERIC",
59+
new[] { arguments[1] },
60+
nullable: false,
61+
argumentsPropagateNullability: new[] { false },
62+
typeof(int)),
63+
_sqlExpressionFactory.Constant(1))
64+
: null;
65+
}
66+
}
67+
}

src/EFCore.SqlServer/Query/Internal/SqlServerMethodCallTranslatorProvider.cs

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ public SqlServerMethodCallTranslatorProvider([NotNull] RelationalMethodCallTrans
3838
new SqlServerFromPartsFunctionTranslator(sqlExpressionFactory, typeMappingSource),
3939
new SqlServerFullTextSearchFunctionsTranslator(sqlExpressionFactory),
4040
new SqlServerIsDateFunctionTranslator(sqlExpressionFactory),
41+
new SqlServerIsNumericFunctionTranslator(sqlExpressionFactory),
4142
new SqlServerMathTranslator(sqlExpressionFactory),
4243
new SqlServerNewGuidTranslator(sqlExpressionFactory),
4344
new SqlServerObjectToStringTranslator(sqlExpressionFactory),

test/EFCore.SqlServer.FunctionalTests/Query/NorthwindDbFunctionsQuerySqlServerTest.cs

+67
Original file line numberDiff line numberDiff line change
@@ -742,6 +742,73 @@ public void IsDate_should_throw_on_client_eval()
742742
exIsDate.Message);
743743
}
744744

745+
[ConditionalTheory]
746+
[MemberData(nameof(IsAsyncData))]
747+
public virtual async Task IsNumeric_not_valid(bool async)
748+
{
749+
await AssertQueryScalar(
750+
async,
751+
ss => ss.Set<Order>()
752+
.Where(o => !EF.Functions.IsNumeric(o.OrderDate.Value.ToString()))
753+
.Select(o => EF.Functions.IsNumeric(o.OrderDate.Value.ToString())),
754+
ss => ss.Set<Order>().Select(c => false));
755+
756+
AssertSql(
757+
@"SELECT CASE
758+
WHEN ISNUMERIC(CONVERT(varchar(100), [o].[OrderDate])) = 1 THEN CAST(1 AS bit)
759+
ELSE CAST(0 AS bit)
760+
END
761+
FROM [Orders] AS [o]
762+
WHERE ISNUMERIC(CONVERT(varchar(100), [o].[OrderDate])) <> 1");
763+
}
764+
765+
[ConditionalTheory]
766+
[MemberData(nameof(IsAsyncData))]
767+
public virtual async Task IsNummeric_valid(bool async)
768+
{
769+
await AssertQueryScalar(
770+
async,
771+
ss => ss.Set<OrderDetail>()
772+
.Where(o => EF.Functions.IsNumeric(o.UnitPrice.ToString()))
773+
.Select(o => EF.Functions.IsNumeric(o.UnitPrice.ToString())),
774+
ss => ss.Set<OrderDetail>().Select(o => true));
775+
776+
AssertSql(
777+
@"SELECT CASE
778+
WHEN ISNUMERIC(CONVERT(varchar(100), [o].[UnitPrice])) = 1 THEN CAST(1 AS bit)
779+
ELSE CAST(0 AS bit)
780+
END
781+
FROM [Order Details] AS [o]
782+
WHERE ISNUMERIC(CONVERT(varchar(100), [o].[UnitPrice])) = 1");
783+
}
784+
785+
[ConditionalTheory]
786+
[MemberData(nameof(IsAsyncData))]
787+
public virtual async Task IsNumeric_join_fields(bool async)
788+
{
789+
await AssertCount(
790+
async,
791+
ss => ss.Set<Order>(),
792+
ss => ss.Set<Order>(),
793+
c => EF.Functions.IsNumeric(c.CustomerID + c.OrderID),
794+
c => false);
795+
796+
AssertSql(
797+
@"SELECT COUNT(*)
798+
FROM [Orders] AS [o]
799+
WHERE ISNUMERIC(COALESCE([o].[CustomerID], N'') + CAST([o].[OrderID] AS nchar(5))) = 1");
800+
}
801+
802+
[ConditionalFact]
803+
public void IsNumeric_should_throw_on_client_eval()
804+
{
805+
var exIsDate = Assert.Throws<InvalidOperationException>(() => EF.Functions.IsNumeric("#ISNUMERIC#"));
806+
807+
Assert.Equal(
808+
CoreStrings.FunctionOnClient(nameof(SqlServerDbFunctionsExtensions.IsNumeric)),
809+
exIsDate.Message);
810+
}
811+
745812
[ConditionalTheory]
746813
[MemberData(nameof(IsAsyncData))]
747814
public virtual async Task DateTimeFromParts_column_compare(bool async)

0 commit comments

Comments
 (0)