Skip to content

Commit 6f50116

Browse files
CSHARP-2013: Support more expressive $lookup.
1 parent 9f5faf4 commit 6f50116

10 files changed

+860
-20
lines changed

src/MongoDB.Driver/AggregateFluent.cs

+14-2
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,13 @@
1313
* limitations under the License.
1414
*/
1515

16+
using MongoDB.Bson;
17+
using MongoDB.Bson.Serialization;
18+
using MongoDB.Driver.Core.Misc;
1619
using System.Collections.Generic;
1720
using System.Linq;
1821
using System.Threading;
1922
using System.Threading.Tasks;
20-
using MongoDB.Bson.Serialization;
21-
using MongoDB.Driver.Core.Misc;
2223

2324
namespace MongoDB.Driver
2425
{
@@ -146,6 +147,17 @@ public override IAggregateFluent<TNewResult> Lookup<TForeignDocument, TNewResult
146147
return WithPipeline(_pipeline.Lookup(foreignCollection, localField, foreignField, @as, options));
147148
}
148149

150+
public override IAggregateFluent<TNewResult> Lookup<TForeignDocument, TAsElement, TAs, TNewResult>(
151+
IMongoCollection<TForeignDocument> foreignCollection,
152+
BsonDocument let,
153+
PipelineDefinition<TForeignDocument, TAsElement> lookupPipeline,
154+
FieldDefinition<TNewResult, TAs> @as,
155+
AggregateLookupOptions<TForeignDocument, TNewResult> options = null)
156+
{
157+
Ensure.IsNotNull(foreignCollection, nameof(foreignCollection));
158+
return WithPipeline(_pipeline.Lookup(foreignCollection, let, lookupPipeline, @as));
159+
}
160+
149161
public override IAggregateFluent<TResult> Match(FilterDefinition<TResult> filter)
150162
{
151163
return WithPipeline(_pipeline.Match(filter));

src/MongoDB.Driver/AggregateFluentBase.cs

+12
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,18 @@ public virtual IAggregateFluent<TNewResult> Lookup<TForeignDocument, TNewResult>
131131
throw new NotImplementedException();
132132
}
133133

134+
/// <inheritdoc />
135+
public virtual IAggregateFluent<TNewResult> Lookup<TForeignDocument, TAsElement, TAs, TNewResult>(
136+
IMongoCollection<TForeignDocument> foreignCollection,
137+
BsonDocument let,
138+
PipelineDefinition<TForeignDocument, TAsElement> lookupPipeline,
139+
FieldDefinition<TNewResult, TAs> @as,
140+
AggregateLookupOptions<TForeignDocument, TNewResult> options = null)
141+
where TAs : IEnumerable<TAsElement>
142+
{
143+
throw new NotImplementedException();
144+
}
145+
134146
/// <inheritdoc />
135147
public abstract IAggregateFluent<TResult> Match(FilterDefinition<TResult> filter);
136148

src/MongoDB.Driver/IAggregateFluent.cs

+21
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,27 @@ IAggregateFluent<TNewResult> GraphLookup<TFrom, TConnectFrom, TConnectTo, TStart
206206
/// <returns>The fluent aggregate interface.</returns>
207207
IAggregateFluent<TNewResult> Lookup<TForeignDocument, TNewResult>(string foreignCollectionName, FieldDefinition<TResult> localField, FieldDefinition<TForeignDocument> foreignField, FieldDefinition<TNewResult> @as, AggregateLookupOptions<TForeignDocument, TNewResult> options = null);
208208

