Skip to content

Commit ef72b30

Browse files
committed
CSHARP-1401: added preserveNullAndEmptyArrays support to $unwind.
1 parent 7f1b6ab commit ef72b30

File tree

7 files changed

+149
-14
lines changed

7 files changed

+149
-14
lines changed

src/MongoDB.Driver.Tests/IAggregateFluentExtensionsTests.cs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,13 +202,35 @@ public void Unwind_should_generate_the_correct_unwind()
202202
public void Unwind_to_new_result_with_a_serializer_should_generate_the_correct_unwind()
203203
{
204204
var subject = CreateSubject()
205-
.Unwind("Age", BsonDocumentSerializer.Instance);
205+
.Unwind("Age", new AggregateUnwindOptions<BsonDocument> { ResultSerializer = BsonDocumentSerializer.Instance });
206206

207207
var expectedUnwind = BsonDocument.Parse("{$unwind: '$Age'}");
208208

209209
AssertLast(subject, expectedUnwind);
210210
}
211211

212+
[Test]
213+
public void Unwind_with_options_where_no_options_are_set()
214+
{
215+
var subject = CreateSubject()
216+
.Unwind("Age", new AggregateUnwindOptions<BsonDocument>());
217+
218+
var expectedUnwind = BsonDocument.Parse("{$unwind: '$Age'}");
219+
220+
AssertLast(subject, expectedUnwind);
221+
}
222+
223+
[Test]
224+
public void Unwind_with_options_with_preserveNullAndEmptyArrays_set()
225+
{
226+
var subject = CreateSubject()
227+
.Unwind("Age", new AggregateUnwindOptions<BsonDocument> { PreserveNullAndEmptyArrays = true });
228+
229+
var expectedUnwind = BsonDocument.Parse("{$unwind: { path: '$Age', preserveNullAndEmptyArrays: true } }");
230+
231+
AssertLast(subject, expectedUnwind);
232+
}
233+
212234
private void AssertLast<TDocument>(IAggregateFluent<TDocument> fluent, BsonDocument expectedLast)
213235
{
214236
var pipeline = new PipelineStagePipelineDefinition<Person, TDocument>(fluent.Stages);

src/MongoDB.Driver/AggregateFluent.cs

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -192,14 +192,33 @@ public override IAggregateFluent<TResult> Sort(SortDefinition<TResult> sort)
192192

193193
public override IAggregateFluent<TNewResult> Unwind<TNewResult>(FieldDefinition<TResult> field, IBsonSerializer<TNewResult> newResultSerializer)
194194
{
195+
return Unwind(field, new AggregateUnwindOptions<TNewResult> { ResultSerializer = newResultSerializer });
196+
}
197+
198+
public override IAggregateFluent<TNewResult> Unwind<TNewResult>(FieldDefinition<TResult> field, AggregateUnwindOptions<TNewResult> options)
199+
{
200+
options = options ?? new AggregateUnwindOptions<TNewResult>();
201+
195202
const string operatorName = "$unwind";
196203
var stage = new DelegatedPipelineStageDefinition<TResult, TNewResult>(
197204
operatorName,
198-
(s, sr) => new RenderedPipelineStageDefinition<TNewResult>(
199-
operatorName, new BsonDocument(
205+
(s, sr) =>
206+
{
207+
var fieldName = "$" + field.Render(s, sr).FieldName;
208+
BsonValue value = fieldName;
209+
if (options.PreserveNullAndEmptyArrays.HasValue)
210+
{
211+
value = new BsonDocument
212+
{
213+
{ "path", fieldName },
214+
{ "preserveNullAndEmptyArrays", options.PreserveNullAndEmptyArrays, options.PreserveNullAndEmptyArrays.HasValue }
215+
};
216+
}
217+
return new RenderedPipelineStageDefinition<TNewResult>(
200218
operatorName,
201-
"$" + field.Render(s, sr).FieldName),
202-
newResultSerializer ?? (s as IBsonSerializer<TNewResult>) ?? sr.GetSerializer<TNewResult>()));
219+
new BsonDocument(operatorName, value),
220+
options.ResultSerializer ?? (s as IBsonSerializer<TNewResult>) ?? sr.GetSerializer<TNewResult>());
221+
});
203222

204223
return AppendStage<TNewResult>(stage);
205224
}

