Skip to content

Commit bcd3d96

Browse files
committed
CSHARP-1445: Added support for injecting arbitrary FilterDefinitions.
1 parent e446cc9 commit bcd3d96

File tree

12 files changed

+171
-1
lines changed

12 files changed

+171
-1
lines changed

src/MongoDB.Driver/Linq/Expressions/ExtensionExpressionType.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ internal enum ExtensionExpressionType
4646
FieldAsDocument,
4747
Field,
4848
GroupingKey,
49+
InjectedFilter,
4950

5051
// Temporary
5152
Correlated,

src/MongoDB.Driver/Linq/Expressions/ExtensionExpressionVisitor.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ protected internal virtual Expression VisitCollection(CollectionExpression node)
4343
return node;
4444
}
4545

46+
internal Expression VisitInjectedFilter(InjectedFilterExpression node)
47+
{
48+
return node;
49+
}
50+
4651
protected internal virtual Expression VisitConcat(ConcatExpression node)
4752
{
4853
return node.Update(
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/* Copyright 2016 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;
19+
using MongoDB.Bson.Serialization;
20+
using MongoDB.Driver.Core.Misc;
21+
22+
namespace MongoDB.Driver.Linq.Expressions
23+
{
24+
internal sealed class InjectedFilterExpression : ExtensionExpression
25+
{
26+
private readonly BsonDocument _filter;
27+
28+
public InjectedFilterExpression(BsonDocument filter)
29+
{
30+
_filter = Ensure.IsNotNull(filter, nameof(filter));
31+
}
32+
33+
public override ExtensionExpressionType ExtensionType => ExtensionExpressionType.InjectedFilter;
34+
35+
public BsonDocument Filter => _filter;
36+
37+
public override Type Type => typeof(bool);
38+
39+
public override string ToString() => _filter.ToString();
40+
41+
protected internal override Expression Accept(ExtensionExpressionVisitor visitor)
42+
{
43+
return visitor.VisitInjectedFilter(this);
44+
}
45+
}
46+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/* Copyright 2016 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.Collections.Generic;
18+
using System.Linq;
19+
using System.Text;
20+
using System.Threading.Tasks;
21+
22+
namespace MongoDB.Driver.Linq
23+
{
24+
/// <summary>
25+
/// This static class holds methods that can be used to express MongoDB specific operations in LINQ queries.
26+
/// </summary>
27+
public static class LinqExtensions
28+
{
29+
/// <summary>
30+
/// Injects a low level FilterDefinition{T} into a LINQ where clause. Can only be used in LINQ queries.
31+
/// </summary>
32+
/// <typeparam name="T"></typeparam>
33+
/// <param name="filter">The filter.</param>
34+
/// <returns>
35+
/// Throws an InvalidOperationException if called.
36+
/// </returns>
37+
public static bool Inject<T>(this FilterDefinition<T> filter)
38+
{
39+
throw new InvalidOperationException("The LinqExtensions.Inject method is only intended to be used in LINQ Where clauses.");
40+
}
41+
}
42+
}

src/MongoDB.Driver/Linq/Processors/EmbeddedPipeline/EmbeddedPipelineBindingContext.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ public EmbeddedPipelineBindingContext(IBindingContext parent)
3838
_memberMapping = new Dictionary<MemberInfo, Expression>();
3939
}
4040

41+
public IBsonSerializerRegistry SerializerRegistry
42+
{
43+
get { return _parent.SerializerRegistry; }
44+
}
45+
4146
public void AddCorrelatingId(Expression node, Guid correlatingId)
4247
{
4348
_correlationMapping.Add(node, correlatingId);

src/MongoDB.Driver/Linq/Processors/IBindingContext.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ namespace MongoDB.Driver.Linq.Processors
2222
{
2323
internal interface IBindingContext
2424
{
25+
IBsonSerializerRegistry SerializerRegistry { get; }
26+
2527
void AddCorrelatingId(Expression node, Guid correlatingId);
2628

2729
void AddExpressionMapping(Expression original, Expression replacement);

src/MongoDB.Driver/Linq/Processors/PartialEvaluator.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,14 @@ protected override Expression VisitMethodCall(MethodCallExpression node)
169169
}
170170
}
171171

172-
return base.VisitMethodCall(node);
172+
node = (MethodCallExpression)base.VisitMethodCall(node);
173+
if (node.Object == null && node.Method.DeclaringType == typeof(LinqExtensions) &&
174+
node.Method.Name == "Inject")
175+
{
176+
_isBlocked = true;
177+
}
178+
179+
return node;
173180
}
174181

175182
protected override Expression VisitParameter(ParameterExpression node)

src/MongoDB.Driver/Linq/Processors/Pipeline/PipelineBindingContext.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ public PipelineBindingContext(IBsonSerializerRegistry serializerRegistry)
3838
_memberMapping = new Dictionary<MemberInfo, Expression>();
3939
}
4040

41+
public IBsonSerializerRegistry SerializerRegistry
42+
{
43+
get { return _serializerRegistry; }
44+
}
45+
4146
public void AddCorrelatingId(Expression node, Guid correlatingId)
4247
{
4348
Ensure.IsNotNull(node, nameof(node));

src/MongoDB.Driver/Linq/Processors/SerializationBinder.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
using System.Linq;
1919
using System.Linq.Expressions;
2020
using System.Reflection;
21+
using MongoDB.Bson;
2122
using MongoDB.Bson.Serialization;
2223
using MongoDB.Driver.Linq.Expressions;
2324
using MongoDB.Driver.Linq.Processors.EmbeddedPipeline;
@@ -169,6 +170,8 @@ protected override Expression VisitMethodCall(MethodCallExpression node)
169170
return BindElementAt(node);
170171
case "get_Item":
171172
return BindGetItem(node);
173+
case "Inject":
174+
return BindInject(node);
172175
}
173176

174177
// Select and SelectMany are the only supported client-side projection operators
@@ -341,6 +344,28 @@ private Expression BindGetItem(MethodCallExpression node)
341344
return node; // return the original node because we can't translate this expression.
342345
}
343346

347+
348+
private Expression BindInject(MethodCallExpression node)
349+
{
350+
if (node.Method.DeclaringType == typeof(LinqExtensions))
351+
{
352+
var arg = node.Arguments[0] as ConstantExpression;
353+
if (arg != null)
354+
{
355+
var docType = node.Method.GetGenericArguments()[0];
356+
var serializer = _bindingContext.GetSerializer(node.Method.GetGenericArguments()[0], arg);
357+
var renderedFilter = (BsonDocument)typeof(FilterDefinition<>).MakeGenericType(docType)
358+
.GetTypeInfo()
359+
.GetMethod("Render")
360+
.Invoke(arg.Value, new object[] { serializer, _bindingContext.SerializerRegistry });
361+
362+
return new InjectedFilterExpression(renderedFilter);
363+
}
364+
}
365+
366+
return node;
367+
}
368+
344369
private Expression BindClientSideProjector(MethodCallExpression node)
345370
{
346371
var arguments = new List<Expression>();

src/MongoDB.Driver/Linq/Translators/PredicateTranslator.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,8 @@ private FilterDefinition<BsonDocument> Translate(Expression node)
117117
filter = TranslateBoolean(mongoExpression);
118118
}
119119
break;
120+
case ExtensionExpressionType.InjectedFilter:
121+
return TranslateInjectedFilter((InjectedFilterExpression)node);
120122
case ExtensionExpressionType.Pipeline:
121123
filter = TranslatePipeline((PipelineExpression)node);
122124
break;
@@ -663,6 +665,11 @@ private FilterDefinition<BsonDocument> TranslateIn(MethodCallExpression methodCa
663665
return null;
664666
}
665667

668+
private FilterDefinition<BsonDocument> TranslateInjectedFilter(InjectedFilterExpression node)
669+
{
670+
return new BsonDocumentFilterDefinition<BsonDocument>(node.Filter);
671+
}
672+
666673
private FilterDefinition<BsonDocument> TranslateIsMatch(MethodCallExpression methodCallExpression)
667674
{
668675
if (methodCallExpression.Method.DeclaringType == typeof(Regex))

src/MongoDB.Driver/MongoDB.Driver.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@
8888
<Compile Include="Linq\ExpressionHelper.cs" />
8989
<Compile Include="Linq\Expressions\AccumulatorExpression.cs" />
9090
<Compile Include="Linq\Expressions\AccumulatorType.cs" />
91+
<Compile Include="Linq\Expressions\InjectedFilterExpression.cs" />
9192
<Compile Include="Linq\Expressions\CollectionExpression.cs" />
9293
<Compile Include="Linq\Expressions\CorrelatedExpression.cs" />
9394
<Compile Include="Linq\Expressions\ConcatExpression.cs" />
@@ -143,6 +144,7 @@
143144
<Compile Include="Linq\IMongoQueryable.cs" />
144145
<Compile Include="Linq\IMongoQueryProvider.cs" />
145146
<Compile Include="Linq\IResultTransformer.cs" />
147+
<Compile Include="Linq\LinqExtensions.cs" />
146148
<Compile Include="Linq\MethodHelper.cs" />
147149
<Compile Include="Linq\MongoEnumerable.cs" />
148150
<Compile Include="Linq\MongoQueryable.cs" />

tests/MongoDB.Driver.Tests/Linq/Translators/PredicateTranslatorTests.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
using MongoDB.Bson.TestHelpers.XunitExtensions;
2626
using MongoDB.Driver.Core;
2727
using MongoDB.Driver.Core.TestHelpers.XunitExtensions;
28+
using MongoDB.Driver.Linq;
2829
using MongoDB.Driver.Linq.Translators;
2930
using Xunit;
3031

@@ -775,6 +776,28 @@ public void Binding_through_an_unnecessary_conversion_with_a_builder()
775776
root.A.Should().Be("Awesome");
776777
}
777778

779+
[Fact]
780+
public void Injecting_a_filter()
781+
{
782+
var filter = Builders<Root>.Filter.Eq(x => x.B, "Balloon");
783+
var root = __collection.FindSync(x => filter.Inject()).Single();
784+
785+
root.Should().NotBeNull();
786+
root.A.Should().Be("Awesome");
787+
root.B.Should().Be("Balloon");
788+
}
789+
790+
[Fact]
791+
public void Injecting_a_filter_with_a_conjunction()
792+
{
793+
var filter = Builders<Root>.Filter.Eq(x => x.B, "Balloon");
794+
var root = __collection.FindSync(x => x.A == "Awesome" && filter.Inject()).Single();
795+
796+
root.Should().NotBeNull();
797+
root.A.Should().Be("Awesome");
798+
root.B.Should().Be("Balloon");
799+
}
800+
778801
private T FindFirstOrDefault<T>(IMongoCollection<T> collection, int id) where T : IRoot
779802
{
780803
return collection.FindSync(x => x.Id == id).FirstOrDefault();

0 commit comments

Comments
 (0)