Skip to content

Commit 38ed158

Browse files
Einar Egilssoncraiggwilson
authored andcommitted
Added .WithIndex method to Linq queries for index hinting.
.WithIndex can take a string or a BsonDocument as an index hint. It's an extension method on IQueryable<T> and returns IQueryable<T>. Example: var results = _collection.AsQueryable<T>.WithIndex("foo").Where(x=>x.Name == 1).ToList()
1 parent 22cbd96 commit 38ed158

File tree

5 files changed

+340
-29
lines changed

5 files changed

+340
-29
lines changed

MongoDB.Driver/Linq/LinqToMongo.cs

Lines changed: 58 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
using System;
1717
using System.Collections.Generic;
1818
using System.Linq;
19+
using System.Linq.Expressions;
20+
using System.Reflection;
1921
using MongoDB.Bson;
2022

2123
namespace MongoDB.Driver.Linq
@@ -25,6 +27,7 @@ namespace MongoDB.Driver.Linq
2527
/// </summary>
2628
public static class LinqToMongo
2729
{
30+
// public static methods
2831
/// <summary>
2932
/// Determines whether a sequence contains all of the specified values.
3033
/// </summary>
@@ -49,29 +52,6 @@ public static bool ContainsAny<TSource>(this IEnumerable<TSource> source, IEnume
4952
return source.Any(s => values.Contains(s));
5053
}
5154

52-
53-
/// <summary>
54-
/// Determines whether a specified value is contained in a sequence.
55-
/// </summary>
56-
/// <typeparam name="TSource">The type of the elements of source.</typeparam>
57-
/// <param name="value">The value to locate in the sequence.</param>
58-
/// <param name="source">A sequence in which to locate the values.</param>
59-
/// <returns>True if the value is contained in the sequence.</returns>
60-
public static bool In<TSource>(this TSource value, IEnumerable<TSource> source)
61-
{
62-
return source.Contains(value);
63-
}
64-
65-
/// <summary>
66-
/// Injects a low level IMongoQuery into a LINQ where clause. Can only be used in LINQ queries.
67-
/// </summary>
68-
/// <param name="query">The low level query.</param>
69-
/// <returns>Throws an InvalidOperationException if called.</returns>
70-
public static bool Inject(this IMongoQuery query)
71-
{
72-
throw new InvalidOperationException("The LinqToMongo.Inject method is only intended to be used in LINQ Where clauses.");
73-
}
74-
7555
/// <summary>
7656
/// Returns an explanation of how the query was executed (instead of the results).
7757
/// </summary>
@@ -104,5 +84,60 @@ public static BsonDocument Explain<T>(this IQueryable<T> query, bool verbose)
10484
}
10585
return projector.Cursor.Explain(verbose);
10686
}
87+
88+
/// <summary>
89+
/// Determines whether a specified value is contained in a sequence.
90+
/// </summary>
91+
/// <typeparam name="TSource">The type of the elements of source.</typeparam>
92+
/// <param name="value">The value to locate in the sequence.</param>
93+
/// <param name="source">A sequence in which to locate the values.</param>
94+
/// <returns>True if the value is contained in the sequence.</returns>
95+
public static bool In<TSource>(this TSource value, IEnumerable<TSource> source)
96+
{
97+
return source.Contains(value);
98+
}
99+
100+
/// <summary>
101+
/// Injects a low level IMongoQuery into a LINQ where clause. Can only be used in LINQ queries.
102+
/// </summary>
103+
/// <param name="query">The low level query.</param>
104+
/// <returns>Throws an InvalidOperationException if called.</returns>
105+
public static bool Inject(this IMongoQuery query)
106+
{
107+
throw new InvalidOperationException("The LinqToMongo.Inject method is only intended to be used in LINQ Where clauses.");
108+
}
109+
110+
/// <summary>
111+
/// Sets an index hint on the query that's being built.
112+
/// </summary>
113+
/// <typeparam name="TSource">The type of the elements of source.</typeparam>
114+
/// <param name="query">The query being built.</param>
115+
/// <param name="indexName">The name of the index to use.</param>
116+
/// <returns>New query where the expression includes a WithIndex method call.</returns>
117+
public static IQueryable<TSource> WithIndex<TSource>(this IQueryable<TSource> query, string indexName)
118+
{
119+
return WithIndex(query, (BsonValue)indexName);
120+
}
121+
122+
/// <summary>
123+
/// Sets an index hint on the query that's being built.
124+
/// </summary>
125+
/// <typeparam name="TSource">The type of the elements of source.</typeparam>
126+
/// <param name="query">The query being built.</param>
127+
/// <param name="indexHint">Hint for what index to use.</param>
128+
/// <returns>New query where the expression includes a WithIndex method call.</returns>
129+
public static IQueryable<TSource> WithIndex<TSource>(this IQueryable<TSource> query, BsonDocument indexHint)
130+
{
131+
return WithIndex(query, (BsonValue)indexHint);
132+
}
133+
134+
// private static methods
135+
private static IQueryable<TSource> WithIndex<TSource>(IQueryable<TSource> query, BsonValue index)
136+
{
137+
var method = ((MethodInfo)MethodBase.GetCurrentMethod()).MakeGenericMethod(typeof(TSource));
138+
var args = new[] { query.Expression, Expression.Constant(index) };
139+
var expression = Expression.Call(null, method, args);
140+
return query.Provider.CreateQuery<TSource>(expression);
141+
}
107142
}
108143
}

