Overview
Puede consultar datos almacenados en MongoDB directamente desde el código de su aplicación cliente utilizando el SDK Swift de Realm. MongoClient con la API de consulta. Atlas App Services proporciona reglas de acceso a datos en colecciones para recuperar resultados de forma segura según el usuario conectado o el contenido de cada documento.
Tip
This page covers querying a MongoDB data source directly. To filter data you retrieve from a realm, see: Filter Data.
Casos de uso
Existen variedad de razones por las que podría querer query una fuente de datos de MongoDB. Trabajar con datos en tu cliente a través de Atlas Device Sync no siempre es práctico o posible. Podrías querer query MongoDB cuando:
The data set is large or the client device has constraints against loading the entire data set
Estás recuperando documentos que no están modelados en Realm
Your app needs to access collections that don't have strict schemas
A non-Realm service generates collections that you want to access
While not exhaustive, these are some common use cases for querying MongoDB directly.
Requisitos previos
Before you can query MongoDB from your client application, you must set up MongoDB Data Access in your App Services App. To learn how to set up your backend App to let the Realm SDK query Atlas, refer to Set Up MongoDB Data Access in the App Services documentation.
Datos de ejemplo
These examples operate on a MongoDB collection that describes coffee drinks in a chain of coffee shops. The documents represent objects with these properties:
class CoffeeDrink: Object { (primaryKey: true) var _id: ObjectId var name: String var beanRegion: String? var containsDairy: Bool var storeNumber: Int }
The complete code for each example includes logging in and instantiating a MongoDB collection handle before completing each operation. For brevity, these examples omit the login and collection handle code. However, each complete example looks like this:
appClient.login(credentials: Credentials.anonymous) { (result) in // Remember to dispatch back to the main thread in completion handlers // if you want to do anything on the UI. DispatchQueue.main.async { switch result { case .failure(let error): print("Login failed: \(error)") case .success(let user): print("Login as \(user) succeeded!") // mongodb-atlas is the cluster service name let mongoClient = user.mongoClient("mongodb-atlas") // Select the database let database = mongoClient.database(named: "ios") // Select the collection let collection = database.collection(withName: "CoffeeDrinks") // This document represents a CoffeeDrink object let drink: Document = [ "name": "Colombian Blend", "beanRegion": "Timbio, Colombia", "containsDairy": false, "storeNumber": 43] // Insert the document into the collection collection.insertOne(drink) { result in switch result { case .failure(let error): print("Call to MongoDB failed: \(error.localizedDescription)") return case .success(let objectId): // Success returns the objectId for the inserted document print("Successfully inserted a document with id: \(objectId)") } } } } }
Async/Await Query MongoDB
Novedad en la versión 10.16.0.
El SDK Realm Swift proporciona versiones async/await de los métodos MongoCollection.
Todos los métodos de esta página son compatibles con la sintaxis async/await. Este ejemplo ilustra la sintaxis para collection.insertOne() método. Puede ver la versión del controlador de finalización en Insertar un solo documento.
// This document represents a CoffeeDrink object let drink: Document = [ "name": "Bean of the Day", "beanRegion": "Timbio, Colombia", "containsDairy": false, "storeNumber": 43] do { // Use the async collection method to insert the document let objectId = try await collection.insertOne(drink) print("Successfully inserted a document with id: \(objectId)") } catch { print("Call to MongoDB failed: \(error.localizedDescription)") }
Starting with Realm Swift SDK Versions 10.15.0 and 10.16.0, many of the Realm APIs support the Swift async/await syntax. Projects must meet these requirements:
Versión del SDK de Swift | Swift Version Requirement | Supported OS |
|---|---|---|
10.25.0 | Swift 5.6 | iOS 13.x |
10.15.0 or 10.16.0 | Swift 5.5 | iOS 15.x |
If your app accesses Realm in an async/await context, mark the code with @MainActor to avoid threading-related crashes.
Crear documentos
These code snippets demonstrate how to insert one or more documents into a MongoDB collection from a mobile application. These methods take one or more documents and return a result. Success returns the objectId of the inserted document, or an array of objectIds in order when inserting multiple documents.
Inserta un solo documento
Puedes insertar un solo documento utilizando colección.insertOne().
This snippet inserts a single document describing a "Colombian Blend" coffee drink into a collection of documents that describe coffee drinks for sale in a group of stores:
// This document represents a CoffeeDrink object let drink: Document = [ "name": "Colombian Blend", "beanRegion": "Timbio, Colombia", "containsDairy": false, "storeNumber": 43] // Insert the document into the collection collection.insertOne(drink) { result in switch result { case .failure(let error): print("Call to MongoDB failed: \(error.localizedDescription)") return case .success(let objectId): // Success returns the objectId for the inserted document print("Successfully inserted a document with id: \(objectId)") } }
Successfully inserted a document with id: objectId(64e50cab7765243942cd04ce)
Inserta varios documentos
Puede insertar varios documentos utilizando collection.insertMany().
This snippet inserts three documents describing coffee drinks into a collection of documents that describe coffee drinks for sale in a group of stores:
let drink: Document = [ "name": "Bean of the Day", "beanRegion": "Timbio, Colombia", "containsDairy": false, "storeNumber": 42] let drink2: Document = [ "name": "Maple Latte", "beanRegion": "Yirgacheffe, Ethiopia", "containsDairy": true, "storeNumber": 42] let drink3: Document = [ "name": "Bean of the Day", "beanRegion": "San Marcos, Guatemala", "containsDairy": false, "storeNumber": 47] // Insert the documents into the collection collection.insertMany([drink, drink2, drink3]) { result in switch result { case .failure(let error): print("Call to MongoDB failed: \(error.localizedDescription)") return case .success(let objectIds): print("Successfully inserted \(objectIds.count) new documents.") } }
Successfully inserted 3 new documents.
Lea los documentos
Estos fragmentos de código muestran cómo leer datos almacenados en una colección de MongoDB desde una aplicación móvil. Las operaciones de lectura utilizan una sintaxis de consulta estándar para especificar los documentos que se devolverán de la base de datos. Devuelven un resultado que se resuelve como un solo documento coincidente (en el caso findOneDocument() de), un long valor numérico (en el caso count() de) o una matriz de documentos coincidentes (en el caso find() de).
Find a Single Document
Puede encontrar un solo documento utilizando collection.findOneDocument().
Este snippet encuentra un solo documento de una colección de documentos que describen bebidas de café a la venta en un grupo de tiendas, donde el campo name del documento contiene el valor de cadena "Colombian Blend":
let queryFilter: Document = ["name": "Colombian Blend"] collection.findOneDocument(filter: queryFilter) { result in switch result { case .failure(let error): print("Did not find matching documents: \(error.localizedDescription)") return case .success(let document): print("Found a matching document: \(String(describing: document))") } }
Found a matching document: Optional([ "name": Optional(RealmSwift.AnyBSON.string("Colombian Blend")), "_id": Optional(RealmSwift.AnyBSON.objectId(64e5014f65796c813bc68274)), "beanRegion": Optional(RealmSwift.AnyBSON.string("Timbio, Colombia")), "containsDairy": Optional(RealmSwift.AnyBSON.bool(false)), "storeNumber": Optional(RealmSwift.AnyBSON.int64(43)) ])
Encuentra varios documentos
You can find multiple documents using collection.find().
This snippet finds all documents in a collection of documents that describe coffee drinks for sale in a group of stores, where the document's name field contains the value "Americano":
let queryFilter: Document = ["name": "Americano"] collection.find(filter: queryFilter) { result in switch result { case .failure(let error): print("Call to MongoDB failed: \(error.localizedDescription)") return case .success(let bsonDocumentArray): print("Results: ") for bsonDocument in bsonDocumentArray { print("Coffee drink named: \(String(describing: bsonDocument["name"]))") } } }
Results: Coffee drink named: Optional(Optional(RealmSwift.AnyBSON.string("Americano"))) ... more matching documents ...
Find and Sort Documents
Puede ordenar los documentos de una colección inicializando una instancia de FindOptions con sus opciones deFindOptions ordenación preferidas. tiene tres parámetros: límite, proyección y ordenación. El sorting argumento puede ser una matriz de pares clave-valor, donde cada clave representa un campo. Para cada clave, el valor 1 ordena en orden descendente, o -1 en orden ascendente.
You then pass the FindOptions instance to the collection.find() method when running the query.
Este fragmento encuentra todos los documentos en una colección de documentos que describen bebidas de café a la venta en un grupo de tiendas, donde el campo del documento name contiene el valor "Americano", ordenado en orden descendente por el beanRegion campo:
let queryFilter: Document = ["name": "Americano"] let findOptions = FindOptions(0, nil, [["beanRegion": 1]]) collection.find(filter: queryFilter, options: findOptions) { result in switch result { case .failure(let error): print("Call to MongoDB failed: \(error.localizedDescription)") return case .success(let documents): print("Results: ") for document in documents { print("Coffee drink: \(document)") } } }
Results: Coffee drink: [ "_id": Optional(RealmSwift.AnyBSON.objectId(64e521ed6b124fd047534345)), "beanRegion": Optional(RealmSwift.AnyBSON.string("San Marcos, Guatemala")), "name": Optional(RealmSwift.AnyBSON.string("Americano")), "containsDairy": Optional(RealmSwift.AnyBSON.bool(true)), "storeNumber": Optional(RealmSwift.AnyBSON.int64(42)) ] Coffee drink: [ "_id": Optional(RealmSwift.AnyBSON.objectId(64e521ed6b124fd047534344)), "storeNumber": Optional(RealmSwift.AnyBSON.int64(42)), "containsDairy": Optional(RealmSwift.AnyBSON.bool(false)), "name": Optional(RealmSwift.AnyBSON.string("Americano")), "beanRegion": Optional(RealmSwift.AnyBSON.string("Timbio, Colombia")) ] ... more matching documents, sorted by `beanRegion` ...
Contar documentos en la colección
You can count documents in a collection using collection.count(). You can specify an optional query and limit to determine which documents to count. If you don't specify a query, the action counts all documents in the collection.
This snippet counts the number of documents in a collection of documents that describe coffee drinks for sale in a group of stores, where the document's name field contains the value "Bean of the Day":
let queryFilter: Document = ["name": "Bean of the Day"] collection.count(filter: queryFilter) { result in switch result { case .failure(let error): print("Call to MongoDB failed: \(error.localizedDescription)") return case .success(let count): print("Found this many documents in the collection matching the filter: \(count)") } }
Found this many documents in the collection matching the filter: 24
Update Documents
Estos fragmentos de código muestran cómo actualizar los datos almacenados en una colección de MongoDB desde una aplicación móvil. Las operaciones de actualización utilizan consultas para especificar qué documentos actualizar y operadores de actualización para describir cómo modificar los documentos que coinciden con la consulta. Las operaciones de actualización devuelven un resultado que se resuelve como UpdateResult Erroro.
Actualiza un solo documento
Puede actualizar un solo documento utilizando collection.updateOneDocument().
Este fragmento actualiza un documento de una colección que describe bebidas de café a la venta en un grupo de tiendas. Esta operación de actualización busca un documento cuyo name campo contiene el valor "Grano del día" y establece el containsDairy campo true en:
let queryFilter: Document = ["name": "Bean of the Day", "storeNumber": 42] let documentUpdate: Document = ["$set": ["containsDairy": true]] collection.updateOneDocument(filter: queryFilter, update: documentUpdate) { result in switch result { case .failure(let error): print("Failed to update document: \(error.localizedDescription)") return case .success(let updateResult): if updateResult.matchedCount == 1 && updateResult.modifiedCount == 1 { print("Successfully updated a matching document.") } else { print("Did not update a document") } }
Successfully updated a matching document.
Actualiza varios documentos
Puedes actualizar varios documentos usando colección.updateManyDocuments().
This snippet updates multiple documents in a collection of documents that describe coffee drinks for sale in a group of stores. This update operation queries for documents where the name field contains the value "Bean of the Day", and changes the containsDairy field to true:
let queryFilter: Document = ["name": "Bean of the Day"] let documentUpdate: Document = ["$set": ["containsDairy": true]] collection.updateManyDocuments(filter: queryFilter, update: documentUpdate) { result in switch result { case .failure(let error): print("Failed to update document: \(error.localizedDescription)") return case .success(let updateResult): print("Successfully updated \(updateResult.modifiedCount) documents.") } }
Successfully updated 24 documents.
Realizar inserción de documentos
Si una operación de actualización no coincide con ningún documento en la colección, puedes insertar automáticamente un nuevo documento en la colección que coincida con la query de actualización configurando la opción upsert en true.
El siguiente fragmento actualiza un documento de una colección que describe bebidas de café a la venta en un grupo de tiendas. Si ningún documento coincide con la consulta, se inserta uno nuevo. Esta operación busca documentos cuyo name campo tenga el valor "Grano del día" y el storeNumber campo tenga el 55 valor.
Because this snippet sets the upsert option to true, if no document matches the query, MongoDB creates a new document that includes both the query and specified updates:
let queryFilter: Document = ["name": "Bean of the Day", "storeNumber": 55] let documentUpdate: Document = ["name": "Bean of the Day", "beanRegion": "Yirgacheffe, Ethiopia", "containsDairy": false, "storeNumber": 55] collection.updateOneDocument(filter: queryFilter, update: documentUpdate, upsert: true) { result in switch result { case .failure(let error): print("Failed to update document: \(error.localizedDescription)") return case .success(let updateResult): if let unwrappedDocumentId = updateResult.documentId { print("Successfully upserted a document with id: \(unwrappedDocumentId)") } else { print("Did not upsert a document") } } }
Successfully upserted a document with id: 64e523e37765243942eba44a
Delete Documents
Estos snippets demuestran cómo borrar documentos almacenados en una colección de MongoDB desde una aplicación móvil. Las operaciones de borrado utilizan una query para especificar qué documentos borrar y devolver resultados que resuelven en una cuenta Int de documentos borrados o Error.
Borrar un único documento
Puede eliminar un solo documento de una colección utilizando collection.deleteOneDocument().
Este fragmento elimina un documento de una colección que describe bebidas de café a la venta en un grupo de tiendas. Esta operación busca un documento cuyo name campo tenga el valor "Mocha" y el storeNumber campo tenga el valor,17 y lo elimina.
let queryFilter: Document = ["name": "Mocha", "storeNumber": 17] collection.deleteOneDocument(filter: queryFilter) { deletedResult in switch deletedResult { case .failure(let error): print("Failed to delete a document: \(error.localizedDescription)") return case .success(let deletedResult): print("Successfully deleted a document.") } }
Successfully deleted a document.
Borra varios documentos
You can delete multiple items from a collection using collection.deleteManyDocuments().
This snippet deletes all documents in a collection of documents that describe coffee drinks for sale in a group of stores that match the query for documents whose name field contains the value "Caramel Latte":
let filter: Document = ["name": "Caramel Latte"] collection.deleteManyDocuments(filter: filter) { deletedResult in switch deletedResult { case .failure(let error): print("Failed to delete a document: \(error.localizedDescription)") return case .success(let deletedResult): print("Successfully deleted \(deletedResult) documents.") } }
Successfully deleted 3 documents.
Esté atento a los cambios
Puede supervisar una colección para ver las notificaciones de cambio que MongoDB emite cada vez que se crea, modifica o elimina un documento de la colección. Cada notificación especifica el documento que cambió, cómo cambió y el documento completo después de la operación que provocó el evento.
Importante
Serverless Limitations
No puedes observar cambios si la fuente de datos es una instancia sin servidor de Atlas. El servidor sin servidor de MongoDB actualmente no admite flujos de cambios, que se emplean en las colecciones monitoreadas para escuchar cambios.
Esté atento a los cambios en una colección
Puedes abrir un flujo de cambios realizados en una colección llamando a collection.watch(). Esta función crea un publicador que emite un evento de cambio AnyBSON cuando cambia la colección de MongoDB.
You can optionally watch a filtered list of _ids in the collection with collection.watch(filterIds:), or apply a $match filter to incoming change events with collection.watch(matchFilter:).
The .watch() method can take a ChangeEventDelegate to subscribe to changes on a stream. For example, with this ChangeEventDelegate:
class MyChangeStreamDelegate: ChangeEventDelegate { func changeStreamDidOpen(_ changeStream: RealmSwift.ChangeStream) { print("Change stream opened: \(changeStream)") } func changeStreamDidClose(with error: Error?) { if let anError = error { print("Change stream closed with error: \(anError.localizedDescription)") } else { print("Change stream closed") } } func changeStreamDidReceive(error: Error) { print("Received error: \(error.localizedDescription)") } func changeStreamDidReceive(changeEvent: RealmSwift.AnyBSON?) { guard let changeEvent = changeEvent else { return } guard let document = changeEvent.documentValue else { return } print("Change event document received: \(document)") } }
This code watches for changes to documents in the CoffeeDrinks collection:
appClient.login(credentials: Credentials.anonymous) { (result) in DispatchQueue.main.async { switch result { case .failure(let error): print("Login failed: \(error)") case .success(let user): print("Login as \(user) succeeded!") // Continue below } // Set up the client, database, and collection. let client = self.appClient.currentUser!.mongoClient("mongodb-atlas") let database = client.database(named: "ios") let collection = database.collection(withName: "CoffeeDrinks") // Watch the collection. In this example, we use a queue and delegate, // both of which are optional arguments. let queue = DispatchQueue(label: "io.realm.watchQueue") let delegate = MyChangeStreamDelegate() let changeStream = collection.watch(delegate: delegate, queue: queue) // Adding a document triggers a change event. let drink: Document = [ "name": "Bean of the Day", "beanRegion": "Timbio, Colombia", "containsDairy": false, "storeNumber": 42] collection.insertOne(drink) { result in switch result { case .failure(let error): print("Call to MongoDB failed: \(error.localizedDescription)") return case .success(let objectId): print("Successfully inserted a document with id: \(objectId)") } } // After you're done watching for events, close the change stream. changeStream.close() } }
Login as <RLMUser: 0x600002dfd1a0> succeeded! Change stream opened: <RLMChangeStream: 0x60000182ab80> Successfully inserted a document with id: objectId(64e525665fef1743dedb5aa6) Change event document received: [ "clusterTime": Optional(RealmSwift.AnyBSON.datetime(2023-08-22 21:15:18 +0000)), "_id": Optional(RealmSwift.AnyBSON.document([ "_data": Optional(RealmSwift.AnyBSON.string( "8264E525660000000B2B022C0100296E5A100464816C3449884434A07AC19F4AAFCB8046645F6964006464E525665FEF1743DEDB5AA60004" )) ])), "documentKey": Optional(RealmSwift.AnyBSON.document([ "_id": Optional(RealmSwift.AnyBSON.objectId(64e525665fef1743dedb5aa6)) ])), "ns": Optional(RealmSwift.AnyBSON.document([ "coll": Optional(RealmSwift.AnyBSON.string("CoffeeDrinks")), "db": Optional(RealmSwift.AnyBSON.string("ios")) ])), "operationType": Optional(RealmSwift.AnyBSON.string("insert")), "fullDocument": Optional(RealmSwift.AnyBSON.document([ "name": Optional(RealmSwift.AnyBSON.string("Bean of the Day")), "storeNumber": Optional(RealmSwift.AnyBSON.int64(42)), "beanRegion": Optional(RealmSwift.AnyBSON.string("Timbio, Colombia")), "_id": Optional(RealmSwift.AnyBSON.objectId(64e525665fef1743dedb5aa6)), "containsDairy": Optional(RealmSwift.AnyBSON.bool(false)) ]))] Change stream closed
Watch for Changes in a Collection to Specific IDs
Puede observar los cambios en una colección de una lista específica de objetos pasando _id su. Llame a collection.watch(filterIds: ) con una matriz de ObjectIds para recibir solo los eventos de cambio que se apliquen a esos documentos.
Considera un ejemplo que utiliza este Delegado de eventos de cambio:
class MyChangeStreamDelegate: ChangeEventDelegate { func changeStreamDidOpen(_ changeStream: RealmSwift.ChangeStream) { print("Change stream opened: \(changeStream)") } func changeStreamDidClose(with error: Error?) { if let anError = error { print("Change stream closed with error: \(anError.localizedDescription)") } else { print("Change stream closed") } } func changeStreamDidReceive(error: Error) { print("Received error: \(error.localizedDescription)") } func changeStreamDidReceive(changeEvent: RealmSwift.AnyBSON?) { guard let changeEvent = changeEvent else { return } guard let document = changeEvent.documentValue else { return } print("Change event document received: \(document)") } }
The code below uses this delegate to watch for changes to specific documents in the CoffeeDrinks collection:
appClient.login(credentials: Credentials.anonymous) { (result) in DispatchQueue.main.async { switch result { case .failure(let error): print("Login failed: \(error)") case .success(let user): print("Login as \(user) succeeded!") // Continue below } // Set up the client, database, and collection. let client = self.appClient.currentUser!.mongoClient("mongodb-atlas") let database = client.database(named: "ios") let collection = database.collection(withName: "CoffeeDrinks") // Watch the collection. In this example, we use a queue and delegate, // both of which are optional arguments. // `filterIds` is an array of specific document ObjectIds you want to watch. let queue = DispatchQueue(label: "io.realm.watchQueue") let delegate = MyChangeStreamDelegate() let changeStream = collection.watch(filterIds: [drinkObjectId], delegate: delegate, queue: queue) // An update to a relevant document triggers a change event. let queryFilter: Document = ["_id": AnyBSON(drinkObjectId) ] let documentUpdate: Document = ["$set": ["containsDairy": true]] collection.updateOneDocument(filter: queryFilter, update: documentUpdate) { result in switch result { case .failure(let error): print("Call to MongoDB failed: \(error.localizedDescription)") return case .success(let updateResult): print("Successfully updated the document") } } // After you're done watching for events, close the change stream. changeStream.close() } }
Login as <RLMUser: 0x60000010eb00> succeeded! Successfully inserted a document with id: objectId(64e525ce7765243942ef0a58) Change stream opened: <RLMChangeStream: 0x6000034946c0> Change event document received: [ "fullDocument": Optional(RealmSwift.AnyBSON.document([ "containsDairy": Optional(RealmSwift.AnyBSON.bool(true)), "beanRegion": Optional(RealmSwift.AnyBSON.string("Timbio, Colombia")), "_id": Optional(RealmSwift.AnyBSON.objectId(64e525ce7765243942ef0a58)), "name": Optional(RealmSwift.AnyBSON.string("Bean of the Day")), "storeNumber": Optional(RealmSwift.AnyBSON.int64(42)) ])), "clusterTime": Optional(RealmSwift.AnyBSON.datetime(2023-08-22 21:17:09 +0000)), "operationType": Optional(RealmSwift.AnyBSON.string("update")), "documentKey": Optional(RealmSwift.AnyBSON.document([ "_id": Optional(RealmSwift.AnyBSON.objectId(64e525ce7765243942ef0a58)) ])), "ns": Optional(RealmSwift.AnyBSON.document([ "db": Optional(RealmSwift.AnyBSON.string("ios")), "coll": Optional(RealmSwift.AnyBSON.string("CoffeeDrinks")) ])), "updateDescription": Optional(RealmSwift.AnyBSON.document([ "removedFields": Optional(RealmSwift.AnyBSON.array([])), "updatedFields": Optional(RealmSwift.AnyBSON.document([ "containsDairy": Optional(RealmSwift.AnyBSON.bool(true))])) ])), "_id": Optional(RealmSwift.AnyBSON.document([ "_data": Optional(RealmSwift.AnyBSON.string( "8264E525D5000000082B022C0100296E5A100464816C3449884434A07AC19F4AAFCB8046645F6964006464E525CE7765243942EF0A580004" )) ]))] Change stream closed
Watch for Changes in a Collection with a Filter
Puede abrir un flujo de cambios realizados en los documentos de una colección que cumplan ciertos criterios llamando a collection.watch(matchFilter: ). Este método acepta un Document parámetro que se utiliza como consulta del operador $match para procesar cada evento de base de datos que ocurre durante la supervisión de la colección.
Considera un ejemplo que utiliza este Delegado de eventos de cambio:
class MyChangeStreamDelegate: ChangeEventDelegate { func changeStreamDidOpen(_ changeStream: RealmSwift.ChangeStream) { print("Change stream opened: \(changeStream)") } func changeStreamDidClose(with error: Error?) { if let anError = error { print("Change stream closed with error: \(anError.localizedDescription)") } else { print("Change stream closed") } } func changeStreamDidReceive(error: Error) { print("Received error: \(error.localizedDescription)") } func changeStreamDidReceive(changeEvent: RealmSwift.AnyBSON?) { guard let changeEvent = changeEvent else { return } guard let document = changeEvent.documentValue else { return } print("Change event document received: \(document)") } }
The code below uses this delegate to watch for changes to documents in the CoffeeDrink collection. It only triggers the provided callback for events whose documents have a storeNumber value of 42:
appClient.login(credentials: Credentials.anonymous) { (result) in DispatchQueue.main.async { switch result { case .failure(let error): print("Login failed: \(error)") case .success(let user): print("Login as \(user) succeeded!") // Continue below } // Set up the client, database, and collection. let client = self.appClient.currentUser!.mongoClient("mongodb-atlas") let database = client.database(named: "ios") let collection = database.collection(withName: "CoffeeDrinks") // Watch the collection. In this example, we use a queue and delegate, // both of which are optional arguments. let queue = DispatchQueue(label: "io.realm.watchQueue") let delegate = MyChangeStreamDelegate() let matchFilter = [ "fullDocument.storeNumber": AnyBSON(42) ] let changeStream = collection.watch(matchFilter: matchFilter, delegate: delegate, queue: queue) // An update to a relevant document triggers a change event. let queryFilter: Document = ["_id": AnyBSON(drinkObjectId) ] let documentUpdate: Document = ["$set": ["containsDairy": true]] collection.updateOneDocument(filter: queryFilter, update: documentUpdate) { result in switch result { case .failure(let error): print("Call to MongoDB failed: \(error.localizedDescription)") return case .success(let updateResult): print("Successfully updated the document") } } // After you're done watching for events, close the change stream. changeStream.close() } }
Login as <RLMUser: 0x6000026ee640> succeeded! Successfully inserted a document with id: objectId(64e5266731323150716faf13) Change stream opened: <RLMChangeStream: 0x6000013283c0> Change event document received: [ "operationType": Optional(RealmSwift.AnyBSON.string("update")), "updateDescription": Optional(RealmSwift.AnyBSON.document([ "removedFields": Optional(RealmSwift.AnyBSON.array([])), "updatedFields": Optional(RealmSwift.AnyBSON.document([ "containsDairy": Optional(RealmSwift.AnyBSON.bool(true)) ])) ])), "clusterTime": Optional(RealmSwift.AnyBSON.datetime(2023-08-22 21:19:44 +0000)), "_id": Optional(RealmSwift.AnyBSON.document([ "_data": Optional(RealmSwift.AnyBSON.string( "8264E526700000000E2B022C0100296E5A100464816C3449884434A07AC19F4AAFCB8046645F6964006464E5266731323150716FAF130004" )) ])), "ns": Optional(RealmSwift.AnyBSON.document([ "db": Optional(RealmSwift.AnyBSON.string("ios")), "coll": Optional(RealmSwift.AnyBSON.string("CoffeeDrinks")) ])), "fullDocument": Optional(RealmSwift.AnyBSON.document([ "_id": Optional(RealmSwift.AnyBSON.objectId(64e5266731323150716faf13)), "name": Optional(RealmSwift.AnyBSON.string("Bean of the Day")), "containsDairy": Optional(RealmSwift.AnyBSON.bool(true)), "beanRegion": Optional(RealmSwift.AnyBSON.string("Timbio, Colombia")), "storeNumber": Optional(RealmSwift.AnyBSON.int64(42)) ])), "documentKey": Optional(RealmSwift.AnyBSON.document([ "_id": Optional(RealmSwift.AnyBSON.objectId(64e5266731323150716faf13)) ]))] Successfully updated the document Change stream closed
Vigilar una Colección como una Secuencia Asíncrona
Novedad en la versión 10.37.0.
You can open an async sequence to watch for changes to a collection. In an async context, call changeEvents() on a collection to open a change stream. This provides an async sequence of AnyBSON values containing information about each change to the MongoDB collection.
Opcionalmente, puede proporcionar una devolución de llamada changeEvents(onOpen: ) que se invoca cuando la transmisión de observación se ha inicializado en el servidor.
The changeEvents() API can take filterIds or a matchFilter to watch a subset of documents in a collection, similar to the examples above.
The following snippet watches for changes to any documents in the CoffeeDrinks collection as an async sequence:
let user = try await appClient.login(credentials: Credentials.anonymous) // Set up the client, database, and collection. let mongoClient = user.mongoClient("mongodb-atlas") let database = mongoClient.database(named: "ios") let collection = database.collection(withName: "CoffeeDrinks") // Set up a task you'll later await to keep the change stream open, // and you can cancel it when you're done watching for events. let task = Task { // Open the change stream. let changeEvents = collection.changeEvents(onOpen: { print("Successfully opened change stream") }) // Await events in the change stream. for try await event in changeEvents { let doc = event.documentValue! print("Received event: \(event.documentValue!)") } } // Updating a document in the collection triggers a change event. let queryFilter: Document = ["_id": AnyBSON(objectId) ] let documentUpdate: Document = ["$set": ["containsDairy": true]] let updateResult = try await collection.updateOneDocument(filter: queryFilter, update: documentUpdate) // Cancel the task when you're done watching the stream. task.cancel() _ = await task.result
Successfully opened change stream Received event: [ "operationType": Optional(RealmSwift.AnyBSON.string("update")), "documentKey": Optional(RealmSwift.AnyBSON.document([ "_id": Optional(RealmSwift.AnyBSON.objectId(64e526d9850b15debe83ff46)) ])), "ns": Optional(RealmSwift.AnyBSON.document([ "coll": Optional(RealmSwift.AnyBSON.string("CoffeeDrinks")), "db": Optional(RealmSwift.AnyBSON.string("ios")) ])), "clusterTime": Optional(RealmSwift.AnyBSON.datetime(2023-08-22 21:21:30 +0000)), "fullDocument": Optional(RealmSwift.AnyBSON.document([ "name": Optional(RealmSwift.AnyBSON.string("Bean of the Day")), "containsDairy": Optional(RealmSwift.AnyBSON.bool(true)), "storeNumber": Optional(RealmSwift.AnyBSON.int64(43)), "_id": Optional(RealmSwift.AnyBSON.objectId(64e526d9850b15debe83ff46)), "beanRegion": Optional(RealmSwift.AnyBSON.string("Timbio, Colombia")) ])), "_id": Optional(RealmSwift.AnyBSON.document([ "_data": Optional(RealmSwift.AnyBSON.string( "8264E526DA000000092B022C0100296E5A100464816C3449884434A07AC19F4AAFCB8046645F6964006464E526D9850B15DEBE83FF460004" ) ) ])), "updateDescription": Optional(RealmSwift.AnyBSON.document([ "updatedFields": Optional(RealmSwift.AnyBSON.document([ "containsDairy": Optional(RealmSwift.AnyBSON.bool(true)) ])), "removedFields": Optional(RealmSwift.AnyBSON.array([])) ]))]
Documentos agregados
Las operaciones de agregación ejecutan todos los documentos de una colección a través de una serie de etapas de agregación de datos denominadas canalización de agregación. La agregación permite filtrar y transformar documentos, recopilar datos resumidos sobre grupos de documentos relacionados y realizar otras operaciones complejas de datos.
Se pueden configurar y ejecutar operaciones de agregación en una colección mediante collection.aggregate().
An aggregation operation accepts a list of aggregation stages as input and returns a result that resolves to a collection of documents processed by the pipeline, or an Error.
Filtrar Documentos
You can use the $match stage to filter documents using standard MongoDB query syntax:
This $match stage filters documents to include only those where the storeNumber field has a value equal to 42:
let pipeline: [Document] = [["$match": ["storeNumber": ["$eq": 42]]]] collection.aggregate(pipeline: pipeline) { result in switch result { case .failure(let error): print("Failed to aggregate: \(error.localizedDescription)") return case .success(let documents): print("Successfully ran the aggregation:") for document in documents { print("Coffee drink: \(document)") } } }
Successfully ran the aggregation: Coffee drink: [ "_id": Optional(RealmSwift.AnyBSON.objectId(64e53171313231507183e7c2)), "name": Optional(RealmSwift.AnyBSON.string("Bean of the Day")), "containsDairy": Optional(RealmSwift.AnyBSON.bool(false)), "beanRegion": Optional(RealmSwift.AnyBSON.string("Timbio, Colombia")), "storeNumber": Optional(RealmSwift.AnyBSON.int64(42))] Coffee drink: [ "containsDairy": Optional(RealmSwift.AnyBSON.bool(true)), "name": Optional(RealmSwift.AnyBSON.string("Maple Latte")), "beanRegion": Optional(RealmSwift.AnyBSON.string("Yirgacheffe, Ethiopia")), "_id": Optional(RealmSwift.AnyBSON.objectId(64e53171313231507183e7c3)), "storeNumber": Optional(RealmSwift.AnyBSON.int64(42))] (...more results...)
Documentos de grupo
You can use the $group stage to aggregate summary data for one or more documents. MongoDB groups documents based on the expression defined in the _id field of the $group stage. You can reference a specific document field by prefixing the field name with a $.
This $group stage arranges documents by the value of their storeNumber field. It then calculates the number of coffee drink documents that contain that store number in the field's value. In other words, we're calculating the number of coffee drinks for each store number.
let pipeline: [Document] = [["$group": ["_id": "$storeNumber", "numItems": ["$sum": 1]]]] collection.aggregate(pipeline: pipeline) { result in switch result { case .failure(let error): print("Failed to aggregate: \(error.localizedDescription)") return case .success(let results): print("Successfully ran the aggregation.") for result in results { print(result) } } }
Successfully ran the aggregation. ["numItems": Optional(RealmSwift.AnyBSON.int64(27)), "_id": Optional(RealmSwift.AnyBSON.int64(42))] ["numItems": Optional(RealmSwift.AnyBSON.int64(44)), "_id": Optional(RealmSwift.AnyBSON.int64(47))] (...more results...)
Campos del documento del proyecto
Puede usar la etapa $project para incluir u omitir campos específicos de los documentos o para calcular nuevos campos mediante operadores de agregación. Las proyecciones funcionan de dos maneras:
Especifica que quieres incluir campos usando un
1. Esto tiene el efecto secundario de excluir, de forma implícita, todos los campos no especificados.Especifique que desea excluir campos utilizando un
0. Esto tiene como efecto secundario incluir implícitamente todos los campos no especificados.
These two methods of projection are mutually exclusive. If you specify fields to include, you cannot also specify fields to exclude, and vice versa.
Nota
The _id field is a special case: it is always included in every query unless explicitly specified otherwise. For this reason, you can exclude the _id field with a 0 value while simultaneously including other fields, like storeNumber, with a 1. Only the special case of exclusion of the _id field allows both exclusion and inclusion in one $project stage.
Para este ejemplo, supongamos que el documento CoffeeDrink tiene un campo store que es un valor de cadena que contiene la palabra "almacenar" con un número, como "almacenar 42", similar a estos documentos:
let drink: Document = [ "name": "Bean of the Day", "beanRegion": "Timbio, Colombia", "containsDairy": false, "store": "Store 42"] let drink2: Document = [ "name": "Bean of the Day", "beanRegion": "Yirgacheffe, Ethiopia", "containsDairy": true, "store": "Store 47"]
The following $project stage omits the _id field, includes the name field, and creates a new field named storeNumber. The storeNumber is generated using two aggregation operators:
$splitseparates thestorestring representation into two string segments surrounding the space character. For example, the value "Store 42" split in this way returns an array with two elements: "Store" and "42".$arrayElemAtselects a specific element from an array based on the second argument. In this case, the value1selects the second element from the array generated by the$splitoperator since arrays index from0. For example, the value ["Store", "42"] passed to this operation would return a value of "42".
let pipeline: [Document] = [["$project": ["_id": 0, "name": 1, "storeNumber": ["$arrayElemAt": [["$split": ["$store", " "]], 1]]]]] collection.aggregate(pipeline: pipeline) { result in switch result { case .failure(let error): print("Failed to aggregate: \(error.localizedDescription)") return case .success(let results): print("Successfully ran the aggregation.") for result in results { print(result) } } }
Successfully ran the aggregation. ["name": Optional(RealmSwift.AnyBSON.string("Bean of the Day")), "storeNumber": Optional(RealmSwift.AnyBSON.string("42"))] ["storeNumber": Optional(RealmSwift.AnyBSON.string("47")), "name": Optional(RealmSwift.AnyBSON.string("Bean of the Day"))] (...more results...)
Agregar campos a los documentos
Puedes utilizar la etapa $addFields para añadir nuevos campos con valores calculados utilizando operadores de agregación.
Nota
$addFields es similar a $project pero no te permite incluir ni omitir campos.
Para este ejemplo, supongamos que el documento CoffeeDrink tiene un campo store que es un valor de cadena que contiene la palabra "almacenar" con un número, como "almacenar 42", similar a estos documentos:
let drink: Document = [ "name": "Bean of the Day", "beanRegion": "Timbio, Colombia", "containsDairy": false, "store": "Store 42"] let drink2: Document = [ "name": "Bean of the Day", "beanRegion": "Yirgacheffe, Ethiopia", "containsDairy": true, "store": "Store 47"]
The following $addFields stage creates a new field named storeNumber where the value is the output of two aggregate operators that transform the value of the store field.
let pipeline: [Document] = [["$addFields": ["storeNumber": ["$arrayElemAt": [["$split": ["$store", " "]], 1]]]]] collection.aggregate(pipeline: pipeline) { result in switch result { case .failure(let error): print("Failed to aggregate: \(error.localizedDescription)") return case .success(let results): print("Successfully ran the aggregation.") for result in results { print(result) } } }
Successfully ran the aggregation. [ "storeNumber": Optional(RealmSwift.AnyBSON.string("42")), "name": Optional(RealmSwift.AnyBSON.string("Bean of the Day")), "_id": Optional(RealmSwift.AnyBSON.objectId(64e588ff5fef1743de3559aa)), "store": Optional(RealmSwift.AnyBSON.string("Store 42")), "containsDairy": Optional(RealmSwift.AnyBSON.bool(false)), "beanRegion": Optional(RealmSwift.AnyBSON.string("Timbio, Colombia"))] [ "containsDairy": Optional(RealmSwift.AnyBSON.bool(true)), "storeNumber": Optional(RealmSwift.AnyBSON.string("47")), "store": Optional(RealmSwift.AnyBSON.string("Store 47")), "beanRegion": Optional(RealmSwift.AnyBSON.string("Yirgacheffe, Ethiopia")), "name": Optional(RealmSwift.AnyBSON.string("Bean of the Day")), "_id": Optional(RealmSwift.AnyBSON.objectId(64e588ff5fef1743de3559ab))]
Unwind Array Values
You can use the $unwind stage to transform a single document containing an array into multiple documents containing individual values from that array. When you unwind an array field, MongoDB copies each document once for each element of the array field but replaces the array value with the array element in each copy.
Consider this document that includes a featuredInPromotions array:
let drink: Document = [ "name": "Maple Latte", "beanRegion": "Yirgacheffe, Ethiopia", "containsDairy": true, "storeNumber": 42, "featuredInPromotions": [ "Spring into Spring", "Tastes of Fall", "Winter Delights" ] ]
La siguiente etapa $unwind crea un documento nuevo para cada elemento del arreglo items en cada documento. También agrega un campo llamado itemIndex a cada nuevo documento que especifica el índice de posición del elemento en el arreglo original:
let pipeline: [Document] = [["$unwind": ["path": "$featuredInPromotions", "includeArrayIndex": "itemIndex"]]] collection.aggregate(pipeline: pipeline) { result in switch result { case .failure(let error): print("Failed to aggregate: \(error.localizedDescription)") return case .success(let results): print("Successfully ran the aggregation.") for result in results { print("Coffee drink: \(result)") } } }
Successfully ran the aggregation. Coffee drink: [ "_id": Optional(RealmSwift.AnyBSON.objectId(64e58bb4fc901d40e03fde64)), "storeNumber": Optional(RealmSwift.AnyBSON.int64(42)), "beanRegion": Optional(RealmSwift.AnyBSON.string("Yirgacheffe, Ethiopia")), "featuredInPromotions": Optional(RealmSwift.AnyBSON.string("Spring into Spring")), "itemIndex": Optional(RealmSwift.AnyBSON.int64(0)), "name": Optional(RealmSwift.AnyBSON.string("Maple Latte")), "containsDairy": Optional(RealmSwift.AnyBSON.bool(true))] Coffee drink: [ "itemIndex": Optional(RealmSwift.AnyBSON.int64(1)), "name": Optional(RealmSwift.AnyBSON.string("Maple Latte")), "_id": Optional(RealmSwift.AnyBSON.objectId(64e58bb4fc901d40e03fde64)), "featuredInPromotions": Optional(RealmSwift.AnyBSON.string("Tastes of Fall")), "storeNumber": Optional(RealmSwift.AnyBSON.int64(42)), "containsDairy": Optional(RealmSwift.AnyBSON.bool(true)), "beanRegion": Optional(RealmSwift.AnyBSON.string("Yirgacheffe, Ethiopia"))] Coffee drink: [ "name": Optional(RealmSwift.AnyBSON.string("Maple Latte")), "_id": Optional(RealmSwift.AnyBSON.objectId(64e58bb4fc901d40e03fde64)), "beanRegion": Optional(RealmSwift.AnyBSON.string("Yirgacheffe, Ethiopia")), "itemIndex": Optional(RealmSwift.AnyBSON.int64(2)), "containsDairy": Optional(RealmSwift.AnyBSON.bool(true)), "storeNumber": Optional(RealmSwift.AnyBSON.int64(42)), "featuredInPromotions": Optional(RealmSwift.AnyBSON.string("Winter Delights"))]
Luego, puede por $group el valor de y featuredInPromotions $sum el número de bebidas de café en cada promoción como en el ejemplo de los documentos de grupo, o realizar otros cálculos o transformaciones en función de sus datos.