Example #1
0
        /// <summary>
        /// Watches changes on all collections in all databases.
        /// </summary>
        /// <param name="client">The client.</param>
        /// <param name="options">The options.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <returns>
        /// A change stream.
        /// </returns>
        public static IAsyncCursor <ChangeStreamDocument <BsonDocument> > Watch(
            this IMongoClient client,
            ChangeStreamOptions options         = null,
            CancellationToken cancellationToken = default(CancellationToken))
        {
            Ensure.IsNotNull(client, nameof(client));
            var emptyPipeline = new EmptyPipelineDefinition <ChangeStreamDocument <BsonDocument> >();

            return(client.Watch(emptyPipeline, options, cancellationToken));
        }
Example #2
0
        /// <summary>
        /// Watches changes on all collection in a database.
        /// </summary>
        /// <param name="database">The database.</param>
        /// <param name="options">The options.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <returns>
        /// A change stream.
        /// </returns>
        public static Task <IAsyncCursor <ChangeStreamDocument <BsonDocument> > > WatchAsync(
            this IMongoDatabase database,
            ChangeStreamOptions options         = null,
            CancellationToken cancellationToken = default(CancellationToken))
        {
            Ensure.IsNotNull(database, nameof(database));
            var emptyPipeline = new EmptyPipelineDefinition <ChangeStreamDocument <BsonDocument> >();

            return(database.WatchAsync(emptyPipeline, options, cancellationToken));
        }
Example #3
0
        private static PipelineDefinition <ChangeStreamDocument <Product>, ChangeStreamDocument <Product> > BuildPipelineDefinition()
        {
            var pipeline =
                new EmptyPipelineDefinition <ChangeStreamDocument <Product> >()
                .Match(x => x.FullDocument.PublishStatus == false &&
                       (x.OperationType == ChangeStreamOperationType.Insert ||
                        x.OperationType == ChangeStreamOperationType.Update));

            return(pipeline);
        }
Example #4
0
        public void Sample_should_add_expected_stage()
        {
            var pipeline = new EmptyPipelineDefinition <BsonDocument>();

            var result = pipeline.Sample(15);

            var stages = RenderStages(result, BsonDocumentSerializer.Instance);

            stages.Count.Should().Be(1);
            stages[0].Should().Be("{ $sample: { size: 15 } }");
        }
Example #5
0
        protected Task cofigureWatcher <WatchType>(
            IMongoCollection <WatchType> collectionToMonitor,
            Func <ChangeStreamDocument <WatchType>, Task> watchFunction,
            ChangeStreamOperationType operationType,
            ChangeStreamFullDocumentOption changeStreamDocumentOption = ChangeStreamFullDocumentOption.UpdateLookup,
            byte secondsToWait = 2)
        {
            var pipeline = new EmptyPipelineDefinition <ChangeStreamDocument <WatchType> >().Match(x => x.OperationType == operationType);

            return(buildWatcher(pipeline, changeStreamDocumentOption, secondsToWait, watchFunction, collectionToMonitor));
        }
Example #6
0
        public async Task <bool> WatchCollectionAsync <T>(string collectionName, Expression <Func <ChangeStreamDocument <T>, bool> > filter)
        {
            var collection       = db.GetCollection <T>(collectionName);
            var filterDefinition = Builders <ChangeStreamDocument <T> > .Filter.Where(filter);

            var pipeline = new EmptyPipelineDefinition <ChangeStreamDocument <T> >().Match(filterDefinition);

            var changeQueueStream = await collection.WatchAsync(pipeline);

            return(changeQueueStream.ToEnumerable().GetEnumerator().MoveNext());
        }
