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
Esta página explica cómo consultar directamente una fuente de datos de MongoDB. Para filtrar los datos recuperados de un dominio, consulte: Filtrar datos.
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:
El conjunto de datos es grande o el dispositivo cliente tiene restricciones para cargar todo el conjunto de datos
Estás recuperando documentos que no están modelados en Realm
Su aplicación necesita acceder a colecciones que no tienen esquemas estrictos
Un servicio que no es de Realm genera colecciones a las que desea acceder
Si bien no son exhaustivos, estos son algunos casos de uso comunes para consultar MongoDB directamente.
Requisitos previos
Antes de poder consultar MongoDB desde su aplicación cliente, debe configurar el acceso a datos de MongoDB en su aplicación de App Services. Para saber cómo configurar su aplicación backend para que el SDK de Realm pueda consultar Atlas, consulte "Configurar el acceso a datos de MongoDB" en la documentación de App Services.
Datos de ejemplo
Estos ejemplos operan en una colección de MongoDB que describe las bebidas de café de una cadena de cafeterías. Los documentos representan objetos con las siguientes propiedades:
class CoffeeDrink: Object { (primaryKey: true) var _id: ObjectId var name: String var beanRegion: String? var containsDairy: Bool var storeNumber: Int }
El código completo de cada ejemplo incluye el inicio de sesión y la instanciación de un identificador de colección de MongoDB antes de completar cada operación. Para abreviar, estos ejemplos omiten el código de inicio de sesión y del identificador de colección. Sin embargo, cada ejemplo completo se ve así:
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)") } } } } }
Consulta asíncrona/en espera MongoDB
Nuevo 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)") }
A partir de las versiones 10.15.0 y 10.16.0 del SDK de Realm Swift, muchas de las API de Realm admiten la sintaxis async/await de Swift. Los proyectos deben cumplir estos requisitos:
Versión del SDK de Swift | Requisito de versión Swift | Sistema operativo compatible |
|---|---|---|
10.25.0 | Swift 5.6 | iOS 13.x |
10.15.0 o 10.16.0 | Swift 5.5 | iOS 15.x |
Si su aplicación accede a Realm en un contexto async/await, marque el código con @MainActor para evitar fallas relacionadas con subprocesos.
Crear documentos
Estos fragmentos de código muestran cómo insertar uno o más documentos en una colección de MongoDB desde una aplicación móvil. Estos métodos toman uno o más documentos y devuelven un resultado. Si se insertan varios documentos, la operación "success" devuelve el objectId del documento insertado o una matriz de objectIds en orden.
Inserta un solo documento
Puede insertar un solo documento utilizando collection.insertOne().
Este fragmento inserta un solo documento que describe una bebida de café "Colombian Blend" en una colección de documentos que describen bebidas de café a la venta en un grupo de tiendas:
// 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().
Este fragmento inserta tres documentos que describen bebidas de café en una colección de documentos que describen bebidas de café a la venta en un grupo de tiendas:
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).
Buscar un solo documento
Puede encontrar un solo documento utilizando collection.findOneDocument().
Este fragmento 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 del documento name contiene el valor de cadena "Mezcla colombiana":
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
Puede encontrar varios documentos utilizando collection.find().
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":
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 ...
Buscar y ordenar documentos
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.
Luego, pasa la instancia FindOptions al método collection.find() cuando ejecuta la consulta.
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
Puedes contar los documentos de una colección usando collection.count(). Puedes especificar una consulta y un límite opcionales para determinar qué documentos contar. Si no especificas una consulta, la acción cuenta todos los documentos de la colección.
Este fragmento cuenta la cantidad de 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 "Grano del día":
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().
Este fragmento actualiza varios documentos de una colección que describen bebidas de café a la venta en un grupo de tiendas. Esta operación de actualización busca documentos cuyo name campo contenga el valor "Grano del día" y cambia el containsDairy campo true a:
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.
Insertar documentos
Si una operación de actualización no coincide con ningún documento de la colección, puede insertar automáticamente un solo documento nuevo en la colección que coincida con la consulta 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.
Debido a que este fragmento establece la opción upsert en true, si ningún documento coincide con la consulta, MongoDB crea un nuevo documento que incluye tanto la consulta como las actualizaciones especificadas:
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 fragmentos de código muestran cómo eliminar documentos almacenados en una colección de MongoDB desde una aplicación móvil. Las operaciones de eliminación utilizan una consulta para especificar qué documentos eliminar y devuelven resultados que se resuelven en Int 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
Puede eliminar varios elementos de una colección utilizando collection.deleteManyDocuments().
Este fragmento elimina todos los documentos de una colección de documentos que describen bebidas de café a la venta en un grupo de tiendas que coinciden con la consulta de documentos cuyo name campo contiene el valor "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
Limitaciones de la tecnología sin servidor
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.
Opcionalmente, puede observar una lista filtrada de _ids en la colección con collection.watch(filterIds:), o aplicar un $match filtro a los eventos de cambio entrantes con collection.watch(matchFilter:).
El .watch() método puede usar un ChangeEventDelegate para suscribirse a los cambios en una secuencia. Por ejemplo, con ChangeEventDelegate este:
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)") } }
Este código vigila los cambios en los documentos de la colección CoffeeDrinks:
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
Esté atento a los cambios en una colección en identificaciones específicas
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)") } }
El código siguiente utiliza este delegado para observar cambios en documentos específicos en la colección CoffeeDrinks:
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
Esté atento a los cambios en una colección con un filtro
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)") } }
El código siguiente usa este delegado para detectar cambios en los documentos de la colección CoffeeDrink. Solo activa la devolución de llamada proporcionada para eventos cuyos documentos tengan un valor storeNumber de 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.
Puedes abrir una secuencia asíncrona para supervisar los cambios en una colección. En un contexto asíncrono, llama a changeEvents() en una colección para abrir un flujo de cambios. Esto proporciona una secuencia asíncrona de valores AnyBSON que contiene información sobre cada cambio en la colección de MongoDB.
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.
La API changeEvents() puede tomar filterIds o matchFilter para observar un subconjunto de documentos en una colección, similar a los ejemplos anteriores.
El siguiente fragmento observa los cambios en cualquier documento en la colección CoffeeDrinks como una secuencia asincrónica:
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.
Puede configurar y ejecutar operaciones de agregación en una colección utilizando collection.aggregate().
Una operación de agregación acepta una lista de etapas de agregación como entrada y devuelve un resultado que se resuelve en una colección de documentos procesados por la canalización, o un Error.
Filtrar Documentos
Puede utilizar la etapa $match para filtrar documentos utilizando la sintaxis de consulta estándar de MongoDB:
Esta etapa $match filtra los documentos para incluir solo aquellos donde el campo storeNumber tiene un valor igual a 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
Puede usar la etapa $group para agregar datos de resumen de uno o más documentos. MongoDB agrupa documentos según la expresión definida en el _id campo de la $group etapa. Puede hacer referencia a un campo de documento específico anteponiendo al nombre del $ campo.
Esta etapa $group ordena los documentos según el valor de su campo storeNumber. A continuación, calcula la cantidad de documentos de café que contienen ese número de tienda en el valor del campo. En otras palabras, calculamos la cantidad de cafés por cada número de tienda.
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:
Especifique que desea incluir campos mediante
1. Esto tiene el efecto secundario de excluir implícitamente todos los campos no especificados.Especifique que desea excluir campos mediante
0. Esto tiene el efecto secundario de incluir implícitamente todos los campos no especificados.
Estos dos métodos de proyección son mutuamente excluyentes. Si especifica campos para incluir, no puede especificar también campos para excluir, y viceversa.
Nota
El _id campo es un caso especial: siempre se incluye en todas las consultas, a menos que se especifique lo contrario. Por esta razón, se puede excluir el _id campo con un 0 valor e incluir simultáneamente otros campos,storeNumber como, con un 1 valor. Solo el caso especial de exclusión del _id campo permite tanto la exclusión como la inclusión en una sola $project etapa.
Para este ejemplo, supongamos que el documento CoffeeDrink tiene un campo store que es un valor de cadena que contiene la palabra "Tienda" con un número, como "Tienda 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"]
La siguiente etapa $project omite el campo _id, incluye el campo name y crea un nuevo campo llamado storeNumber. El storeNumber se genera mediante dos operadores de agregación:
$splitSepara la representación de la cadenastoreen dos segmentos que rodean el espacio. Por ejemplo, el valor "Store 42" dividido de esta manera devuelve una matriz con dos elementos: "Store" y "42".$arrayElemAtSelecciona un elemento específico de un array según el segundo argumento. En este caso, el valor1selecciona el segundo elemento del array generado por el operador$split, ya que los arrays indexan desde0. Por ejemplo, el valor ["Store", "42"] pasado a esta operación devolvería un valor de "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
Puede utilizar la etapa $addFields para agregar nuevos campos con valores calculados utilizando operadores de agregación.
Nota
$addFields es similar a $project pero no permite incluir u omitir campos.
Para este ejemplo, supongamos que el documento CoffeeDrink tiene un campo store que es un valor de cadena que contiene la palabra "Tienda" con un número, como "Tienda 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"]
La siguiente etapa $addFields crea un nuevo campo llamado storeNumber donde el valor es la salida de dos operadores agregados que transforman el valor del campo store.
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
Puede usar la etapa $unwind para transformar un documento que contiene una matriz en varios documentos que contienen valores individuales de esa matriz. Al desenrollar un campo de matriz, MongoDB copia cada documento una vez por cada elemento de dicho campo, pero reemplaza el valor de la matriz con el elemento de la matriz en cada copia.
Considere este documento que incluye una matriz featuredInPromotions:
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 nuevo documento para cada elemento de la matriz items en cada documento. También añade un campo llamado itemIndex a cada nuevo documento que especifica el índice de posición del elemento en la matriz 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.