209+
/// <summary>
210+
/// Appends a lookup stage to the pipeline.
211+
/// </summary>
212+
/// <typeparam name="TForeignDocument">The type of the foreign collection documents.</typeparam>
213+
/// <typeparam name="TAsElement">The type of the as field elements.</typeparam>
214+
/// <typeparam name="TAs">The type of the as field.</typeparam>
215+
/// <typeparam name="TNewResult">The type of the new result.</typeparam>
216+
/// <param name="foreignCollection">The foreign collection.</param>
217+
/// <param name="let">The "let" definition.</param>
218+
/// <param name="lookupPipeline">The lookup pipeline.</param>
219+
/// <param name="as">The as field in <typeparamref name="TNewResult" /> in which to place the results of the lookup pipeline.</param>
220+
/// <param name="options">The options.</param>
221+
/// <returns>The fluent aggregate interface.</returns>
222+
IAggregateFluent<TNewResult> Lookup<TForeignDocument, TAsElement, TAs, TNewResult>(
223+
IMongoCollection<TForeignDocument> foreignCollection,
224+
BsonDocument let,
225+
PipelineDefinition<TForeignDocument, TAsElement> lookupPipeline,
226+
FieldDefinition<TNewResult, TAs> @as,
227+
AggregateLookupOptions<TForeignDocument, TNewResult> options = null)
228+
where TAs : IEnumerable<TAsElement>;
229+
209230
/// <summary>
210231
/// Appends a match stage to the pipeline.
211232
/// </summary>

src/MongoDB.Driver/IAggregateFluentExtensions.cs

+59
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,65 @@ public static IAggregateFluent<TNewResult> Lookup<TResult, TForeignDocument, TNe
370370
return aggregate.AppendStage(PipelineStageDefinitionBuilder.Lookup(foreignCollection, localField, foreignField, @as, options));
371371
}
372372

373+
/// <summary>
374+
/// Appends a lookup stage to the pipeline.
375+
/// </summary>
376+
/// <typeparam name="TResult">The type of the result.</typeparam>
377+
/// <param name="aggregate">The aggregate.</param>
378+
/// <param name="foreignCollection">The foreign collection.</param>
379+
/// <param name="let">The "let" definition.</param>
380+
/// <param name="lookupPipeline">The lookup pipeline.</param>
381+
/// <param name="as">The as field in the result in which to place the results of the lookup pipeline.</param>
382+
/// <returns>The fluent aggregate interface.</returns>
383+
public static IAggregateFluent<BsonDocument> Lookup<TResult>(
384+
this IAggregateFluent<TResult> aggregate,
385+
IMongoCollection<BsonDocument> foreignCollection,
386+
BsonDocument let,
387+
PipelineDefinition<BsonDocument, BsonDocument> lookupPipeline,
388+
FieldDefinition<BsonDocument, IEnumerable<BsonDocument>> @as)
389+
{
390+
Ensure.IsNotNull(aggregate, nameof(aggregate));
391+
Ensure.IsNotNull(foreignCollection, nameof(foreignCollection));
392+
return aggregate.AppendStage(PipelineStageDefinitionBuilder.Lookup<TResult, BsonDocument, BsonDocument, IEnumerable<BsonDocument>, BsonDocument>(
393+
foreignCollection,
394+
let,
395+
lookupPipeline,
396+
@as));
397+
}
398+
399+
/// <summary>
400+
/// Appends a lookup stage to the pipeline.
401+
/// </summary>
402+
/// <typeparam name="TResult">The type of the result.</typeparam>
403+
/// <typeparam name="TForeignDocument">The type of the foreign collection documents.</typeparam>
404+
/// <typeparam name="TAsElement">The type of the as field elements.</typeparam>
405+
/// <typeparam name="TAs">The type of the as field.</typeparam>
406+
/// <typeparam name="TNewResult">The type of the new result.</typeparam>
407+
/// <param name="aggregate">The aggregate.</param>
408+
/// <param name="foreignCollection">The foreign collection.</param>
409+
/// <param name="let">The "let" definition.</param>
410+
/// <param name="lookupPipeline">The lookup pipeline.</param>
411+
/// <param name="as">The as field in <typeparamref name="TNewResult" /> in which to place the results of the lookup pipeline.</param>
412+
/// <param name="options">The options.</param>
413+
/// <returns>The fluent aggregate interface.</returns>
414+
public static IAggregateFluent<TNewResult> Lookup<TResult, TForeignDocument, TAsElement, TAs, TNewResult>(
415+
this IAggregateFluent<TResult> aggregate,
416+
IMongoCollection<TForeignDocument> foreignCollection,
417+
BsonDocument let,
418+
PipelineDefinition<TForeignDocument, TAsElement> lookupPipeline,
419+
Expression<Func<TNewResult, TAs>> @as,
420+
AggregateLookupOptions<TForeignDocument, TNewResult> options = null)
421+
where TAs : IEnumerable<TAsElement>
422+
{
423+
Ensure.IsNotNull(aggregate, nameof(aggregate));
424+
return aggregate.AppendStage(PipelineStageDefinitionBuilder.Lookup<TResult, TForeignDocument, TAsElement, TAs, TNewResult>(
425+
foreignCollection,
426+
let,
427+
lookupPipeline,
428+
@as,
429+
options));
430+
}
431+
373432
/// <summary>
374433
/// Appends a match stage to the pipeline.
375434
/// </summary>