Example #7
0
        public void ChangestreamExample4()
        {
            RequireServer.Check().Supports(Feature.AggregateAddFields);

            var client   = DriverTestConfiguration.Client;
            var database = client.GetDatabase("ChangeStreamExamples");

            database.DropCollection("inventory");

            var cancelationTokenSource = new CancellationTokenSource();

            try
            {
                var document = new BsonDocument("username", "alice");

                Task.Run(() =>
                {
                    var inventoryCollection = database.GetCollection <BsonDocument>("inventory");

                    while (!cancelationTokenSource.IsCancellationRequested)
                    {
                        Thread.Sleep(TimeSpan.FromMilliseconds(100));
                        document["_id"] = ObjectId.GenerateNewId();
                        inventoryCollection.InsertOne(document);
                    }
                }, cancelationTokenSource.Token);

                // Start Changestream Example 4
                var pipeline = new EmptyPipelineDefinition <ChangeStreamDocument <BsonDocument> >()
                               .Match(change =>
                                      change.FullDocument["username"] == "alice" ||
                                      change.OperationType == ChangeStreamOperationType.Delete)
                               .AppendStage <ChangeStreamDocument <BsonDocument>, ChangeStreamDocument <BsonDocument>, BsonDocument>(
                    "{ $addFields : { newField : 'this is an added field!' } }");

                var collection = database.GetCollection <BsonDocument>("inventory");
                using (var changeStream = collection.Watch(pipeline))
                {
                    using (var enumerator = changeStream.ToEnumerable().GetEnumerator())
                    {
                        if (enumerator.MoveNext())
                        {
                            var next = enumerator.Current;
                        }
                    }
                }
                // End Changestream Example 4
            }
            finally
            {
                cancelationTokenSource.Cancel();
            }
        }
Example #8
0
        private static async Task TriggerChangeStreamAsync()
        {
            Console.WriteLine("TriggerChangeStream Method start");
            try
            {
                MongoClient dbClient = new MongoClient("mongodb://*****:*****@deliverymoment-cosmos-mongo-db.mongo.cosmos.azure.com:10255/?ssl=true&replicaSet=globaldb&maxIdleTimeMS=120000&appName=@deliverymoment-cosmos-mongo-db@");
                //Database List

                //Get Database and Collection
                Console.WriteLine("Get Database : deliverymoment-db :");
                IMongoDatabase db = dbClient.GetDatabase("deliverymoment-db");

                Console.WriteLine("Get Deilverymoment Collection :");

                var deliveryColl = db.GetCollection <BsonDocument>("delivery-moment​");

                Console.WriteLine("Changestream code start");

                //change feed code start
                var pipeline = new EmptyPipelineDefinition <ChangeStreamDocument <BsonDocument> >()
                               .Match(change => change.OperationType == ChangeStreamOperationType.Insert || change.OperationType == ChangeStreamOperationType.Update || change.OperationType == ChangeStreamOperationType.Replace)
                               .AppendStage <ChangeStreamDocument <BsonDocument>, ChangeStreamDocument <BsonDocument>, BsonDocument>(
                    "{ $project: { '_id': 1, 'fullDocument': 1, 'ns': 1, 'documentKey': 1 }}");

                var options = new ChangeStreamOptions
                {
                    FullDocument = ChangeStreamFullDocumentOption.UpdateLookup
                };

                var enumerator = deliveryColl.Watch(pipeline, options).ToEnumerable().GetEnumerator();
                Console.WriteLine("Reading Change stream while loop start");
                while (enumerator.MoveNext())
                {
                    //publish message to Confluent Kafka
                    var document = enumerator.Current;
                    Console.WriteLine($"Start publishing messaage : {document}");
                    await DeliveryMomentMessageHandler.PublishMessage(document.ToString());

                    Console.WriteLine($"End publishing messaage : {document}");
                }

                enumerator.Dispose();

                Console.WriteLine("Reading Change stream while loop end");
                //change feed code end
            }
            catch (Exception ex)
            {
                Console.WriteLine("Error: " + ex.Message);
            }

            Console.WriteLine("TriggerChangeStream Method end");
        }