src/MongoDB.Driver/AggregateFluentBase.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,10 @@ public virtual IAggregateFluent<TNewResult> Lookup<TNewResult>(string from, Fiel
5656
public abstract IAggregateFluent<TResult> Match(FilterDefinition<TResult> filter);
5757

5858
/// <inheritdoc />
59-
public abstract IAggregateFluent<TNewResult> OfType<TNewResult>(IBsonSerializer<TNewResult> newResultSerializer = null) where TNewResult : TResult;
59+
public abstract IAggregateFluent<TNewResult> OfType<TNewResult>(IBsonSerializer<TNewResult> newResultSerializer) where TNewResult : TResult;
6060

6161
/// <inheritdoc />
62-
public abstract Task<IAsyncCursor<TResult>> OutAsync(string collectionName, CancellationToken cancellationToken = default(CancellationToken));
62+
public abstract Task<IAsyncCursor<TResult>> OutAsync(string collectionName, CancellationToken cancellationToken);
6363

6464
/// <inheritdoc />
6565
public abstract IAggregateFluent<TNewResult> Project<TNewResult>(ProjectionDefinition<TResult, TNewResult> projection);
@@ -71,7 +71,13 @@ public virtual IAggregateFluent<TNewResult> Lookup<TNewResult>(string from, Fiel
7171
public abstract IAggregateFluent<TResult> Sort(SortDefinition<TResult> sort);
7272

7373
/// <inheritdoc />
74-
public abstract IAggregateFluent<TNewResult> Unwind<TNewResult>(FieldDefinition<TResult> field, IBsonSerializer<TNewResult> newResultSerializer = null);
74+
public abstract IAggregateFluent<TNewResult> Unwind<TNewResult>(FieldDefinition<TResult> field, IBsonSerializer<TNewResult> newResultSerializer);
75+
76+
/// <inheritdoc />
77+
public virtual IAggregateFluent<TNewResult> Unwind<TNewResult>(FieldDefinition<TResult> field, AggregateUnwindOptions<TNewResult> options)
78+
{
79+
throw new NotImplementedException();
80+
}
7581

7682
/// <inheritdoc />
7783
public abstract Task<IAsyncCursor<TResult>> ToCursorAsync(CancellationToken cancellationToken);
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/* Copyright 2015 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 MongoDB.Bson.Serialization;
17+
18+
namespace MongoDB.Driver
19+
{
20+
/// <summary>
21+
/// Options for the $unwind aggregation stage.
22+
/// </summary>
23+
public class AggregateUnwindOptions<TResult>
24+
{
25+
private bool? _preserveNullAndEmptyArrays;
26+
private IBsonSerializer<TResult> _resultSerializer;
27+
28+
/// <summary>
29+
/// Gets or sets whether to preserve null and empty arrays.
30+
/// </summary>
31+
public bool? PreserveNullAndEmptyArrays
32+
{
33+
get { return _preserveNullAndEmptyArrays; }
34+
set { _preserveNullAndEmptyArrays = value; }
35+
}
36+
37+
/// <summary>
38+
/// Gets or sets the result serializer.
39+
/// </summary>
40+
public IBsonSerializer<TResult> ResultSerializer
41+
{
42+
get { return _resultSerializer; }
43+
set { _resultSerializer = value; }
44+
}
45+
}
46+
}

src/MongoDB.Driver/IAggregateFluent.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
* limitations under the License.
1414
*/
1515

16+
using System;
1617
using System.Collections.Generic;
1718
using System.Threading;
1819
using System.Threading.Tasks;
@@ -140,9 +141,20 @@ public interface IAggregateFluent<TResult> : IAsyncCursorSource<TResult>
140141
/// <returns>
141142
/// The fluent aggregate interface.
142143
/// </returns>
143-
IAggregateFluent<TNewResult> Unwind<TNewResult>(FieldDefinition<TResult> field, IBsonSerializer<TNewResult> newResultSerializer = null);
144+
[Obsolete("Use the Unwind overload which takes an options parameter.")]
145+
IAggregateFluent<TNewResult> Unwind<TNewResult>(FieldDefinition<TResult> field, IBsonSerializer<TNewResult> newResultSerializer);
146+
147+
/// <summary>
148+
/// Appends an unwind stage to the pipeline.
149+
/// </summary>
150+
/// <typeparam name="TNewResult">The type of the new result.</typeparam>
151+
/// <param name="field">The field.</param>
152+
/// <param name="options">The options.</param>
153+
/// The fluent aggregate interface.
154+
IAggregateFluent<TNewResult> Unwind<TNewResult>(FieldDefinition<TResult> field, AggregateUnwindOptions<TNewResult> options = null);
144155
}
145156

157+
146158
/// <summary>
147159
/// Fluent interface for aggregate.
148160
/// </summary>

src/MongoDB.Driver/IAggregateFluentExtensions.cs

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
using System.Threading.Tasks;
2121
using MongoDB.Bson;
2222
using MongoDB.Bson.Serialization;
23+
using MongoDB.Bson.Serialization.Serializers;
2324
using MongoDB.Driver.Core.Misc;
2425
using MongoDB.Driver.Linq.Translators;
2526

@@ -232,7 +233,9 @@ public static IAggregateFluent<BsonDocument> Unwind<TResult>(this IAggregateFlue
232233
Ensure.IsNotNull(aggregate, nameof(aggregate));
233234
Ensure.IsNotNull(field, nameof(field));
234235

235-
return aggregate.Unwind<BsonDocument>(field);
236+
return aggregate.Unwind(
237+
field,
238+
new AggregateUnwindOptions<BsonDocument>());
236239
}
237240

238241
/// <summary>
@@ -249,7 +252,9 @@ public static IAggregateFluent<BsonDocument> Unwind<TResult>(this IAggregateFlue
249252
Ensure.IsNotNull(aggregate, nameof(aggregate));
250253
Ensure.IsNotNull(field, nameof(field));
251254

252-
return aggregate.Unwind<BsonDocument>(new ExpressionFieldDefinition<TResult>(field));
255+
return aggregate.Unwind(
256+
new ExpressionFieldDefinition<TResult>(field),
257+
new AggregateUnwindOptions<BsonDocument>());
253258
}
254259

255260
/// <summary>
@@ -263,12 +268,36 @@ public static IAggregateFluent<BsonDocument> Unwind<TResult>(this IAggregateFlue
263268
/// <returns>
264269
/// The fluent aggregate interface.
265270
/// </returns>
266-
public static IAggregateFluent<TNewResult> Unwind<TResult, TNewResult>(this IAggregateFluent<TResult> aggregate, Expression<Func<TResult, object>> field, IBsonSerializer<TNewResult> newResultSerializer = null)
271+
[Obsolete("Use the Unwind overload which takes an options parameter.")]
272+
public static IAggregateFluent<TNewResult> Unwind<TResult, TNewResult>(this IAggregateFluent<TResult> aggregate, Expression<Func<TResult, object>> field, IBsonSerializer<TNewResult> newResultSerializer)
267273
{
268274
Ensure.IsNotNull(aggregate, nameof(aggregate));
269275
Ensure.IsNotNull(field, nameof(field));
270276

271-
return aggregate.Unwind<TNewResult>(new ExpressionFieldDefinition<TResult>(field), newResultSerializer);
277+
return aggregate.Unwind(
278+
new ExpressionFieldDefinition<TResult>(field),
279+
new AggregateUnwindOptions<TNewResult> { ResultSerializer = newResultSerializer });
280+
}
281+
282+
/// <summary>
283+
/// Appends an unwind stage to the pipeline.
284+
/// </summary>
285+
/// <typeparam name="TResult">The type of the result.</typeparam>
286+
/// <typeparam name="TNewResult">The type of the new result.</typeparam>
287+
/// <param name="aggregate">The aggregate.</param>
288+
/// <param name="field">The field to unwind.</param>
289+
/// <param name="options">The options.</param>
290+
/// <returns>
291+
/// The fluent aggregate interface.
292+
/// </returns>
293+
public static IAggregateFluent<TNewResult> Unwind<TResult, TNewResult>(this IAggregateFluent<TResult> aggregate, Expression<Func<TResult, object>> field, AggregateUnwindOptions<TNewResult> options = null)
294+
{
295+
Ensure.IsNotNull(aggregate, nameof(aggregate));
296+
Ensure.IsNotNull(field, nameof(field));
297+
298+
return aggregate.Unwind(
299+
new ExpressionFieldDefinition<TResult>(field),
300+
options);
272301
}
273302

274303
/// <summary>

src/MongoDB.Driver/MongoDB.Driver.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,7 @@
268268
<Compile Include="Support\ReflectionExtensions.cs" />
269269
<Compile Include="Sync\AsyncCursorEnumerableAdapter.cs" />
270270
<Compile Include="Sync\AsyncCursorEnumeratorAdapter.cs" />
271+
<Compile Include="AggregateUnwindOptions.cs" />
271272
<Compile Include="UpdateDefinitionBuilder.cs" />
272273
<Compile Include="OfTypeMongoCollection.cs" />
273274
<Compile Include="IFilteredMongoCollection.cs" />
@@ -435,4 +436,4 @@
435436
<Target Name="AfterBuild">
436437
</Target>
437438
-->
438-
</Project>
439+
</Project>

0 commit comments

Comments
 (0)