Skip to content

Commit 8160082

Browse files
committed
CSHARP-4662: Add an As method for IMongoQueryable similar to the existing As method for aggregation pipelines.
1 parent 00ae76a commit 8160082

File tree

5 files changed

+147
-1
lines changed

5 files changed

+147
-1
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ internal static class MongoQueryableMethod
2828
private static readonly MethodInfo __anyAsync;
2929
private static readonly MethodInfo __anyWithPredicateAsync;
3030
private static readonly MethodInfo __appendStage;
31+
private static readonly MethodInfo __as;
3132
private static readonly MethodInfo __averageDecimalAsync;
3233
private static readonly MethodInfo __averageDecimalWithSelectorAsync;
3334
private static readonly MethodInfo __averageDoubleAsync;
@@ -177,6 +178,7 @@ static MongoQueryableMethod()
177178
__anyAsync = ReflectionInfo.Method((IMongoQueryable<object> source, CancellationToken cancellationToken) => source.AnyAsync(cancellationToken));
178179
__anyWithPredicateAsync = ReflectionInfo.Method((IMongoQueryable<object> source, Expression<Func<object, bool>> predicate, CancellationToken cancellationToken) => source.AnyAsync(predicate, cancellationToken));
179180
__appendStage = ReflectionInfo.Method((IMongoQueryable<object> source, PipelineStageDefinition<object, object> stage, IBsonSerializer<object> resultSerializer) => source.AppendStage(stage, resultSerializer));
181+
__as = ReflectionInfo.Method((IMongoQueryable<object> source, IBsonSerializer<object> resultSerializer) => source.As(resultSerializer));
180182
__averageDecimalAsync = ReflectionInfo.Method((IMongoQueryable<decimal> source, CancellationToken cancellationToken) => source.AverageAsync(cancellationToken));
181183
__averageDecimalWithSelectorAsync = ReflectionInfo.Method((IMongoQueryable<object> source, Expression<Func<object, decimal>> selector, CancellationToken cancellationToken) => source.AverageAsync(selector, cancellationToken));
182184
__averageDoubleAsync = ReflectionInfo.Method((IMongoQueryable<double> source, CancellationToken cancellationToken) => source.AverageAsync(cancellationToken));
@@ -325,6 +327,7 @@ static MongoQueryableMethod()
325327
public static MethodInfo AnyAsync => __anyAsync;
326328
public static MethodInfo AnyWithPredicateAsync => __anyWithPredicateAsync;
327329
public static MethodInfo AppendStage => __appendStage;
330+
public static MethodInfo As => __as;
328331
public static MethodInfo AverageDecimalAsync => __averageDecimalAsync;
329332
public static MethodInfo AverageDecimalWithSelectorAsync => __averageDecimalWithSelectorAsync;
330333
public static MethodInfo AverageDoubleAsync => __averageDoubleAsync;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
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;
17+
using System.Linq.Expressions;
18+
using MongoDB.Bson.Serialization;
19+
using MongoDB.Driver.Linq.Linq3Implementation.Ast;
20+
using MongoDB.Driver.Linq.Linq3Implementation.Ast.Stages;
21+
using MongoDB.Driver.Linq.Linq3Implementation.ExtensionMethods;
22+
using MongoDB.Driver.Linq.Linq3Implementation.Misc;
23+
using MongoDB.Driver.Linq.Linq3Implementation.Reflection;
24+
25+
namespace MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToPipelineTranslators
26+
{
27+
internal static class AsMethodToPipelineTranslator
28+
{
29+
// public static methods
30+
public static AstPipeline Translate(TranslationContext context, MethodCallExpression expression)
31+
{
32+
var method = expression.Method;
33+
var arguments = expression.Arguments;
34+
35+
if (method.Is(MongoQueryableMethod.As))
36+
{
37+
var sourceExpression = ConvertHelper.RemoveConvertToMongoQueryable(arguments[0]);
38+
var pipeline = ExpressionToPipelineTranslator.Translate(context, sourceExpression);
39+
40+
var resultSerializerExpression = arguments[1];
41+
var resultSerializer = resultSerializerExpression.GetConstantValue<IBsonSerializer>(expression);
42+
var resultType = method.GetGenericArguments()[1];
43+
var outputSerializer = resultSerializer ?? BsonSerializer.LookupSerializer(resultType);
44+
45+
pipeline = pipeline.AddStages(outputSerializer, newStages: Array.Empty<AstStage>());
46+
return pipeline;
47+
}
48+
49+
throw new ExpressionNotSupportedException(expression);
50+
}
51+
}
52+
}