Example #9
0
        public void UnionWith_should_throw_when_withCollection_is_null()
        {
            var pipeline = new EmptyPipelineDefinition <BsonDocument>();
            IMongoCollection <BsonDocument> withCollection = null;
            var withPipeline = new EmptyPipelineDefinition <BsonDocument>();

            var exception = Record.Exception(() => pipeline.UnionWith(withCollection, withPipeline));

            var argumentNullException = exception.Should().BeOfType <ArgumentNullException>().Subject;

            argumentNullException.ParamName.Should().Be("withCollection");
        }
        public void ChangeStream_should_add_the_expected_stage_when_options_is_null()
        {
            var pipeline = new EmptyPipelineDefinition <BsonDocument>();
            ChangeStreamStageOptions options = null;

            var result = pipeline.ChangeStream(options);

            var stages = RenderStages(result);

            stages.Count.Should().Be(1);
            stages[0].Should().Be("{ $changeStream : { fullDocument : \"default\" } }");
        }
Example #11
0
        /// <summary>
        /// Watches changes on all collection in a database.
        /// </summary>
        /// <param name="database">The database.</param>
        /// <param name="session">The session.</param>
        /// <param name="options">The options.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <returns>
        /// A change stream.
        /// </returns>
        public static IChangeStreamCursor <ChangeStreamDocument <BsonDocument> > Watch(
            this IMongoDatabase database,
            IClientSessionHandle session,
            ChangeStreamOptions options         = null,
            CancellationToken cancellationToken = default(CancellationToken))
        {
            Ensure.IsNotNull(database, nameof(database));
            Ensure.IsNotNull(session, nameof(session));
            var emptyPipeline = new EmptyPipelineDefinition <ChangeStreamDocument <BsonDocument> >();

            return(database.Watch(session, emptyPipeline, options, cancellationToken));
        }
        /// <summary>
        /// Watches changes on all collections in all databases.
        /// </summary>
        /// <param name="client">The client.</param>
        /// <param name="session">The session.</param>
        /// <param name="options">The options.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <returns>
        /// A change stream.
        /// </returns>
        public static Task <IChangeStreamCursor <ChangeStreamDocument <BsonDocument> > > WatchAsync(
            this IMongoClient client,
            IClientSessionHandle session,
            ChangeStreamOptions options         = null,
            CancellationToken cancellationToken = default(CancellationToken))
        {
            Ensure.IsNotNull(client, nameof(client));
            Ensure.IsNotNull(session, nameof(session));
            var emptyPipeline = new EmptyPipelineDefinition <ChangeStreamDocument <BsonDocument> >();

            return(client.WatchAsync(session, emptyPipeline, options, cancellationToken));
        }
Example #13
0
        //-----------------------------------------------------//

        #region Watch One Collection for any kind of a change

        public bool WatchDatabase()
        {
            var pipeline = new EmptyPipelineDefinition <ChangeStreamDocument <BsonDocument> >().Match(x =>
                                                                                                      x.OperationType == ChangeStreamOperationType.Delete ||
                                                                                                      x.OperationType == ChangeStreamOperationType.Insert ||
                                                                                                      x.OperationType == ChangeStreamOperationType.Update ||
                                                                                                      x.OperationType == ChangeStreamOperationType.Replace

                                                                                                      );
            var change = db.Watch(pipeline).ToEnumerable().GetEnumerator();

            return(change.MoveNext());
        }
Example #14
0
        public IChangeStreamCursor <ChangeStreamDocument <Notification> > Watch(string userId, CancellationToken cancellationToken)
        {
            var filter = Builders <ChangeStreamDocument <Notification> > .Filter.And(
                Builders <ChangeStreamDocument <Notification> > .Filter.Eq(n => n.OperationType, ChangeStreamOperationType.Insert),
                Builders <ChangeStreamDocument <Notification> > .Filter.Eq(n => n.FullDocument.UserLogin, userId)
                );

            var pipeline = new EmptyPipelineDefinition <ChangeStreamDocument <Notification> >()
                           .Match(filter);

            return(Collection.Watch(pipeline, new ChangeStreamOptions {
            }, cancellationToken));
        }
