diff --git a/src/EFCore.Relational/Extensions/RelationalDbFunctionsExtensions.cs b/src/EFCore.Relational/Extensions/RelationalDbFunctionsExtensions.cs index f0f6a0e1ea8..bd427e791f3 100644 --- a/src/EFCore.Relational/Extensions/RelationalDbFunctionsExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalDbFunctionsExtensions.cs @@ -56,4 +56,17 @@ public static class RelationalDbFunctionsExtensions this DbFunctions _, [NotParameterized] params T[] values) => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Greatest))); + + /// + /// Returns if equals , otherwise returns . Usually corresponds to the NULLIF SQL function. + /// + /// The type + /// The instance. + /// The expression. + /// The matching expression. + public static T? NullIf( + this DbFunctions _, + T expression, + T matchExpression) + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Collate))); } diff --git a/src/EFCore.Relational/Query/Internal/NullIfTranslator.cs b/src/EFCore.Relational/Query/Internal/NullIfTranslator.cs new file mode 100644 index 00000000000..794d9ec2330 --- /dev/null +++ b/src/EFCore.Relational/Query/Internal/NullIfTranslator.cs @@ -0,0 +1,52 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.Query.SqlExpressions; + +namespace Microsoft.EntityFrameworkCore.Query.Internal; + +/// +/// 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. +/// +public class NullIfTranslator : IMethodCallTranslator +{ + private static readonly MethodInfo MethodInfo + = typeof(RelationalDbFunctionsExtensions).GetMethod(nameof(RelationalDbFunctionsExtensions.NullIf))!; + + private readonly ISqlExpressionFactory _sqlExpressionFactory; + + /// + /// 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. + /// + public NullIfTranslator(ISqlExpressionFactory sqlExpressionFactory) + { + _sqlExpressionFactory = sqlExpressionFactory; + } + + /// + /// 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. + /// + public SqlExpression? Translate( + SqlExpression? instance, + MethodInfo method, + IReadOnlyList arguments, + IDiagnosticsLogger logger) + => method.IsGenericMethod + && Equals(method.GetGenericMethodDefinition(), MethodInfo) + ? _sqlExpressionFactory.Function( + "NULLIF", + new[] { arguments[1], arguments[2] }, + nullable: true, + argumentsPropagateNullability: new[] { true, true }, + method.ReturnType) + : null; +} diff --git a/src/EFCore.Relational/Query/RelationalMethodCallTranslatorProvider.cs b/src/EFCore.Relational/Query/RelationalMethodCallTranslatorProvider.cs index 1de2f16a1f3..3d79bfeaed2 100644 --- a/src/EFCore.Relational/Query/RelationalMethodCallTranslatorProvider.cs +++ b/src/EFCore.Relational/Query/RelationalMethodCallTranslatorProvider.cs @@ -37,7 +37,8 @@ public RelationalMethodCallTranslatorProvider(RelationalMethodCallTranslatorProv new GetValueOrDefaultTranslator(sqlExpressionFactory), new ComparisonTranslator(sqlExpressionFactory), new ByteArraySequenceEqualTranslator(sqlExpressionFactory), - new RandomTranslator(sqlExpressionFactory) + new RandomTranslator(sqlExpressionFactory), + new NullIfTranslator(sqlExpressionFactory) }); _sqlExpressionFactory = sqlExpressionFactory; } diff --git a/test/EFCore.Relational.Specification.Tests/Query/RelationalNorthwindDbFunctionsQueryTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/RelationalNorthwindDbFunctionsQueryTestBase.cs index db705b79cda..d2fd53d5e51 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/RelationalNorthwindDbFunctionsQueryTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/RelationalNorthwindDbFunctionsQueryTestBase.cs @@ -92,6 +92,16 @@ public virtual async Task Greatest_with_parameter_array_is_not_supported(bool as ss => ss.Set().Where(od => EF.Functions.Greatest(arr) == 10251))); } + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task NullIf(bool async) + => AssertCount( + async, + ss => ss.Set(), + ss => ss.Set(), + c => EF.Functions.NullIf(c.ContactName, "maria anders") == null, + c => c.ContactName == null); + protected abstract string CaseInsensitiveCollation { get; } protected abstract string CaseSensitiveCollation { get; } }