Skip to content

Commit 5694952

Browse files
committed
Add JsonExists DbFunction
- Fixes #31136 - Define SqlServer translation - Define Sqllite translation
1 parent 85c1c35 commit 5694952

File tree

5 files changed

+162
-0
lines changed

5 files changed

+162
-0
lines changed

src/EFCore.Relational/Extensions/RelationalDbFunctionsExtensions.cs

+28
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,32 @@ public static T Greatest<T>(
5656
this DbFunctions _,
5757
[NotParameterized] params T[] values)
5858
=> throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Greatest)));
59+
60+
/// <summary>
61+
/// Checks whether a specified JSON path exists within a JSON string.
62+
/// Typically corresponds to a database function or SQL expression.
63+
/// </summary>
64+
/// <remarks>
65+
/// <para>
66+
/// This method is translated to a database-specific function or expression.
67+
/// The support for this function depends on the database and provider being used.
68+
/// Refer to your database provider's documentation for detailed support information.
69+
/// </para>
70+
/// <para>
71+
/// For more details, see <see href="https://learn.microsoft.com/en-us/ef/core/providers">EF Core database providers</see>.
72+
/// </para>
73+
/// </remarks>
74+
/// <param name="_">The <see cref="DbFunctions"/> instance.</param>
75+
/// <param name="expression">The JSON string or column containing JSON text.</param>
76+
/// <param name="path">The JSON path to check for existence.</param>
77+
/// <typeparam name="T">The type of the JSON expression.</typeparam>
78+
/// <returns>
79+
/// A nullable boolean value, <see langword="true"/> if the JSON path exists, <see langword="false"/> if not, and <see langword="null"/>
80+
/// when the JSON string is null.
81+
/// </returns>
82+
public static bool? JsonExists<T>(
83+
this DbFunctions _,
84+
T expression,
85+
string path)
86+
=> throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(JsonExists)));
5987
}

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

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ public SqlServerMethodCallTranslatorProvider(
3838
new SqlServerFullTextSearchFunctionsTranslator(sqlExpressionFactory),
3939
new SqlServerIsDateFunctionTranslator(sqlExpressionFactory),
4040
new SqlServerIsNumericFunctionTranslator(sqlExpressionFactory),
41+
new SqlServerJsonFunctionsTranslator(sqlExpressionFactory, sqlServerSingletonOptions),
4142
new SqlServerMathTranslator(sqlExpressionFactory),
4243
new SqlServerNewGuidTranslator(sqlExpressionFactory),
4344
new SqlServerObjectToStringTranslator(sqlExpressionFactory, typeMappingSource),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
5+
using Microsoft.EntityFrameworkCore.SqlServer.Infrastructure.Internal;
6+
using Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal;
7+
8+
// ReSharper disable once CheckNamespace
9+
namespace Microsoft.EntityFrameworkCore.SqlServer.Query.Internal;
10+
11+
/// <summary>
12+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
13+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
14+
/// any release. You should only use it directly in your code with extreme caution and knowing that
15+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
16+
/// </summary>
17+
public class SqlServerJsonFunctionsTranslator : IMethodCallTranslator
18+
{
19+
private static readonly MethodInfo JsonExistsMethodInfo = typeof(RelationalDbFunctionsExtensions)
20+
.GetRuntimeMethod(nameof(RelationalDbFunctionsExtensions.JsonExists), [typeof(DbFunctions), typeof(object), typeof(string)])!;
21+
22+
private readonly ISqlExpressionFactory _sqlExpressionFactory;
23+
private readonly ISqlServerSingletonOptions _sqlServerSingletonOptions;
24+
25+
/// <summary>
26+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
27+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
28+
/// any release. You should only use it directly in your code with extreme caution and knowing that
29+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
30+
/// </summary>
31+
public SqlServerJsonFunctionsTranslator(ISqlExpressionFactory sqlExpressionFactory, ISqlServerSingletonOptions sqlServerSingletonOptions)
32+
{
33+
_sqlExpressionFactory = sqlExpressionFactory;
34+
_sqlServerSingletonOptions = sqlServerSingletonOptions;
35+
}
36+
37+
/// <summary>
38+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
39+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
40+
/// any release. You should only use it directly in your code with extreme caution and knowing that
41+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
42+
/// </summary>
43+
public virtual SqlExpression? Translate(
44+
SqlExpression? instance,
45+
MethodInfo method,
46+
IReadOnlyList<SqlExpression> arguments,
47+
IDiagnosticsLogger<DbLoggerCategory.Query> logger)
48+
{
49+
if (JsonExistsMethodInfo.Equals(method)
50+
&& arguments[0].TypeMapping is not SqlServerOwnedJsonTypeMapping and not StringTypeMapping
51+
&& _sqlServerSingletonOptions.EngineType == SqlServerEngineType.SqlServer
52+
&& _sqlServerSingletonOptions.SqlServerCompatibilityLevel >= 160)
53+
{
54+
return _sqlExpressionFactory.Function(
55+
"JSON_PATH_EXISTS",
56+
arguments,
57+
nullable: true,
58+
argumentsPropagateNullability: Statics.TrueArrays[2],
59+
method.ReturnType);
60+
}
61+
62+
return null;
63+
}
64+
}

src/EFCore.Sqlite.Core/Query/Internal/SqliteMethodCallTranslatorProvider.cs

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ public SqliteMethodCallTranslatorProvider(RelationalMethodCallTranslatorProvider
3030
new SqliteDateTimeMethodTranslator(sqlExpressionFactory),
3131
new SqliteGlobMethodTranslator(sqlExpressionFactory),
3232
new SqliteHexMethodTranslator(sqlExpressionFactory),
33+
new SqliteJsonFunctionsTranslator(sqlExpressionFactory),
3334
new SqliteMathTranslator(sqlExpressionFactory),
3435
new SqliteObjectToStringTranslator(sqlExpressionFactory),
3536
new SqliteRandomTranslator(sqlExpressionFactory),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
5+
using Microsoft.EntityFrameworkCore.Sqlite.Storage.Internal;
6+
7+
// ReSharper disable once CheckNamespace
8+
namespace Microsoft.EntityFrameworkCore.Sqlite.Query.Internal;
9+
10+
/// <summary>
11+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
12+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
13+
/// any release. You should only use it directly in your code with extreme caution and knowing that
14+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
15+
/// </summary>
16+
public class SqliteJsonFunctionsTranslator : IMethodCallTranslator
17+
{
18+
private static readonly MethodInfo JsonExistsMethodInfo = typeof(RelationalDbFunctionsExtensions)
19+
.GetRuntimeMethod(nameof(RelationalDbFunctionsExtensions.JsonExists), [typeof(DbFunctions), typeof(object), typeof(string)])!;
20+
21+
private readonly ISqlExpressionFactory _sqlExpressionFactory;
22+
23+
/// <summary>
24+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
25+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
26+
/// any release. You should only use it directly in your code with extreme caution and knowing that
27+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
28+
/// </summary>
29+
public SqliteJsonFunctionsTranslator(ISqlExpressionFactory sqlExpressionFactory)
30+
{
31+
_sqlExpressionFactory = sqlExpressionFactory;
32+
}
33+
34+
/// <summary>
35+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
36+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
37+
/// any release. You should only use it directly in your code with extreme caution and knowing that
38+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
39+
/// </summary>
40+
public virtual SqlExpression? Translate(
41+
SqlExpression? instance,
42+
MethodInfo method,
43+
IReadOnlyList<SqlExpression> arguments,
44+
IDiagnosticsLogger<DbLoggerCategory.Query> logger)
45+
{
46+
if (JsonExistsMethodInfo.Equals(method)
47+
&& arguments[0].TypeMapping is not SqliteJsonTypeMapping and not StringTypeMapping)
48+
{
49+
// IIF(arguments_0 IS NULL, NULL, JSON_TYPE(arguments_0, arguments_1) IS NOT NULL)
50+
return _sqlExpressionFactory.Function("IFF",
51+
[
52+
_sqlExpressionFactory.IsNull(arguments[0]),
53+
_sqlExpressionFactory.Fragment("NULL", method.ReturnType),
54+
_sqlExpressionFactory.IsNotNull(
55+
_sqlExpressionFactory.Function("JSON_TYPE",
56+
arguments,
57+
nullable: true,
58+
argumentsPropagateNullability: Statics.TrueArrays[2],
59+
returnType: typeof(string)))
60+
],
61+
nullable: true,
62+
argumentsPropagateNullability: Statics.TrueArrays[3],
63+
method.ReturnType);
64+
}
65+
66+
return null;
67+
}
68+
}

0 commit comments

Comments
 (0)