Example #15
0
        public async Task <IChangeStreamCursor <ChangeStreamDocument <Notification> > > GetNotificationChangeStreamCursorAsync()
        {
            var            mongoClient = new MongoClient(_server);
            IMongoDatabase database    = mongoClient.GetDatabase(_database);
            var            collection  = database.GetCollection <Notification>(_collection);
            var            options     = new ChangeStreamOptions {
                FullDocument = ChangeStreamFullDocumentOption.UpdateLookup
            };
            var pipeline = new EmptyPipelineDefinition <ChangeStreamDocument <Notification> >().Match("{ operationType: { $in: [ 'insert' ] } }");
            IChangeStreamCursor <ChangeStreamDocument <Notification> > cursor = await collection.WatchAsync(pipeline, options);

            return(cursor);
        }
Example #16
0
        public void UnionWith_should_throw_when_TWith_is_not_the_same_with_TInput_and_withPipeline_is_null()
        {
            var pipeline       = new EmptyPipelineDefinition <BsonDocument>();
            var withCollection = Mock.Of <IMongoCollection <object> >(
                coll => coll.CollectionNamespace == CollectionNamespace.FromFullName("db.test"));

            var exception = Record.Exception(() => pipeline.UnionWith(withCollection, withPipeline: null));

            var e = exception.Should().BeOfType <ArgumentException>().Subject;

            e.Message.Should().StartWith("The withPipeline cannot be null when TWith != TInput. A pipeline is required to transform the TWith documents to TInput documents.");
            e.ParamName.Should().Be("withPipeline");
        }
Example #17
0
        public void Aggregate_with_write_stage_should_not_have_transaction_id(
            [Values("$out", "$merge")] string outStage,
            [Values(false, true)] bool async)
        {
            RequireServer
            .Check()
            .ClusterTypes(ClusterType.ReplicaSet, ClusterType.Sharded)
            .Serverless(false);     // $out and $merge are not supported on serverless.

            if (outStage == "$merge")
            {
                RequireServer.Check().Supports(Feature.AggregateMerge);
            }

            DropAndCreateCollection();

            var eventCapturer = CreateEventCapturer();

            using (var client = CreateDisposableClient(eventCapturer))
            {
                var database   = client.GetDatabase(_databaseName);
                var collection = database.GetCollection <BsonDocument>(_collectionName);

                PipelineDefinition <BsonDocument, BsonDocument> pipeline = new EmptyPipelineDefinition <BsonDocument>();
                var outputCollection = database.GetCollection <BsonDocument>(_collectionName + "-outputCollection");
                switch (outStage)
                {
                case "$out":
                    pipeline = pipeline.Out(outputCollection);
                    break;

                case "$merge":
                    var mergeOptions = new MergeStageOptions <BsonDocument>();
                    pipeline = pipeline.Merge(outputCollection, mergeOptions);
                    break;

                default:
                    throw new Exception($"Unexpected outStage: {outStage}.");
                }
                if (async)
                {
                    collection.AggregateAsync(pipeline).GetAwaiter().GetResult();
                }
                else
                {
                    collection.Aggregate(pipeline);
                }

                AssertCommandDoesNotHaveTransactionId(eventCapturer);
            }
        }
Example #18
0
        public void UnionWith_should_add_expected_stage()
        {
            var pipeline       = new EmptyPipelineDefinition <BsonDocument>();
            var withCollection = Mock.Of <IMongoCollection <BsonDocument> >(
                coll => coll.CollectionNamespace == CollectionNamespace.FromFullName("db.test"));
            var withPipeline = new EmptyPipelineDefinition <BsonDocument>()
                               .AppendStage <BsonDocument, BsonDocument, BsonDocument>("{ $match : { b : 1 } }");

            var result = pipeline.UnionWith(withCollection, withPipeline);

            var stages = RenderStages(result, BsonDocumentSerializer.Instance);

            stages[0].Should().Be("{ $unionWith : { coll : 'test', pipeline : [{ $match : { b : 1 } }] } }");
        }
