diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosMethodCallTranslatorProvider.cs b/src/EFCore.Cosmos/Query/Internal/CosmosMethodCallTranslatorProvider.cs
index e6007b2344f..37e983cbd81 100644
--- a/src/EFCore.Cosmos/Query/Internal/CosmosMethodCallTranslatorProvider.cs
+++ b/src/EFCore.Cosmos/Query/Internal/CosmosMethodCallTranslatorProvider.cs
@@ -37,7 +37,7 @@ public CosmosMethodCallTranslatorProvider(
new IMethodCallTranslator[]
{
new EqualsTranslator(sqlExpressionFactory),
- //new StringMethodTranslator(sqlExpressionFactory),
+ new StringMethodTranslator(sqlExpressionFactory),
new ContainsTranslator(sqlExpressionFactory)
//new LikeTranslator(sqlExpressionFactory),
//new EnumHasFlagTranslator(sqlExpressionFactory),
diff --git a/src/EFCore.Cosmos/Query/Internal/SqlExpressionFactory.cs b/src/EFCore.Cosmos/Query/Internal/SqlExpressionFactory.cs
index 5956c5c850d..01281c5a696 100644
--- a/src/EFCore.Cosmos/Query/Internal/SqlExpressionFactory.cs
+++ b/src/EFCore.Cosmos/Query/Internal/SqlExpressionFactory.cs
@@ -85,6 +85,9 @@ public virtual SqlExpression ApplyTypeMapping(SqlExpression sqlExpression, CoreT
case SqlParameterExpression sqlParameterExpression:
return sqlParameterExpression.ApplyTypeMapping(typeMapping);
+ case SqlFunctionExpression sqlFunctionExpression:
+ return sqlFunctionExpression.ApplyTypeMapping(typeMapping);
+
default:
return sqlExpression;
}
diff --git a/src/EFCore.Cosmos/Query/Internal/StringMethodTranslator.cs b/src/EFCore.Cosmos/Query/Internal/StringMethodTranslator.cs
new file mode 100644
index 00000000000..55322524dbb
--- /dev/null
+++ b/src/EFCore.Cosmos/Query/Internal/StringMethodTranslator.cs
@@ -0,0 +1,81 @@
+// 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.Utilities;
+
+namespace Microsoft.EntityFrameworkCore.Cosmos.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 StringMethodTranslator : IMethodCallTranslator
+ {
+ private static readonly MethodInfo _containsMethodInfo
+ = typeof(string).GetRuntimeMethod(nameof(string.Contains), new[] { typeof(string) });
+
+ private static readonly MethodInfo _startsWithMethodInfo
+ = typeof(string).GetRuntimeMethod(nameof(string.StartsWith), new[] { typeof(string) });
+
+ private static readonly MethodInfo _endsWithMethodInfo
+ = typeof(string).GetRuntimeMethod(nameof(string.EndsWith), new[] { typeof(string) });
+
+ 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 StringMethodTranslator([NotNull] 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 virtual SqlExpression Translate(SqlExpression instance, MethodInfo method, IReadOnlyList arguments)
+ {
+ Check.NotNull(method, nameof(method));
+ Check.NotNull(arguments, nameof(arguments));
+
+ if (_containsMethodInfo.Equals(method))
+ {
+ return TranslateSystemFunction("CONTAINS", instance, arguments[0], typeof(bool));
+ }
+
+ if (_startsWithMethodInfo.Equals(method))
+ {
+ return TranslateSystemFunction("STARTSWITH", instance, arguments[0], typeof(bool));
+ }
+
+ if (_endsWithMethodInfo.Equals(method))
+ {
+ return TranslateSystemFunction("ENDSWITH", instance, arguments[0], typeof(bool));
+ }
+
+ return null;
+ }
+
+ private SqlExpression TranslateSystemFunction(string function, SqlExpression instance, SqlExpression pattern, Type returnType)
+ {
+ Check.NotNull(instance, nameof(instance));
+ return _sqlExpressionFactory.ApplyDefaultTypeMapping(
+ _sqlExpressionFactory.Function(
+ function,
+ new[] { instance, pattern },
+ returnType));
+ }
+ }
+}
diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindFunctionsQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindFunctionsQueryCosmosTest.cs
index 3fe74c7f6cc..f7269f0bafc 100644
--- a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindFunctionsQueryCosmosTest.cs
+++ b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindFunctionsQueryCosmosTest.cs
@@ -19,7 +19,6 @@ public NorthwindFunctionsQueryCosmosTest(
//Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper);
}
- [ConditionalTheory(Skip = "Issue #17246")]
public override async Task String_StartsWith_Literal(bool async)
{
await base.String_StartsWith_Literal(async);
@@ -27,10 +26,9 @@ public override async Task String_StartsWith_Literal(bool async)
AssertSql(
@"SELECT c
FROM root c
-WHERE (c[""Discriminator""] = ""Customer"")");
+WHERE ((c[""Discriminator""] = ""Customer"") AND ((c[""ContactName""] != null) AND ((""M"" != null) AND STARTSWITH(c[""ContactName""], ""M""))))");
}
- [ConditionalTheory(Skip = "Issue #17246")]
public override async Task String_StartsWith_Identity(bool async)
{
await base.String_StartsWith_Identity(async);
@@ -38,10 +36,9 @@ public override async Task String_StartsWith_Identity(bool async)
AssertSql(
@"SELECT c
FROM root c
-WHERE (c[""Discriminator""] = ""Customer"")");
+WHERE ((c[""Discriminator""] = ""Customer"") AND ((c[""ContactName""] = """") OR ((c[""ContactName""] != null) AND ((c[""ContactName""] != null) AND STARTSWITH(c[""ContactName""], c[""ContactName""])))))");
}
- [ConditionalTheory(Skip = "Issue #17246")]
public override async Task String_StartsWith_Column(bool async)
{
await base.String_StartsWith_Column(async);
@@ -49,10 +46,9 @@ public override async Task String_StartsWith_Column(bool async)
AssertSql(
@"SELECT c
FROM root c
-WHERE (c[""Discriminator""] = ""Customer"")");
+WHERE ((c[""Discriminator""] = ""Customer"") AND ((c[""ContactName""] = """") OR ((c[""ContactName""] != null) AND ((c[""ContactName""] != null) AND STARTSWITH(c[""ContactName""], c[""ContactName""])))))");
}
- [ConditionalTheory(Skip = "Issue #17246")]
public override async Task String_StartsWith_MethodCall(bool async)
{
await base.String_StartsWith_MethodCall(async);
@@ -60,10 +56,9 @@ public override async Task String_StartsWith_MethodCall(bool async)
AssertSql(
@"SELECT c
FROM root c
-WHERE (c[""Discriminator""] = ""Customer"")");
+WHERE ((c[""Discriminator""] = ""Customer"") AND ((c[""ContactName""] != null) AND ((""M"" != null) AND STARTSWITH(c[""ContactName""], ""M""))))");
}
- [ConditionalTheory(Skip = "Issue #17246")]
public override async Task String_EndsWith_Literal(bool async)
{
await base.String_EndsWith_Literal(async);
@@ -71,10 +66,9 @@ public override async Task String_EndsWith_Literal(bool async)
AssertSql(
@"SELECT c
FROM root c
-WHERE (c[""Discriminator""] = ""Customer"")");
+WHERE ((c[""Discriminator""] = ""Customer"") AND ((c[""ContactName""] != null) AND ((""b"" != null) AND ENDSWITH(c[""ContactName""], ""b""))))");
}
- [ConditionalTheory(Skip = "Issue #17246")]
public override async Task String_EndsWith_Identity(bool async)
{
await base.String_EndsWith_Identity(async);
@@ -82,10 +76,9 @@ public override async Task String_EndsWith_Identity(bool async)
AssertSql(
@"SELECT c
FROM root c
-WHERE (c[""Discriminator""] = ""Customer"")");
+WHERE ((c[""Discriminator""] = ""Customer"") AND ((c[""ContactName""] = """") OR ((c[""ContactName""] != null) AND ((c[""ContactName""] != null) AND ENDSWITH(c[""ContactName""], c[""ContactName""])))))");
}
- [ConditionalTheory(Skip = "Issue #17246")]
public override async Task String_EndsWith_Column(bool async)
{
await base.String_EndsWith_Column(async);
@@ -93,10 +86,9 @@ public override async Task String_EndsWith_Column(bool async)
AssertSql(
@"SELECT c
FROM root c
-WHERE (c[""Discriminator""] = ""Customer"")");
+WHERE ((c[""Discriminator""] = ""Customer"") AND ((c[""ContactName""] = """") OR ((c[""ContactName""] != null) AND ((c[""ContactName""] != null) AND ENDSWITH(c[""ContactName""], c[""ContactName""])))))");
}
- [ConditionalTheory(Skip = "Issue #17246")]
public override async Task String_EndsWith_MethodCall(bool async)
{
await base.String_EndsWith_MethodCall(async);
@@ -104,10 +96,9 @@ public override async Task String_EndsWith_MethodCall(bool async)
AssertSql(
@"SELECT c
FROM root c
-WHERE (c[""Discriminator""] = ""Customer"")");
+WHERE ((c[""Discriminator""] = ""Customer"") AND ((c[""ContactName""] != null) AND ((""m"" != null) AND ENDSWITH(c[""ContactName""], ""m""))))");
}
- [ConditionalTheory(Skip = "Issue #17246")]
public override async Task String_Contains_Literal(bool async)
{
await base.String_Contains_Literal(async);
@@ -115,21 +106,18 @@ public override async Task String_Contains_Literal(bool async)
AssertSql(
@"SELECT c
FROM root c
-WHERE (c[""Discriminator""] = ""Customer"")");
+WHERE ((c[""Discriminator""] = ""Customer"") AND CONTAINS(c[""ContactName""], ""M""))");
}
- [ConditionalTheory(Skip = "Issue #17246")]
public override async Task String_Contains_Identity(bool async)
{
await base.String_Contains_Identity(async);
-
AssertSql(
@"SELECT c
FROM root c
-WHERE (c[""Discriminator""] = ""Customer"")");
+WHERE ((c[""Discriminator""] = ""Customer"") AND CONTAINS(c[""ContactName""], c[""ContactName""]))");
}
- [ConditionalTheory(Skip = "Issue #17246")]
public override async Task String_Contains_Column(bool async)
{
await base.String_Contains_Column(async);
@@ -137,10 +125,9 @@ public override async Task String_Contains_Column(bool async)
AssertSql(
@"SELECT c
FROM root c
-WHERE (c[""Discriminator""] = ""Customer"")");
+WHERE ((c[""Discriminator""] = ""Customer"") AND CONTAINS(c[""ContactName""], c[""ContactName""]))");
}
- [ConditionalTheory(Skip = "Issue #17246")]
public override async Task String_Contains_MethodCall(bool async)
{
await base.String_Contains_MethodCall(async);
@@ -148,7 +135,7 @@ public override async Task String_Contains_MethodCall(bool async)
AssertSql(
@"SELECT c
FROM root c
-WHERE (c[""Discriminator""] = ""Customer"")");
+WHERE ((c[""Discriminator""] = ""Customer"") AND CONTAINS(c[""ContactName""], ""M""))");
}
[ConditionalTheory(Skip = "Issue #17246")]