src/MongoDB.Driver/PipelineDefinitionBuilder.cs

+71-1
Original file line numberDiff line numberDiff line change
@@ -701,6 +701,76 @@ public static PipelineDefinition<TInput, TOutput> Lookup<TInput, TIntermediate,
701701
return pipeline.AppendStage(PipelineStageDefinitionBuilder.Lookup(foreignCollection, localField, foreignField, @as, options));
702702
}
703703

704+
/// <summary>
705+
/// Appends a $lookup stage to the pipeline.
706+
/// </summary>
707+
/// <typeparam name="TInput">The type of the input documents.</typeparam>
708+
/// <typeparam name="TIntermediate">The type of the intermediate documents.</typeparam>
709+
/// <typeparam name="TForeignDocument">The type of the foreign collection documents.</typeparam>
710+
/// <typeparam name="TAsElement">The type of the as field elements.</typeparam>
711+
/// <typeparam name="TAs">The type of the as field.</typeparam>
712+
/// <typeparam name="TOutput">The type of the output documents.</typeparam>
713+
/// <param name="pipeline">The pipeline.</param>
714+
/// <param name="foreignCollection">The foreign collection.</param>
715+
/// <param name="let">The "let" definition.</param>
716+
/// <param name="lookupPipeline">The lookup pipeline.</param>
717+
/// <param name="as">The as field in <typeparamref name="TOutput" /> in which to place the results of the lookup pipeline.</param>
718+
/// <param name="options">The options.</param>
719+
/// <returns>The stage.</returns>
720+
public static PipelineDefinition<TInput, TOutput> Lookup<TInput, TIntermediate, TForeignDocument, TAsElement, TAs, TOutput>(
721+
this PipelineDefinition<TInput, TIntermediate> pipeline,
722+
IMongoCollection<TForeignDocument> foreignCollection,
723+
BsonDocument let,
724+
PipelineDefinition<TForeignDocument, TAsElement> lookupPipeline,
725+
FieldDefinition<TOutput, TAs> @as,
726+
AggregateLookupOptions<TForeignDocument, TOutput> options = null)
727+
where TAs : IEnumerable<TAsElement>
728+
{
729+
Ensure.IsNotNull(pipeline, nameof(pipeline));
730+
return pipeline.AppendStage(PipelineStageDefinitionBuilder.Lookup<TIntermediate, TForeignDocument, TAsElement, TAs, TOutput>(
731+
foreignCollection,
732+
let,
733+
lookupPipeline,
734+
@as,
735+
options
736+
));
737+
}
738+
739+
/// <summary>
740+
/// Appends a $lookup stage to the pipeline.
741+
/// </summary>
742+
/// <typeparam name="TInput">The type of the input documents.</typeparam>
743+
/// <typeparam name="TIntermediate">The type of the intermediate documents.</typeparam>
744+
/// <typeparam name="TForeignDocument">The type of the foreign collection documents.</typeparam>
745+
/// <typeparam name="TAsElement">The type of the as field elements.</typeparam>
746+
/// <typeparam name="TAs">The type of the as field.</typeparam>
747+
/// <typeparam name="TOutput">The type of the output documents.</typeparam>
748+
/// <param name="pipeline">The pipeline.</param>
749+
/// <param name="foreignCollection">The foreign collection.</param>
750+
/// <param name="let">The "let" definition.</param>
751+
/// <param name="lookupPipeline">The lookup pipeline.</param>
752+
/// <param name="as">The as field in <typeparamref name="TOutput" /> in which to place the results of the lookup pipeline.</param>
753+
/// <param name="options">The options.</param>
754+
/// <returns>The stage.</returns>
755+
public static PipelineDefinition<TInput, TOutput> Lookup<TInput, TIntermediate, TForeignDocument, TAsElement, TAs, TOutput>(
756+
this PipelineDefinition<TInput, TIntermediate> pipeline,
757+
IMongoCollection<TForeignDocument> foreignCollection,
758+
BsonDocument let,
759+
PipelineDefinition<TForeignDocument, TAsElement> lookupPipeline,
760+
Expression<Func<TOutput, TAs>> @as,
761+
AggregateLookupOptions<TForeignDocument, TOutput> options = null)
762+
where TAs : IEnumerable<TAsElement>
763+
{
764+
Ensure.IsNotNull(pipeline, nameof(pipeline));
765+
return pipeline.AppendStage(PipelineStageDefinitionBuilder.Lookup<TIntermediate, TForeignDocument, TAsElement, TAs, TOutput>(
766+
foreignCollection,
767+
let,
768+
lookupPipeline,
769+
@as,
770+
options
771+
));
772+
}
773+
704774
/// <summary>
705775
/// Appends a $match stage to the pipeline.
706776
/// </summary>
@@ -1195,4 +1265,4 @@ public override RenderedPipelineDefinition<TOutput> Render(IBsonSerializer<TInpu
11951265
return new RenderedPipelineDefinition<TOutput>(renderedPipeline.Documents, outputSerializer);
11961266
}
11971267
}
1198-
}
1268+
}

