Skip to content

Commit 1983d9b

Browse files
CSHARP-4203: IFindFluentExtensions for Any and AnyAsync (#817)
Co-authored-by: Matthew DeJonge <[email protected]>
1 parent 66662c8 commit 1983d9b

File tree

2 files changed

+91
-0
lines changed

2 files changed

+91
-0
lines changed

src/MongoDB.Driver/IFindFluentExtensions.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,38 @@ public static IOrderedFindFluent<TDocument, TProjection> ThenByDescending<TDocum
140140
return find;
141141
}
142142

143+
/// <summary>
144+
/// Determine if there are any results.
145+
/// </summary>
146+
/// <typeparam name="TDocument">The type of the document.</typeparam>
147+
/// <typeparam name="TProjection">The type of the projection (same as TDocument if there is no projection).</typeparam>
148+
/// <param name="find">The fluent find.</param>
149+
/// <param name="cancellationToken">The cancellation token.</param>
150+
/// <returns>True if there is at least one document.</returns>
151+
public static bool Any<TDocument, TProjection>(this IFindFluent<TDocument, TProjection> find, CancellationToken cancellationToken = default(CancellationToken))
152+
{
153+
Ensure.IsNotNull(find, nameof(find));
154+
155+
var projection = new BsonDocumentProjectionDefinition<TDocument, BsonDocument>(new BsonDocument("_id", 1));
156+
return IAsyncCursorSourceExtensions.Any(find.Project(projection).Limit(1), cancellationToken);
157+
}
158+
159+
/// <summary>
160+
/// Determine if there are any results.
161+
/// </summary>
162+
/// <typeparam name="TDocument">The type of the document.</typeparam>
163+
/// <typeparam name="TProjection">The type of the projection (same as TDocument if there is no projection).</typeparam>
164+
/// <param name="find">The fluent find.</param>
165+
/// <param name="cancellationToken">The cancellation token.</param>
166+
/// <returns>A Task whose result is true if there is at least one document.</returns>
167+
public static Task<bool> AnyAsync<TDocument, TProjection>(this IFindFluent<TDocument, TProjection> find, CancellationToken cancellationToken = default(CancellationToken))
168+
{
169+
Ensure.IsNotNull(find, nameof(find));
170+
171+
var projection = new BsonDocumentProjectionDefinition<TDocument, BsonDocument>(new BsonDocument("_id", 1));
172+
return IAsyncCursorSourceExtensions.AnyAsync(find.Project(projection).Limit(1), cancellationToken);
173+
}
174+
143175
/// <summary>
144176
/// Get the first result.
145177
/// </summary>

tests/MongoDB.Driver.Tests/IFindFluentExtensionsTests.cs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
*/
1515

1616
using System;
17+
using System.Linq;
1718
using System.Threading;
1819
using System.Threading.Tasks;
1920
using FluentAssertions;
@@ -28,6 +29,64 @@ namespace MongoDB.Driver.Tests
2829
public class IFindFluentExtensionsTests
2930
{
3031
// public methods
32+
[Theory]
33+
[ParameterAttributeData]
34+
public void Any_should_add_projection_and_limit_and_return_expected_result(
35+
[Values(0, 1, 2)] int count,
36+
[Values(false, true)] bool async)
37+
{
38+
var expectedResult = count > 0;
39+
40+
var mockSubject1 = new Mock<IFindFluent<Person, Person>>();
41+
var mockSubject2 = new Mock<IFindFluent<Person, BsonDocument>>();
42+
var mockSubject3 = new Mock<IFindFluent<Person, BsonDocument>>();
43+
var mockCursor = new Mock<IAsyncCursor<BsonDocument>>();
44+
var firstBatch = Enumerable.Range(0, count).Select(i => new BsonDocument("_id", i)).ToArray();
45+
var cancellationToken = new CancellationTokenSource().Token;
46+
47+
mockSubject1.Setup(s => s.Project(It.Is<BsonDocumentProjectionDefinition<Person, BsonDocument>>(p => p.Document["_id"].AsInt32 == 1))).Returns(mockSubject2.Object);
48+
mockSubject2.Setup(s => s.Limit(1)).Returns(mockSubject3.Object);
49+
mockCursor.SetupGet(c => c.Current).Returns(firstBatch);
50+
51+
bool result;
52+
if (async)
53+
{
54+
mockSubject3.Setup(s => s.ToCursorAsync(cancellationToken)).Returns(Task.FromResult(mockCursor.Object));
55+
mockCursor.Setup(c => c.MoveNextAsync(cancellationToken)).Returns(Task.FromResult(true));
56+
57+
result = mockSubject1.Object.AnyAsync(cancellationToken).GetAwaiter().GetResult();
58+
}
59+
else
60+
{
61+
mockSubject3.Setup(s => s.ToCursor(cancellationToken)).Returns(mockCursor.Object);
62+
mockCursor.Setup(c => c.MoveNext(cancellationToken)).Returns(true);
63+
64+
result = mockSubject1.Object.Any(cancellationToken);
65+
}
66+
67+
result.Should().Be(expectedResult);
68+
}
69+
70+
[Theory]
71+
[ParameterAttributeData]
72+
public void Any_should_throw_when_find_is_null(
73+
[Values(false, true)] bool async)
74+
{
75+
IFindFluent<Person, Person> subject = null;
76+
77+
Action action;
78+
if (async)
79+
{
80+
action = () => subject.AnyAsync().GetAwaiter().GetResult();
81+
}
82+
else
83+
{
84+
action = () => subject.Any();
85+
}
86+
87+
action.ShouldThrow<ArgumentNullException>().And.ParamName.Should().Be("find");
88+
}
89+
3190
[Theory]
3291
[ParameterAttributeData]
3392
public void First_should_add_limit_and_call_ToCursor(

0 commit comments

Comments
 (0)