Example #19
0
        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            var options = new ChangeStreamOptions {
                FullDocument = ChangeStreamFullDocumentOption.UpdateLookup
            };
            var pipeline = new EmptyPipelineDefinition <ChangeStreamDocument <User> >()
                           .Match(x => x.OperationType == ChangeStreamOperationType.Replace || x.OperationType == ChangeStreamOperationType.Update);

            using var cursor = _users.Watch(pipeline, options, stoppingToken);
            await cursor.ForEachAsync(async document =>
            {
                await _userHubContext.SendUpdateAsync(document.FullDocument);
            }, stoppingToken);
        }
Example #20
0
        public bool WatchCollection <T>(string collectionName, Expression <Func <ChangeStreamDocument <T>, bool> > filter, out int changeCount)
        {
            var collection       = db.GetCollection <T>(collectionName);
            var filterDefinition = Builders <ChangeStreamDocument <T> > .Filter.Where(filter);

            var pipeline = new EmptyPipelineDefinition <ChangeStreamDocument <T> >().Match(filterDefinition);

            var changeCursor = collection.Watch(pipeline);

            changeCount = changeCursor.Current.Count();
            var changeCursorEnumerator = changeCursor.ToEnumerable().GetEnumerator();

            return(changeCursorEnumerator.MoveNext());
        }
Example #21
0
        public void Merge_should_add_expected_stage()
        {
            var pipeline         = new EmptyPipelineDefinition <BsonDocument>();
            var client           = DriverTestConfiguration.Client;
            var outputDatabase   = client.GetDatabase("database");
            var outputCollection = outputDatabase.GetCollection <BsonDocument>("collection");
            var mergeOptions     = new MergeStageOptions <BsonDocument>();

            var result = pipeline.Merge(outputCollection, mergeOptions);

            var stages = RenderStages(result, BsonDocumentSerializer.Instance);

            stages.Count.Should().Be(1);
            stages[0].Should().Be("{ $merge : { into : { db : 'database', coll : 'collection' } } }");
        }
Example #22
0
        public async Task <bool> WatchDatabaseAsync()
        {
            var pipeline = new EmptyPipelineDefinition <ChangeStreamDocument <BsonDocument> >().Match(x =>
                                                                                                      x.OperationType == ChangeStreamOperationType.Delete ||
                                                                                                      x.OperationType == ChangeStreamOperationType.Insert ||
                                                                                                      x.OperationType == ChangeStreamOperationType.Update ||
                                                                                                      x.OperationType == ChangeStreamOperationType.Replace

                                                                                                      );
            var change = await db.WatchAsync(pipeline);

            var enumerator = change.ToEnumerable().GetEnumerator();

            return(enumerator.MoveNext());
        }
Example #23
0
        protected void ChangeStreamsSync(ChangeStreamOperationType changeStreamOperationType, Action <TEntity> action)
        {
            var pipeline = new EmptyPipelineDefinition <ChangeStreamDocument <TEntity> >().Match(x => x.OperationType == changeStreamOperationType);

            using (var cursor = Collection <TEntity>().Watch(pipeline))
            {
                cursor.ForEachAsync(change =>
                {
                    if (change.OperationType == changeStreamOperationType)
                    {
                        action(change.FullDocument);
                    }
                });
            }
        }