src/MongoDB.Driver/PipelineStageDefinitionBuilder.cs

+83
Original file line numberDiff line numberDiff line change
@@ -811,6 +811,89 @@ public static PipelineStageDefinition<TInput, TOutput> Lookup<TInput, TForeignDo
811811
options);
812812
}
813813

814+
/// <summary>
815+
/// Creates a $lookup stage.
816+
/// </summary>
817+
/// <typeparam name="TInput">The type of the input documents.</typeparam>
818+
/// <typeparam name="TForeignDocument">The type of the foreign collection documents.</typeparam>
819+
/// <typeparam name="TAsElement">The type of the as field elements.</typeparam>
820+
/// <typeparam name="TAs">The type of the as field.</typeparam>
821+
/// <typeparam name="TOutput">The type of the output documents.</typeparam>
822+
/// <param name="foreignCollection">The foreign collection.</param>
823+
/// <param name="let">The "let" definition.</param>
824+
/// <param name="lookupPipeline">The lookup pipeline.</param>
825+
/// <param name="as">The as field in <typeparamref name="TOutput" /> in which to place the results of the lookup pipeline.</param>
826+
/// <param name="options">The options.</param>
827+
/// <returns>The stage.</returns>
828+
public static PipelineStageDefinition<TInput, TOutput> Lookup<TInput, TForeignDocument, TAsElement, TAs, TOutput>(
829+
IMongoCollection<TForeignDocument> foreignCollection,
830+
BsonDocument let,
831+
PipelineDefinition<TForeignDocument, TAsElement> lookupPipeline,
832+
FieldDefinition<TOutput, TAs> @as,
833+
AggregateLookupOptions<TForeignDocument, TOutput> options = null)
834+
where TAs : IEnumerable<TAsElement>
835+
{
836+
Ensure.IsNotNull(foreignCollection, nameof(foreignCollection));
837+
Ensure.IsNotNull(lookupPipeline, nameof(lookupPipeline));
838+
Ensure.IsNotNull(@as, nameof(@as));
839+
840+
options = options ?? new AggregateLookupOptions<TForeignDocument, TOutput>();
841+
const string operatorName = "$lookup";
842+
var stage = new DelegatedPipelineStageDefinition<TInput, TOutput>(
843+
operatorName,
844+
(inputSerializer, sr) =>
845+
{
846+
var foreignSerializer = options.ForeignSerializer ?? foreignCollection.DocumentSerializer ?? inputSerializer as IBsonSerializer<TForeignDocument> ?? sr.GetSerializer<TForeignDocument>();
847+
var outputSerializer = options.ResultSerializer ?? inputSerializer as IBsonSerializer<TOutput> ?? sr.GetSerializer<TOutput>();
848+
var lookupPipelineDocuments = new BsonArray(lookupPipeline.Render(foreignSerializer, sr).Documents);
849+
850+
var lookupBody = new BsonDocument
851+
{
852+
{ "from", foreignCollection.CollectionNamespace.CollectionName },
853+
{ "let", let, let != null },
854+
{ "pipeline", lookupPipelineDocuments },
855+
{ "as", @as.Render(outputSerializer, sr).FieldName }
856+
};
857+
858+
return new RenderedPipelineStageDefinition<TOutput>(operatorName, new BsonDocument(operatorName, lookupBody), outputSerializer);
859+
});
860+
861+
return stage;
862+
}
863+
864+
/// <summary>
865+
/// Creates a $lookup stage.
866+
/// </summary>
867+
/// <typeparam name="TInput">The type of the input documents.</typeparam>
868+
/// <typeparam name="TForeignDocument">The type of the foreign collection documents.</typeparam>
869+
/// <typeparam name="TAsElement">The type of the as field elements.</typeparam>
870+
/// <typeparam name="TAs">The type of the as field.</typeparam>
871+
/// <typeparam name="TOutput">The type of the output documents.</typeparam>
872+
/// <param name="foreignCollection">The foreign collection.</param>
873+
/// <param name="let">The "let" definition.</param>
874+
/// <param name="lookupPipeline">The lookup pipeline.</param>
875+
/// <param name="as">The as field in <typeparamref name="TOutput" /> in which to place the results of the lookup pipeline.</param>
876+
/// <param name="options">The options.</param>
877+
/// <returns>The stage.</returns>
878+
public static PipelineStageDefinition<TInput, TOutput> Lookup<TInput, TForeignDocument, TAsElement, TAs, TOutput>(
879+
IMongoCollection<TForeignDocument> foreignCollection,
880+
BsonDocument let,
881+
PipelineDefinition<TForeignDocument, TAsElement> lookupPipeline,
882+
Expression<Func<TOutput, TAs>> @as,
883+
AggregateLookupOptions<TForeignDocument, TOutput> options = null)
884+
where TAs : IEnumerable<TAsElement>
885+
{
886+
Ensure.IsNotNull(foreignCollection, nameof(foreignCollection));
887+
Ensure.IsNotNull(lookupPipeline, nameof(lookupPipeline));
888+
Ensure.IsNotNull(@as, nameof(@as));
889+
890+
return Lookup<TInput, TForeignDocument, TAsElement, TAs, TOutput>(
891+
foreignCollection,
892+
let,
893+
lookupPipeline,
894+
new ExpressionFieldDefinition<TOutput, TAs>(@as));
895+
}
896+
814897
/// <summary>
815898
/// Creates a $match stage.
816899
/// </summary>

0 commit comments

Comments
 (0)