MongoDB.Driver/Linq/Translators/SelectQuery.cs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ public class SelectQuery : TranslatedQuery
3636
private LambdaExpression _where;
3737
private Type _ofType;
3838
private List<OrderByClause> _orderBy;
39+
private BsonValue _indexHint;
3940
private LambdaExpression _projection;
4041
private int? _skip;
4142
private int? _take;
@@ -96,6 +97,14 @@ public int? Take
9697
get { return _take; }
9798
}
9899

100+
/// <summary>
101+
/// Gets the BsonValue (string or document) that defines which index to use (or null if not specified);
102+
/// </summary>
103+
public BsonValue IndexHint
104+
{
105+
get { return _indexHint; }
106+
}
107+
99108
/// <summary>
100109
/// Gets the LambdaExpression that defines the where clause (or null if not specified).
101110
/// </summary>
@@ -168,6 +177,22 @@ public override object Execute()
168177
cursor.SetLimit(_take.Value);
169178
}
170179

180+
if (_indexHint != null)
181+
{
182+
if (_indexHint.IsString)
183+
{
184+
cursor.SetHint(_indexHint.AsString);
185+
}
186+
else if (_indexHint.IsBsonDocument)
187+
{
188+
cursor.SetHint(_indexHint.AsBsonDocument);
189+
}
190+
else
191+
{
192+
throw new NotSupportedException("Index hints must be strings or documents");
193+
}
194+
}
195+
171196
var projection = _projection;
172197
if (_ofType != null)
173198
{
@@ -434,6 +459,12 @@ private void TranslateDistinct(MethodCallExpression methodCallExpression)
434459
throw new NotSupportedException(message);
435460
}
436461

462+
if (_indexHint != null)
463+
{
464+
var message = "Distinct cannot be used together with WithIndex.";
465+
throw new NotSupportedException(message);
466+
}
467+
437468
_distinct = _projection;
438469
_projection = null;
439470
}
@@ -677,6 +708,9 @@ private void TranslateMethodCall(MethodCallExpression methodCallExpression)
677708
case "ThenByDescending":
678709
TranslateThenBy(methodCallExpression);
679710
break;
711+
case "WithIndex":
712+
TranslateWithIndex(methodCallExpression);
713+
break;
680714
case "Where":
681715
TranslateWhere(methodCallExpression);
682716
break;
@@ -863,6 +897,47 @@ private void TranslateThenBy(MethodCallExpression methodCallExpression)
863897
_orderBy.Add(clause);
864898
}
865899

900+
private void TranslateWithIndex(MethodCallExpression methodCallExpression)
901+
{
902+
if (methodCallExpression.Arguments.Count != 2)
903+
{
904+
throw new ArgumentOutOfRangeException("methodCallExpression");
905+
}
906+
907+
var method = methodCallExpression.Method;
908+
909+
if (method.DeclaringType != typeof(LinqToMongo))
910+
{
911+
var message = string.Format("WithIndex method of class {0} is not supported.", BsonUtils.GetFriendlyTypeName(method.DeclaringType));
912+
throw new NotSupportedException(message);
913+
}
914+
915+
if (_indexHint != null)
916+
{
917+
throw new NotSupportedException("Only one index can be used for each query");
918+
}
919+
920+
if (_distinct != null)
921+
{
922+
var message = "WithIndex cannot be used together with Distinct.";
923+
throw new NotSupportedException(message);
924+
}
925+
926+
Expression expression = methodCallExpression.Arguments[1];
927+
if (expression.Type != typeof(BsonString) && expression.Type != typeof(BsonDocument))
928+
{
929+
throw new ArgumentOutOfRangeException("methodCallExpression", "Expected an Expression of Type BsonString or BsonDocument.");
930+
}
931+
932+
var constantExpression = expression as ConstantExpression;
933+
if (constantExpression == null)
934+
{
935+
throw new ArgumentOutOfRangeException("methodCallExpression", "Expected a ConstantExpression.");
936+
}
937+
938+
_indexHint = (BsonValue)constantExpression.Value;
939+
}
940+
866941
private void TranslateWhere(MethodCallExpression methodCallExpression)
867942
{
868943
if (methodCallExpression.Arguments.Count != 2)

DriverUnitTests/Linq/ExplainTests.cs renamed to MongoDB.DriverUnitTests/Linq/ExplainTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
???/* Copyright 2010-2012 10gen Inc.
1+
/* Copyright 2010-2012 10gen Inc.
22
*
33
* Licensed under the Apache License, Version 2.0 (the "License");
44
* you may not use this file except in compliance with the License.

0 commit comments

Comments
 (0)