Example #24
0
        public static void WatchForChanges()
        {
            var pipeline = new EmptyPipelineDefinition <Geodata>().Match("{ operationType: { $eq: 'update' } }");
            var client   = new MongoClient("mongodb+srv://Fetcher:[email protected]/test?retryWrites=true&w=majority");
            var database = client.GetDatabase("GEM");

            collection = database.GetCollection <Geodata>("Geodata");

            using (var cursor = collection.Watch())
            {
                foreach (var change in cursor.ToEnumerable())
                {
                    UpdateSurvey();
                }
            }
        }
Example #25
0
        public async Task <bool> WatchCollectionAsync <T>(string collectionName)
        {
            var collection = db.GetCollection <T>(collectionName);
            var pipeline   = new EmptyPipelineDefinition <ChangeStreamDocument <T> >().Match(x =>
                                                                                             x.OperationType == ChangeStreamOperationType.Delete ||
                                                                                             x.OperationType == ChangeStreamOperationType.Insert ||
                                                                                             x.OperationType == ChangeStreamOperationType.Update ||
                                                                                             x.OperationType == ChangeStreamOperationType.Replace
                                                                                             );

            var changeQueueStream = await collection.WatchAsync(sessionHandle, pipeline);

            var enumerator = changeQueueStream.ToEnumerable().GetEnumerator();

            return(enumerator.MoveNext());
        }
Example #26
0
        private static PipelineDefinition <ChangeStreamDocument <MongoEventCommit>, ChangeStreamDocument <MongoEventCommit> >?Match(string?streamFilter)
        {
            var result = new EmptyPipelineDefinition <ChangeStreamDocument <MongoEventCommit> >();

            var byStream = Filtering.ByChangeInStream(streamFilter);

            if (byStream != null)
            {
                var filterBuilder = Builders <ChangeStreamDocument <MongoEventCommit> > .Filter;

                var filter = filterBuilder.Or(filterBuilder.Ne(x => x.OperationType, ChangeStreamOperationType.Insert), byStream);

                return(result.Match(filter));
            }

            return(result);
        }
Example #27
0
        public bool WatchCollection <T>(string collectionName)
        {
            var collection = db.GetCollection <T>(collectionName);

            var pipeline = new EmptyPipelineDefinition <ChangeStreamDocument <T> >().Match(x =>
                                                                                           x.OperationType == ChangeStreamOperationType.Delete ||
                                                                                           x.OperationType == ChangeStreamOperationType.Insert ||
                                                                                           x.OperationType == ChangeStreamOperationType.Update ||
                                                                                           x.OperationType == ChangeStreamOperationType.Replace

                                                                                           );

            var changeCursor           = collection.Watch(sessionHandle, pipeline);
            var changeCursorEnumerator = changeCursor.ToEnumerable().GetEnumerator();

            return(changeCursorEnumerator.MoveNext());
        }
Example #28
0
        protected override async Task ExecuteAsync(CancellationToken cancellationToken)
        {
            var client     = new MongoClient(_mongoOptions.ConnectionString);
            var database   = client.GetDatabase(_mongoOptions.Database);
            var collection = database.GetCollection <BlogPost>(nameof(BlogPost));

            var insertOperationsOnlyFilter = new EmptyPipelineDefinition <ChangeStreamDocument <BlogPost> >().Match(x => x.OperationType == ChangeStreamOperationType.Insert);
            var blogPostFeed = new MongoDbChangeStreamFeed <BlogPost>(collection, TimeSpan.FromSeconds(5));

            try
            {
                await foreach (var blog in blogPostFeed.FetchFeed(insertOperationsOnlyFilter, cancellationToken))
                {
                    await _blogHub.Clients.All.BlogPostCreated(blog);
                }
            }
            catch (OperationCanceledException)
            { }
        }