src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToPipelineTranslators/ExpressionToPipelineTranslator.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ public static AstPipeline Translate(TranslationContext context, Expression expre
3636
{
3737
case "AppendStage":
3838
return AppendStageMethodToPipelineTranslator.Translate(context, methodCallExpression);
39+
case "As":
40+
return AsMethodToPipelineTranslator.Translate(context, methodCallExpression);
3941
case "Densify":
4042
return DensifyMethodToPipelineTranslator.Translate(context, methodCallExpression);
4143
case "Distinct":

src/MongoDB.Driver/Linq/MongoQueryable.cs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@
2121
using System.Threading;
2222
using System.Threading.Tasks;
2323
using MongoDB.Bson.Serialization;
24-
using MongoDB.Driver.Search;
2524
using MongoDB.Driver.Core.Misc;
25+
using MongoDB.Driver.Search;
2626

2727
namespace MongoDB.Driver.Linq
2828
{
@@ -100,6 +100,31 @@ public static IMongoQueryable<TResult> AppendStage<TSource, TResult>(
100100
Expression.Constant(resultSerializer, typeof(IBsonSerializer<TResult>))));
101101
}
102102

103+
/// <summary>
104+
/// Allows the results to be interpreted as a different type. It is up to the caller
105+
/// to determine that the new result type is compatible with the actual results.
106+
/// </summary>
107+
/// <typeparam name="TSource">The type of the elements of <paramref name="source" />.</typeparam>
108+
/// <typeparam name="TResult">The new result type for the results.</typeparam>
109+
/// <param name="source">A sequence of values.</param>
110+
/// <param name="resultSerializer">The new serializer (optional, will be looked up if null).</param>
111+
/// <returns>
112+
/// A new IMongoQueryable with a new result type.
113+
/// </returns>
114+
public static IMongoQueryable<TResult> As<TSource, TResult>(
115+
this IMongoQueryable<TSource> source,
116+
IBsonSerializer<TResult> resultSerializer = null)
117+
{
118+
Ensure.IsNotNull(source, nameof(source));
119+
120+
return (IMongoQueryable<TResult>)source.Provider.CreateQuery<TResult>(
121+
Expression.Call(
122+
null,
123+
GetMethodInfo(As, source, resultSerializer),
124+
Expression.Convert(source.Expression, typeof(IMongoQueryable<TSource>)),
125+
Expression.Constant(resultSerializer, typeof(IBsonSerializer<TResult>))));
126+
}
127+
103128
/// <summary>
104129
/// Computes the average of a sequence of <see cref="Decimal"/> values.
105130
/// </summary>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
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;
17+
using FluentAssertions;
18+
using MongoDB.Bson;
19+
using MongoDB.Bson.Serialization.Serializers;
20+
using MongoDB.Driver;
21+
using MongoDB.Driver.Linq;
22+
using Xunit;
23+
24+
namespace MongoDB.Driver.Tests.Linq.Linq3ImplementationTests.Translators.ExpressionToPipelineTranslators
25+
{
26+
public class AsMethodToPipelineTranslatorTests : Linq3IntegrationTest
27+
{
28+
[Fact]
29+
public void As_should_work()
30+
{
31+
var collection = GetCollection();
32+
33+
var queryable = collection.AsQueryable()
34+
.Where(x => x.Name == "John")
35+
.As(BsonDocumentSerializer.Instance);
36+
37+
var stages = Translate(collection, queryable, out var outputSerializer);
38+
AssertStages(
39+
stages,
40+
"{ $match : { Name : 'John' } }");
41+
outputSerializer.Should().BeSameAs(BsonDocumentSerializer.Instance);
42+
43+
var result = queryable.Single();
44+
result.Should().BeOfType<BsonDocument>();
45+
result.Should().Be("{ _id : 1, Name : 'John' }");
46+
}
47+
48+
private IMongoCollection<C> GetCollection()
49+
{
50+
var collection = GetCollection<C>("test");
51+
CreateCollection(
52+
collection,
53+
new C { Id = 1, Name = "John" },
54+
new C { Id = 2, Name = "Jane" });
55+
return collection;
56+
}
57+
58+
private class C
59+
{
60+
public int Id { get; set; }
61+
public string Name { get; set; }
62+
}
63+
}
64+
}

0 commit comments

Comments
 (0)