Query MongoDB - Swift SDK
Nesta página
- Visão geral
- Casos de uso
- Pré-requisitos
- Dados de exemplo
- Assíncrono/Aguardar query MongoDB
- Criar documentos
- Inserir um único documento
- Insira vários documentos
- Ler documentos
- Encontrar um único documento
- Localizar vários documentos
- Localizar e classificar documentos
- Contagem de documentos na coleção
- Atualize documentos
- Atualizar um único documento
- Atualizar vários documentos
- Documentos do Upsert
- Exclua documentos
- Excluir um único documento
- Excluir vários documentos
- Fique atento às mudanças
- Fique atento às mudanças em uma coleção
- Fique atento às alterações em uma collection para ID específicos
- Fique atento às alterações em uma coleção com um filtro
- Assista a uma coleção como uma sequência assíncrona
- Documentos agregados
- Filtrar documentos
- Documentos do grupo
- Campos do documento do projeto
- Adicionar campos aos documentos
- Unwind Array Values
Visão geral
Você pode fazer query dos dados armazenados no MongoDB diretamente do código do seu aplicação cliente usando Realm Swift SDK oMongoClient do com a de Query .API O Atlas App Services fornece regras de acesso a dados em coleções para recuperar resultados com segurança com base no usuário conectado ou no conteúdo de cada documento.
Dica
Veja também:
Esta página aborda a query direta de uma fonte de dados MongoDB. Para filtrar os dados recuperados de um realm, consulte: Filtrar Dados.
Casos de uso
Existem várias razões pelas quais você pode querer fazer uma query em uma fonte de dados MongoDB. Trabalhar com dados em seu cliente via Atlas Device Sync nem sempre é prático ou possível. Talvez você queira fazer uma query no MongoDB quando:
O conjunto de dados for grande ou o dispositivo cliente tiver restrições para carregar todo o conjunto de dados
Você estiver criando ou atualizando dados de usuário personalizados
Você estiver recuperando documentos que não sejam modelados no Realm
Sua aplicação precisa acessar collections que não têm esquemas rigorosos
Um serviço que não é do Realm gera coleções que você deseja acessar
Embora não exaustivos, esses são alguns casos de uso comuns para a query direta do MongoDB.
Pré-requisitos
Antes de poder executar uma query do MongoDB a partir do seu aplicativo cliente, você deve configurar o MongoDB Data Access no seu App Services App. Para saber como configurar seu aplicativo de backend para permitir que o Realm SDK consulte o Atlas, consulte Configurar o acesso aos dados do MongoDB na documentação do Atlas App Services .
Dados de exemplo
Esses exemplos operam em uma coleção MongoDB que descreve as bebidas de café em uma cadeia de lojas de café. Os documentos representam objetos com estas propriedades:
class CoffeeDrink: Object { true) var _id: ObjectId (primaryKey: var name: String var beanRegion: String? var containsDairy: Bool var storeNumber: Int }
O código completo para cada exemplo inclui o login e a instanciação de um identificador de collection do MongoDB antes de concluir cada operação. Por questões de brevidade, esses exemplos omitem o código de identificador de login e collection. No entanto, cada exemplo completo tem a seguinte aparência:
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)") } } } } }
Assíncrono/Aguardar query MongoDB
Novidade na versão 10.16.0.
O Realm Swift SDK fornece versões async/await dos métodos MongoCollection.
Todos os métodos nesta página são compatíveis com a sintaxe async/await. Este exemplo ilustra a sintaxe para o método collection.insertOne()
. Você pode ver a versão do manipulador de conclusão em Inserir um documento único.
// 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 das versões 10.15.0 e 10.16.0 do SDK do Realm Swift, muitas das APIs do Realm suportam a sintaxe async/await do Swift. Os projetos devem atender a estes requisitos:
Versão do Swift SDK | Requisito de versão do Swift | Sistema operacional compatível |
---|---|---|
10.25.0 | Swift 5.6 | iOS 13.x |
10.15.0 ou 10.16.0 | Swift 5.5 | iOS 15.x |
Se a sua aplicação acessar Realm em um contexto do async/await
, marque o código com @MainActor
para evitar falhas relacionadas a threading.
Criar documentos
Esses trechos de código demonstram como inserir um ou mais documentos em uma coleção MongoDB de um aplicativo móvel. Esses métodos usam um ou mais documentos e retornam um resultado. O sucesso retorna o ObjectId do documento inserido ou uma array de objectIds em ordem ao inserir vários documentos.
Inserir um único documento
Você pode inserir um único documento usando collection.insertOne().
Este trecho insere um único documento que descreve uma bebida de café "Colombian baixa" em uma coleção de documentos que descrevem bebidas de café para venda em um grupo de lojas:
// 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)
Insira vários documentos
Você pode inserir vários documentos usando collection.insertMany().
Esse trecho insere três documentos descrevendo bebidas à base de café em uma coleção de documentos que descrevem as bebidas à base de café para venda em um grupo de lojas:
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.
Ler documentos
Esses trechos de código demonstram como ler dados armazenados em uma coleção MongoDB de um aplicativo móvel. As operações de leitura usam uma sintaxe de query padrão para especificar quais documentos devem ser retornados do banco de dados. As operações de leitura retornam um resultado que resulta em um único documento correspondente (no caso de findOneDocument()
), um valor numérico long
(no caso de count()
) ou uma array de documentos correspondentes (no caso de find()
).
Encontrar um único documento
Você pode encontrar um único documento usando collection.findOneDocument().
Esse trecho encontra um único documento de uma coleção de documentos que descrevem bebidas de café à venda em um grupo de lojas, onde o campo name
do documento contém o valor da string "Colombian baixa":
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)) ])
Localizar vários documentos
Você pode encontrar vários documentos usando collection.find().
Este trecho encontra todos os documentos em uma coleção de documentos que descrevem bebidas à base de café à venda em um grupo de lojas, onde o campo name
do documento contém o 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 ...
Localizar e classificar documentos
Você pode classificar documentos em uma coleção inicializando uma instância de FindOptions com suas opções de classificação preferidas. FindOptions
tem três parâmetros: limite, projeção e classificação. O argumento sorting
pode ser uma array de pares de valores-chave onde cada chave representa um campo. Para cada chave, o valor 1
é classificado em ordem decrescente ou -1
é classificado em ordem crescente.
Em seguida, você passa a instância FindOptions
para o método collection.find()
ao executar a query.
Este trecho encontra todos os documentos em uma coleção de documentos que descrevem bebidas de café à venda em um grupo de lojas, onde o campo name
do documento contém o valor " Americano ", classificado em ordem decrescente pelo campo beanRegion
:
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` ...
Contagem de documentos na coleção
Você pode contar documentos em uma coleção usando collection.count(). Você pode especificar uma query opcional e um limite para determinar quais documentos contar. Se você não especificar uma query, a ação contará todos os documentos da collection.
Esse trecho conta o número de documentos em uma coleção de documentos que descrevem bebidas à base de café à venda em um grupo de lojas, onde o campo name
do documento contém o valor "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
Atualize documentos
Esses trechos de código demonstram como atualizar os dados armazenados em uma coleção MongoDB de um aplicativo móvel. As operações de atualização usam queries para especificar quais documentos atualizar e atualizar os operadores para descrever como mutar documentos que correspondem à query. As operações de atualização retornam um resultado que se resolve para um UpdateResult ou Error
.
Atualizar um único documento
Você pode atualizar um único documento usando collection.updateOneDocument().
Este trecho atualiza um único documento em uma coleção de documentos que descrevem bebidas de café para venda em um grupo de lojas. Esta operação de atualização consulta um documento cujo campo name
contém o valor "Bean of the day" e define o campo containsDairy
como true
:
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.
Atualizar vários documentos
Você pode atualizar vários documentos usando collection.updateManyDocuments().
Este trecho atualiza vários documentos em uma coleção de documentos que descrevem bebidas de café para venda em um grupo de lojas. Esta operação de atualização faz queries em documentos onde o campo name
contém o valor "Bean of the day" e altera o campo containsDairy
para 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.
Documentos do Upsert
Se uma operação de atualização não corresponder a nenhum documento na coleção, você poderá inserir automaticamente um único novo documento na coleção que corresponda à query de atualização definindo a opção upsert
como true
.
O trecho a seguir atualiza um documento em uma coleção de documentos que descrevem bebidas de café para venda em um grupo de lojas. Se nenhum documento corresponder à query, ele inserirá um novo documento se nenhum documento. Esta operação faz uam query em documentos onde o campo name
tem um valor de "Bean of the day" e o campo storeNumber
tem um valor de 55
.
Como esse trecho define a opção upsert
como true
, se nenhum documento corresponder à query, o MongoDB cria um novo documento que inclui a query e as atualizações 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
Exclua documentos
Esses trechos de código demonstram como excluir documentos armazenados em uma coleção MongoDB de um aplicativo móvel. As operações de exclusão usam uma query para especificar quais documentos excluir e retornar resultados que se resolvem em uma contagem de documentos excluídos ou Int
Error
.
Excluir um único documento
Você pode excluir um único documento de uma coleção usando collection.deleteOneDocument().
Este trecho exclui um documento em uma coleção de documentos que descrevem bebidas à base de café para venda em um grupo de lojas. Esta operação consulta um documento onde o campo name
tem um valor de "Mocha" e o campo storeNumber
tem um valor de 17
e o exclui.
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.
Excluir vários documentos
Você pode excluir vários itens de uma coleção usando collection.deleteManyDocuments().
Este snippet exclui todos os documentos em uma coleção de documentos que descrevem bebidas à base de café à venda em um grupo de lojas que correspondem à query de documentos cujo campo name
contém o valor "Caramel Latter":
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.
Fique atento às mudanças
Você pode assistir a uma coleção para notificações de alteração que o MongoDB emite sempre que um documento da coleção for criado, modificado ou excluído. Cada notificação especifica um documento que foi alterado, como ele foi alterado e o documento completo após a operação que causou o evento.
Importante
Limitações sem servidor
Você não poderá observar alterações se a fonte de dados for uma instância sem servidor do Atlas. Atualmente, o MongoDB serverless não oferece suporte a fluxos de alterações, que são usados em coleções monitoradas para escutar alterações.
Fique atento às mudanças em uma coleção
Você pode abrir um fluxo de alterações feitas em uma coleção chamando collection.watch(). Esta função cria um editor que emite um evento de alteração AnyBSON quando a coleção MongoDB é alterada.
Opcionalmente, você pode assistir a uma lista filtrada de _ids
na coleção com collection.watch(filterIds:) ou aplicar um filtro de $match
aos eventos de alteração recebidos com coleção.watch(matchFilter:).
O método .watch()
pode usar um ChangeEventDelegate para assinar alterações em um fluxo. Por exemplo, com este 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)") } }
Este código observa alterações em documentos na collection 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
Fique atento às alterações em uma collection para ID específicos
Você pode observar alterações em uma coleção em uma lista específica de objetos passando sua _id
. Chame collection.watch(filterIds: ) com uma array de ObjectIds para receber somente eventos de alteração que se apliquem a esses documentos.
Considere um exemplo que usa este 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)") } }
O código abaixo usa esse delegado para observar alterações em documento específicos na collection 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
Fique atento às alterações em uma coleção com um filtro
Você pode abrir um fluxo de alterações feitas em documentos em uma coleção que atendam a determinados critérios chamando collection.watch(matchFilter: ). Este método aceita um parâmetro Document
que é utilizado como query de um operador $match para processar cada evento do banco de dados que ocorre enquanto assiste à collection.
Considere um exemplo que usa este 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)") } }
O código abaixo usa esse delegado para observar alterações em documento na collection CoffeeDrink
. Ele só Atlas Triggers a chamada de resposta fornecida para evento cujo documento tenham um 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
Assista a uma coleção como uma sequência assíncrona
Novidade na versão 10.37.0 .
Você pode abrir uma sequência assíncrona para observar as alterações em uma coleção. Em um contexto assíncrono, chame changeEvents() em uma coleção para abrir um fluxo de alteração. Isso fornece uma sequência assíncrona de valores AnyBSON contendo informações sobre cada alteração na coleção MongoDB.
Opcionalmente, você pode fornecer uma chamada de resposta changeEvents(onOpen: ) que é invocada quando o fluxo de observação é inicializado no servidor.
A API changeEvents()
pode levar filterIds
ou um matchFilter
para monitorar um subconjunto de documentos em uma coleção, semelhante aos exemplos acima.
O seguinte trecho observa alterações em quaisquer documentos da collection CoffeeDrinks
como uma sequência assíncrona:
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
As operações de agregação executam todos os documentos em uma coleção por meio de uma série de estágios de agregação de dados chamados de pipeline de agregação. A agregação permite filtrar e transformar documentos, coletar dados resumidos sobre grupos de documentos relacionados e outras operações de dados complexas.
Você pode configurar e executar operações de agregação em uma coleção usando collection.aggregate().
Uma operação de aggregation aceita uma lista de estágios de aggregation como entrada e retorna um resultado que se resolve em uma collection de documentos processados pelo pipeline, ou um Error
.
Filtrar documentos
Você pode utilizar o estágio $match para filtrar documentos utilizando a sintaxe de query padrão do MongoDB:
Esse estágio $match
filtra os documentos para incluir apenas aqueles em que o campo storeNumber
tem um 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 do grupo
Você pode usar o estágio $group para agregar dados resumidos de um ou mais documentos. O MongoDB agrupa documentos com base na expressão definida no campo _id
da etapa $group
. Você pode referenciar um campo de documento específico prefixando o nome do campo com um $
.
Este estágio $group
organiza documentos pelo valor de seu campo storeNumber
. Em seguida, ele calcula o número de documentos de bebida de café que contêm esse número de armazenamento no valor do campo. Em outras palavras, estamos calculando o número de bebidas de café para cada número de loja.
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 do documento do projeto
Você pode utilizar o estágio $project para incluir ou omitir campos específicos de documentos ou calcular novos campos utilizando operadores de agregação. As projeções funcionam de duas formas:
Especifique que você deseja incluir campos usando um
1
. Isso tem o efeito colateral de excluir implicitamente todos os campos não especificados.Especifique que você deseja excluir campos usando um
0
. Isso tem o efeito colateral de incluir implicitamente todos os campos não especificados.
Estes dois métodos de projeção são mutuamente exclusivos. Se você especificar campos para incluir, não poderá também especificar campos para excluir e vice-versa.
Observação
O campo _id
é um caso especial: ele é sempre incluído em todas as consultas, a menos que seja explicitamente especificado de outra forma. Por esse motivo, você pode excluir o campo _id
com um valor 0
e, ao mesmo tempo, incluir outros campos, como storeNumber
, com um 1
. Somente o caso especial de exclusão do campo _id
permite tanto a exclusão quanto a inclusão em um estágio $project
.
Para este exemplo, suponha que o documento CoffeeDrink
tenha um campo store
que é um valor de string contendo a palavra "Loja" com um número, como "Loja 42", semelhante a estes 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"]
O estágio $project
a seguir omite o campo _id
, inclui o campo name
e cria um novo campo chamado storeNumber
. O storeNumber
é gerado usando dois operadores de agregação:
$split
separa a representação de stringstore
em dois segmentos de string ao redor do caractere de espaço. Por exemplo, o valor "Store 42" dividido dessa forma retorna uma matriz com dois elementos: "Store" e "42".$arrayElemAt
seleciona um elemento específico de uma matriz com base no segundo argumento. Nesse caso, o valor1
seleciona o segundo elemento da matriz gerada pelo operador$split
, já que as matrizes são indexadas a partir de0
. Por exemplo, o valor ["Loja", "42"] passado para esta operação retornaria um 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...)
Adicionar campos aos documentos
Você pode usar o estágio $addFields para adicionar novos campos com valores calculados usando operadores de agregação.
Observação
$addFields
é semelhante ao $project, mas não permite que você inclua ou omita campos.
Para este exemplo, suponha que o documento CoffeeDrink
tenha um campo store
que é um valor de string contendo a palavra "Loja" com um número, como "Loja 42", semelhante a estes 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"]
O estágio $addFields
a seguir cria um novo campo chamado storeNumber
em que o valor é a saída de dois operadores agregados que transformam o valor do 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
Você pode usar o estágio $unwind para transformar um único documento contendo uma matriz em vários documentos contendo valores individuais dessa matriz. Quando você desenrola um campo de matriz, o MongoDB copia cada documento uma vez para cada elemento do campo de matriz, mas substitui o valor da matriz pelo elemento da matriz em cada cópia.
Considere este documento que inclui um array featuredInPromotions
:
let drink: Document = [ "name": "Maple Latte", "beanRegion": "Yirgacheffe, Ethiopia", "containsDairy": true, "storeNumber": 42, "featuredInPromotions": [ "Spring into Spring", "Tastes of Fall", "Winter Delights" ] ]
A etapa $unwind
a seguir cria um novo documento para cada elemento da array items
em cada documento. Ele também adiciona um campo chamado itemIndex
a cada novo documento que especifica o índice de posição do elemento na array 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"))]
Você pode então $group
pelo valor de featuredInPromotions
e $sum
o número de bebidas de café em cada promoção, como no exemplo de documentos do grupo, ou realizar outros cálculos ou transformações com base em seus dados.