Example #29
0
        public void CreateChangeStreamOperation_for_collection_returns_expected_result()
        {
            var databaseNamespace   = new DatabaseNamespace("databaseName");
            var collectionNamespace = new CollectionNamespace(databaseNamespace, "collectionName");
            var mockCollection      = new Mock <IMongoCollection <BsonDocument> >();

            mockCollection.SetupGet(m => m.CollectionNamespace).Returns(collectionNamespace);
            var pipeline           = new EmptyPipelineDefinition <ChangeStreamDocument <BsonDocument> >().Limit(1);
            var documentSerializer = BsonDocumentSerializer.Instance;
            var options            = new ChangeStreamOptions
            {
                BatchSize                = 123,
                Collation                = new Collation("en-us"),
                FullDocument             = ChangeStreamFullDocumentOption.UpdateLookup,
                FullDocumentBeforeChange = ChangeStreamFullDocumentBeforeChangeOption.Off,
                MaxAwaitTime             = TimeSpan.FromSeconds(123),
                ResumeAfter              = new BsonDocument(),
                StartAfter               = new BsonDocument(),
                StartAtOperationTime     = new BsonTimestamp(1, 2)
            };
            var readConcern            = new ReadConcern();
            var messageEncoderSettings = new MessageEncoderSettings();
            var renderedPipeline       = RenderPipeline(pipeline);

            var result = ChangeStreamHelper.CreateChangeStreamOperation(mockCollection.Object, pipeline, documentSerializer, options, readConcern, messageEncoderSettings, true);

            result.BatchSize.Should().Be(options.BatchSize);
            result.Collation.Should().BeSameAs(options.Collation);
            result.CollectionNamespace.Should().BeSameAs(collectionNamespace);
            result.DatabaseNamespace.Should().BeNull();
            result.FullDocument.Should().Be(options.FullDocument);
            result.FullDocumentBeforeChange.Should().Be(options.FullDocumentBeforeChange);
            result.MaxAwaitTime.Should().Be(options.MaxAwaitTime);
            result.MessageEncoderSettings.Should().BeSameAs(messageEncoderSettings);
            result.Pipeline.Should().Equal(renderedPipeline.Documents);
            result.ReadConcern.Should().BeSameAs(readConcern);
            result.ResultSerializer.Should().BeOfType <ChangeStreamDocumentSerializer <BsonDocument> >();
            result.ResumeAfter.Should().BeSameAs(options.ResumeAfter);
            result.RetryRequested.Should().Be(true);
            result.StartAfter.Should().BeSameAs(options.StartAfter);
            result.StartAtOperationTime.Should().BeSameAs(options.StartAtOperationTime);
        }
        public static IObservable <ChangeStreamDocument <T> > WhenCollectionChanges <T>(this IMongoCollection <T> collection, CancellationToken cancellationToken = default, params ChangeStreamOperationType[] operationTypes)
        {
            if (operationTypes.Length == 0)
            {
                operationTypes = new[] { ChangeStreamOperationType.Insert, ChangeStreamOperationType.Update, ChangeStreamOperationType.Replace, ChangeStreamOperationType.Delete };
            }

            // { operationType: { $in: [...] } }
            var filter = new FilterDefinitionBuilder <ChangeStreamDocument <T> >()
                         .In(d => d.OperationType, operationTypes);

            var pipelineDefinition = new EmptyPipelineDefinition <ChangeStreamDocument <T> >()
                                     .Match(filter);

            var options = new ChangeStreamOptions()
            {
                FullDocument = ChangeStreamFullDocumentOption.UpdateLookup
            };
            var changeStreamCursor = collection.Watch(pipelineDefinition, options, cancellationToken);

            return(Observable.Create <ChangeStreamDocument <T> >(observer =>
            {
                Task.Run(async() =>
                {
                    while (true)
                    {
                        if (await changeStreamCursor.MoveNextAsync(cancellationToken))
                        {
                            foreach (var c in changeStreamCursor.Current)
                            {
                                observer.OnNext(c);
                                cancellationToken.ThrowIfCancellationRequested();
                            }
                        }

                        cancellationToken.ThrowIfCancellationRequested();
                    }
                }, cancellationToken);

                return Disposable.Create(() => changeStreamCursor.Dispose());
            }));
        }