Skip to content

Commit b1f4bde

Browse files
authored
CSHARP-4695: Expression not supported: string.Concat (#1123)
1 parent a3921d0 commit b1f4bde

File tree

5 files changed

+326
-15
lines changed

5 files changed

+326
-15
lines changed

src/MongoDB.Driver/Linq/Linq3Implementation/Reflection/StringMethod.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ internal static class StringMethod
2828
private static readonly MethodInfo __anyStringInWithParams;
2929
private static readonly MethodInfo __anyStringNinWithEnumerable;
3030
private static readonly MethodInfo __anyStringNinWithParams;
31+
private static readonly MethodInfo __concatWith2Strings;
32+
private static readonly MethodInfo __concatWith3Strings;
33+
private static readonly MethodInfo __concatWith4Strings;
34+
private static readonly MethodInfo __concatWithStringArray;
3135
private static readonly MethodInfo __containsWithChar;
3236
private static readonly MethodInfo __containsWithCharAndComparisonType;
3337
private static readonly MethodInfo __containsWithString;
@@ -104,6 +108,10 @@ static StringMethod()
104108
__anyStringInWithParams = ReflectionInfo.Method((IEnumerable<string> s, StringOrRegularExpression[] values) => s.AnyStringIn(values));
105109
__anyStringNinWithEnumerable = ReflectionInfo.Method((IEnumerable<string> s, IEnumerable<StringOrRegularExpression> values) => s.AnyStringNin(values));
106110
__anyStringNinWithParams = ReflectionInfo.Method((IEnumerable<string> s, StringOrRegularExpression[] values) => s.AnyStringNin(values));
111+
__concatWith2Strings = ReflectionInfo.Method((string str0, string str1) => string.Concat(str0, str1));
112+
__concatWith3Strings = ReflectionInfo.Method((string str0, string str1, string str2) => string.Concat(str0, str1, str2));
113+
__concatWith4Strings = ReflectionInfo.Method((string str0, string str1, string str2, string str3) => string.Concat(str0, str1, str2, str3));
114+
__concatWithStringArray = ReflectionInfo.Method((string[] s) => string.Concat(s));
107115
__containsWithString = ReflectionInfo.Method((string s, string value) => s.Contains(value));
108116
__endsWithWithString = ReflectionInfo.Method((string s, string value) => s.EndsWith(value));
109117
__endsWithWithStringAndComparisonType = ReflectionInfo.Method((string s, string value, StringComparison comparisonType) => s.EndsWith(value, comparisonType));
@@ -160,6 +168,10 @@ static StringMethod()
160168
public static MethodInfo AnyStringInWithParams => __anyStringInWithParams;
161169
public static MethodInfo AnyStringNinWithEnumerable => __anyStringNinWithEnumerable;
162170
public static MethodInfo AnyStringNinWithParams => __anyStringNinWithParams;
171+
public static MethodInfo ConcatWith2Strings => __concatWith2Strings;
172+
public static MethodInfo ConcatWith3Strings => __concatWith3Strings;
173+
public static MethodInfo ConcatWith4Strings => __concatWith4Strings;
174+
public static MethodInfo ConcatWithStringArray => __concatWithStringArray;
163175
public static MethodInfo ContainsWithChar => __containsWithChar;
164176
public static MethodInfo ContainsWithCharAndComparisonType => __containsWithCharAndComparisonType;
165177
public static MethodInfo ContainsWithString => __containsWithString;

src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/ConcatMethodToAggregationExpressionTranslator.cs

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,30 +14,21 @@
1414
*/
1515

1616
using System.Linq.Expressions;
17-
using MongoDB.Driver.Linq.Linq3Implementation.Ast.Expressions;
18-
using MongoDB.Driver.Linq.Linq3Implementation.Misc;
19-
using MongoDB.Driver.Linq.Linq3Implementation.Reflection;
20-
using MongoDB.Driver.Linq.Linq3Implementation.Serializers;
2117

2218
namespace MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToAggregationExpressionTranslators.MethodTranslators
2319
{
2420
internal static class ConcatMethodToAggregationExpressionTranslator
2521
{
2622
public static AggregationExpression Translate(TranslationContext context, MethodCallExpression expression)
2723
{
28-
var method = expression.Method;
29-
var arguments = expression.Arguments;
24+
if (EnumerableConcatMethodToAggregationExpressionTranslator.CanTranslate(expression))
25+
{
26+
return EnumerableConcatMethodToAggregationExpressionTranslator.Translate(context, expression);
27+
}
3028

31-
if (method.Is(EnumerableMethod.Concat))
29+
if (StringConcatMethodToAggregationExpressionTranslator.CanTranslate(expression))
3230
{
33-
var firstExpression = arguments[0];
34-
var firstTranslation = ExpressionToAggregationExpressionTranslator.TranslateEnumerable(context, firstExpression);
35-
var secondExpression = arguments[1];
36-
var secondTranslation = ExpressionToAggregationExpressionTranslator.TranslateEnumerable(context, secondExpression);
37-
var ast = AstExpression.ConcatArrays(firstTranslation.Ast, secondTranslation.Ast);
38-
var itemSerializer = ArraySerializerHelper.GetItemSerializer(firstTranslation.Serializer);
39-
var serializer = IEnumerableSerializer.Create(itemSerializer);
40-
return new AggregationExpression(expression, ast, serializer);
31+
return StringConcatMethodToAggregationExpressionTranslator.Translate(context, expression);
4132
}
4233

4334
throw new ExpressionNotSupportedException(expression);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/* Copyright 2010-present MongoDB Inc.
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
using System.Linq.Expressions;
17+
using MongoDB.Driver.Linq.Linq3Implementation.Ast.Expressions;
18+
using MongoDB.Driver.Linq.Linq3Implementation.Misc;
19+
using MongoDB.Driver.Linq.Linq3Implementation.Reflection;
20+
using MongoDB.Driver.Linq.Linq3Implementation.Serializers;
21+
22+
namespace MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToAggregationExpressionTranslators.MethodTranslators
23+
{
24+
internal static class EnumerableConcatMethodToAggregationExpressionTranslator
25+
{
26+
public static bool CanTranslate(MethodCallExpression expression)
27+
=> expression.Method.Is(EnumerableMethod.Concat);
28+
29+
public static AggregationExpression Translate(TranslationContext context, MethodCallExpression expression)
30+
{
31+
var method = expression.Method;
32+
var arguments = expression.Arguments;
33+
34+
if (method.Is(EnumerableMethod.Concat))
35+
{
36+
var firstExpression = arguments[0];
37+
var firstTranslation = ExpressionToAggregationExpressionTranslator.TranslateEnumerable(context, firstExpression);
38+
var secondExpression = arguments[1];
39+
var secondTranslation = ExpressionToAggregationExpressionTranslator.TranslateEnumerable(context, secondExpression);
40+
var ast = AstExpression.ConcatArrays(firstTranslation.Ast, secondTranslation.Ast);
41+
var itemSerializer = ArraySerializerHelper.GetItemSerializer(firstTranslation.Serializer);
42+
var serializer = IEnumerableSerializer.Create(itemSerializer);
43+
return new AggregationExpression(expression, ast, serializer);
44+
}
45+
46+
throw new ExpressionNotSupportedException(expression);
47+
}
48+
}
49+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/* Copyright 2010-present MongoDB Inc.
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
using System.Collections.Generic;
17+
using System.Linq;
18+
using System.Linq.Expressions;
19+
using System.Reflection;
20+
using MongoDB.Bson.Serialization.Serializers;
21+
using MongoDB.Driver.Linq.Linq3Implementation.Ast.Expressions;
22+
using MongoDB.Driver.Linq.Linq3Implementation.Misc;
23+
24+
namespace MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToAggregationExpressionTranslators.MethodTranslators
25+
{
26+
internal static class StringConcatMethodToAggregationExpressionTranslator
27+
{
28+
private static readonly MethodInfo[] __stringConcatMethods = new[]
29+
{
30+
StringMethod.ConcatWith2Strings,
31+
StringMethod.ConcatWith3Strings,
32+
StringMethod.ConcatWith4Strings,
33+
StringMethod.ConcatWithStringArray
34+
};
35+
36+
public static bool CanTranslate(MethodCallExpression expression)
37+
=> expression.Method.IsOneOf(__stringConcatMethods);
38+
39+
public static AggregationExpression Translate(TranslationContext context, MethodCallExpression expression)
40+
{
41+
var method = expression.Method;
42+
var arguments = expression.Arguments;
43+
44+
IEnumerable<AstExpression> argumentsTranslations = null;
45+
46+
if (method.IsOneOf(
47+
StringMethod.ConcatWith2Strings,
48+
StringMethod.ConcatWith3Strings,
49+
StringMethod.ConcatWith4Strings))
50+
{
51+
argumentsTranslations =
52+
arguments.Select(a => ExpressionToAggregationExpressionTranslator.Translate(context, a).Ast);
53+
}
54+
55+
if (method.Is(StringMethod.ConcatWithStringArray))
56+
{
57+
var argumentTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, arguments.Single());
58+
if (argumentTranslation.Ast is AstComputedArrayExpression astArray)
59+
{
60+
argumentsTranslations = astArray.Items;
61+
}
62+
}
63+
64+
if (argumentsTranslations != null)
65+
{
66+
var ast = AstExpression.Concat(argumentsTranslations.ToArray());
67+
return new AggregationExpression(expression, ast, StringSerializer.Instance);
68+
}
69+
70+
throw new ExpressionNotSupportedException(expression);
71+
}
72+
}
73+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
/* Copyright 2010-present MongoDB Inc.
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
using FluentAssertions;
17+
using System.Linq;
18+
using Xunit;
19+
20+
namespace MongoDB.Driver.Tests.Linq.Linq3Implementation.Translators.ExpressionToAggregationExpressionTranslators.MethodTranslators
21+
{
22+
public class StringConcatMethodToAggregationExpressionTranslatorTests : Linq3IntegrationTest
23+
{
24+
[Fact]
25+
public void Filter_using_string_concat_with_two_strings_should_work()
26+
{
27+
var collection = CreateCollection();
28+
29+
var queryable = collection.AsQueryable()
30+
.Where(i => string.Concat(i.A, ";") == "A1;");
31+
32+
var stages = Translate(collection, queryable);
33+
AssertStages(
34+
stages,
35+
"{ $match : { $expr : { $eq : [{ $concat : ['$A', ';'] }, 'A1;'] } } }");
36+
37+
var result = queryable.Single();
38+
result.Id.Should().Be(1);
39+
}
40+
41+
[Fact]
42+
public void Projection_using_string_concat_with_two_strings_should_work()
43+
{
44+
var collection = CreateCollection();
45+
46+
var queryable = collection.AsQueryable()
47+
.Where(i => i.Id == 1)
48+
.Select(i => new { T = string.Concat(i.A, ";") });
49+
50+
var stages = Translate(collection, queryable);
51+
AssertStages(
52+
stages,
53+
"{ $match : { _id : 1 } }",
54+
"{ $project : { T : { $concat : ['$A', ';'] }, _id : 0 } }");
55+
56+
var result = queryable.Single();
57+
result.T.Should().Be("A1;");
58+
}
59+
60+
[Fact]
61+
public void Filter_using_string_concat_with_three_strings_should_work()
62+
{
63+
var collection = CreateCollection();
64+
65+
var queryable = collection.AsQueryable()
66+
.Where(i => string.Concat(i.A, ";", i.B) == "A1;B1");
67+
68+
var stages = Translate(collection, queryable);
69+
AssertStages(
70+
stages,
71+
"{ $match : { $expr : { $eq : [{ $concat : ['$A', ';', '$B'] }, 'A1;B1'] } } }");
72+
73+
var result = queryable.Single();
74+
result.Id.Should().Be(1);
75+
}
76+
77+
[Fact]
78+
public void Projection_using_string_concat_with_three_strings_should_work()
79+
{
80+
var collection = CreateCollection();
81+
82+
var queryable = collection.AsQueryable()
83+
.Where(i => i.Id == 1)
84+
.Select(i => new { T = string.Concat(i.A, ";", i.B) });
85+
86+
var stages = Translate(collection, queryable);
87+
AssertStages(
88+
stages,
89+
"{ $match : { _id : 1 } }",
90+
"{ $project : { T : { $concat : ['$A', ';', '$B'] }, _id : 0 } }");
91+
92+
var result = queryable.Single();
93+
result.T.Should().Be("A1;B1");
94+
}
95+
96+
[Fact]
97+
public void Filter_using_string_concat_with_four_strings_should_work()
98+
{
99+
var collection = CreateCollection();
100+
101+
var queryable = collection.AsQueryable()
102+
.Where(i => string.Concat(i.A, ";", i.B, i.C) == "A1;B1C1");
103+
104+
var stages = Translate(collection, queryable);
105+
AssertStages(
106+
stages,
107+
"{ $match : { $expr : { $eq : [{ $concat : ['$A', ';', '$B', '$C'] }, 'A1;B1C1'] } } }");
108+
109+
var result = queryable.Single();
110+
result.Id.Should().Be(1);
111+
}
112+
113+
[Fact]
114+
public void Projection_using_string_concat_with_four_strings_should_work()
115+
{
116+
var collection = CreateCollection();
117+
118+
var queryable = collection.AsQueryable()
119+
.Where(i => i.Id == 1)
120+
.Select(i => new { T = string.Concat(i.A, ";", i.B, i.C) });
121+
122+
var stages = Translate(collection, queryable);
123+
AssertStages(
124+
stages,
125+
"{ $match : { _id : 1 } }",
126+
"{ $project : { T : { $concat : ['$A', ';', '$B', '$C'] }, _id : 0 } }");
127+
128+
var result = queryable.Single();
129+
result.T.Should().Be("A1;B1C1");
130+
}
131+
132+
[Fact]
133+
public void Filter_using_string_concat_with_params_array_should_work()
134+
{
135+
var collection = CreateCollection();
136+
var queryable = collection.AsQueryable()
137+
.Where(i => string.Concat(i.A, ";", i.B, ";", i.C) == "A1;B1;C1");
138+
139+
var stages = Translate(collection, queryable);
140+
AssertStages(
141+
stages,
142+
"{ $match : { $expr : { $eq : [{ $concat : ['$A', ';', '$B', ';', '$C'] }, 'A1;B1;C1'] } } }");
143+
144+
var result = queryable.Single();
145+
result.Id.Should().Be(1);
146+
}
147+
148+
[Fact]
149+
public void Projection_using_string_concat_with_params_array_should_work()
150+
{
151+
var collection = CreateCollection();
152+
153+
var queryable = collection.AsQueryable()
154+
.Where(i => i.Id == 1)
155+
.Select(i => new { T = string.Concat(i.A, ";", i.B, ";", i.C) });
156+
157+
var stages = Translate(collection, queryable);
158+
AssertStages(
159+
stages,
160+
"{ $match : { '_id' : 1 } }",
161+
"{ $project : { T : { $concat : ['$A', ';', '$B', ';', '$C'] }, _id : 0 } }");
162+
163+
var result = queryable.Single();
164+
result.T.Should().Be("A1;B1;C1");
165+
}
166+
167+
private IMongoCollection<Data> CreateCollection()
168+
{
169+
var collection = GetCollection<Data>("test");
170+
CreateCollection(
171+
collection,
172+
new Data { Id = 1, A = "A1", B = "B1", C = "C1", D="D1" },
173+
new Data { Id = 2, A = "A2", B = "B2", C = "C2", D="D2" });
174+
return collection;
175+
}
176+
177+
private class Data
178+
{
179+
public int Id { get; set; }
180+
public string A { get; set; }
181+
public string B { get; set; }
182+
public string C { get; set; }
183+
public string D { get; set; }
184+
}
185+
}
186+
}

0 commit comments

Comments
 (0)