Explore o novo chatbot do Developer Center! O MongoDB AI chatbot pode ser acessado na parte superior da sua navegação para responder a todas as suas perguntas sobre o MongoDB .

Junte-se a nós no Amazon Web Services re:Invent 2024! Saiba como usar o MongoDB para casos de uso de AI .
Desenvolvedor do MongoDB
Central de desenvolvedor do MongoDBchevron-right
Idiomaschevron-right
C#chevron-right

Usando polimorfismo com MongoDB e C#

Markus Wildgruber6 min read • Published Apr 30, 2024 • Updated Apr 30, 2024
C#
Ícone do FacebookÍcone do Twitterícone do linkedin
Avalie esse Tutorial
star-empty
star-empty
star-empty
star-empty
star-empty
Em comparação com relational database management systems (RDBMS), o esquema flexível do MongoDB é um grande avanço no tratamento de dados orientados a objetos. Essas estruturas geralmente fazem uso de polimorfismo, onde as classes base comuns contêm os campos compartilhados que estão disponíveis para todas as classes na hierarquia; classes derivadas adicionam os campos que são relevantes apenas para os objetos específicos. Um exemplo poderia ser ter vários tipos de veículos, como carros e motocicletas, que possuem alguns campos em comum, mas cada tipo também adiciona alguns campos que só fazem sentido se usados para um tipo:
Um diagrama de classes de entidade mostrando um tipo de veículo e, em seguida, classes de carro e bicicleta que o estendem
Para o RDBMS, armazenar uma hierarquia de objetos é um desafio. Uma maneira é armazenar os dados em uma tabela que contém todos os campos de todas as classes, embora para cada linha seja necessário apenas um subconjunto de campos. Outra abordagem é criar uma tabela para a classe base que contém os campos compartilhados e adicionar uma tabela para cada classe derivada que armazena as colunas do tipo específico e faz referência à tabela base. Nenhuma dessas abordagens é ideal em termos de armazenamento e quando se trata de consultar os dados.
No entanto, com o esquema flexível do MongoDB, pode-se armazenar facilmente documentos na mesma coleção que compartilham apenas alguns, mas não todos os campos. Este artigo mostra como o driverdoMongoDB C# facilita o uso disso para armazenar hierarquias de classes de uma forma muito natural.
Exemplos de casos de uso incluem o armazenamento de metadados para vários tipos de documentos, por exemplo, ofertas, faturas ou outros documentos relacionados a parceiros de negócios em uma collection. Os campos comuns podem ser o título do documento, um resumo, a data, uma incorporação vetorial e a referência ao parceiro de negócios, enquanto uma fatura adicionaria campos para os itens e totais da linha, mas não adicionaria os campos para um relatório de projeto.
Outro caso de uso possível é servir uma visão geral e uma visualização detalhada da mesma collection. Veremos mais de perto como implementar isso no resumo deste artigo.

Noções básicas

Ao acessar uma collection do C#, usamos um objeto que implementa a interfaceIMongoCollection<T> . Este objeto pode ser criado assim:
1var vehiclesColl = db.CreateCollection<Vehicle>("vehicles");
Ao serializar ou deserializar documentos, o parâmetro de tipo T e o tipo real do objeto fornecem ao driver C# do MongoDB uma dica sobre como mapear a representação BSON para uma classe C# e vice-versa. Se apenas documentos do mesmo tipo residirem na coleção, o driver usará o mapa de classe do tipo.
No entanto, para poder lidar com as hierarquias de classes corretamente, o driver precisa de mais informações. É aqui que entra o discriminador de tipo. Ao armazenar um documento de um tipo derivado na coleção, o driver adiciona um campo chamado _t ao documento que contém o nome da classe, por exemplo:
1await vehiclesColl.InsertOneAsync(new Car());
leva à seguinte estrutura de documento:
1{
2 "_id": ObjectId("660d7d43e042f8f6f2726f6a"),
3 "_t": "Car",
4 // ... fields for vehicle
5 // ... fields specific to car
6}
Ao desserializar o documento, o valor do campo _t é utilizado para identificar o tipo do objeto que deve ser criado.
Embora isso funcione imediatamente sem configuração específica, é recomendável oferecer suporte ao driver especificando a hierarquia de classes explicitamente usando o atributoBsonKnownTypes, se você estiver usando o mapeamento declarativo:
1[BsonKnownTypes(typeof(Car), typeof(Motorcycle))]
2public abstract class Vehicle
3{
4 // ...
5}
Se você configurar os mapas de classe imperativamente, basta adicionar um mapa de classe para cada tipo na hierarquia para obter o mesmo efeito.
Por padrão, somente o nome da classe é usado como valor para o discriminador de tipo. especialmente se a hierarquia abranger vários níveis e você desejar executar query de qualquer nível da hierarquia, deverá armazenar a hierarquia como uma array no discriminador de tipo usando o atributoBsonDiscriminator :
1[BsonDiscriminator(RootClass = true)]
2[BsonKnownTypes(typeof(Car), typeof(Motorcycle))]
3public abstract class Vehicle
4{
5 // ...
6}
Isso aplica uma convenção discriminatória diferente aos documentos e armazena a hierarquia como uma matriz:
1{
2 "_id": ObjectId("660d81e5825f1c064024a591"),
3 "_t": [
4 "Vehicle",
5 "Car"
6 ],
7 // ...
8}
Para obter mais detalhes sobre como configurar os mapas de classes para objetos polimórficos, consulte a documentação do driver.

Consultando collection com documentos polimórficos

Ao ler objetos de uma coleção, o driver C# do MongoDB usa o discriminador de tipo para identificar o tipo correspondente e criar um objeto C# da classe correspondente. A seguinte query pode gerar Car Motorcycle objetos e :
1var vehiclesColl = db.GetCollection<Vehicle>("vehicles");
2var vehicles = (await vehiclesColl.FindAsync(FilterDefinition<Vehicle>.Empty))
3 .ToEnumerable();
Se você estiver interessado apenas em documentos de um tipo específico, poderá criar outra instância de IMongoCollection<T> que retorne apenas estes:
1var carsColl = vehiclesColl.OfType<Car>();
2var cars = (await carsColl.FindAsync(FilterDefinition<Car>.Empty))
3 .ToEnumerable();
Esta nova instância de coleção respeita o discriminador de tipo correspondente sempre que uma operação é executada. A declaração a seguir remove apenas Car documentos da coleção, mas mantém os documentosMotorcycle como estão:
1await carsColl.DeleteManyAsync(FilterDefinition<Car>.Empty);
Se você estiver usando o provedor LINQ oferecido pelo driver C# do MongoDB, também poderá usar o método de extensão LINQ OfType<T> para recuperar somente os objetosCar :
1var cars = vehiclesColl.AsQueryable().OfType<Car>();

Servindo várias visualizações de uma única coleção

Como esperado antes, agora examinamos mais de perto um caso de uso de polimorfismo: suponha que estamos construindo um sistema que ofereça suporte ao monitoramento de sensores distribuídos em vários locais. O sistema deve fornecer uma visão geral que liste todos os sites com seu nome e o último valor que foi relatado para o site, juntamente com um carimbo de data/hora. Ao selecionar um site, o sistema mostra informações detalhadas do site que consistem em todos os dados da visão geral e também lista os sensores localizados no local específico com seu último valor e sua data e hora.
Isso pode ser representado criando uma classe base para os documentos que contêm o ID do site, um nome para identificar o documento e a última medida, se disponível. Uma classe derivada para a visão geral do site adiciona o endereço do site; outro para os detalhes do sensor contém a localização do sensor:
1using MongoDB.Bson;
2using MongoDB.Bson.Serialization.Attributes;
3
4public abstract class BaseDocument
5{
6 [BsonRepresentation(BsonType.ObjectId)]
7 public string Id { get; set; } = ObjectId.GenerateNewId().ToString();
8
9 [BsonRepresentation(BsonType.ObjectId)]
10 public string SiteId { get; set; } = ObjectId.GenerateNewId().ToString();
11
12 public string Name { get; set; } = string.Empty;
13
14 public Measurement? Last { get; set; }
15}
16
17public class Measurement
18{
19 public int Value { get; set; }
20
21 public DateTime Timestamp { get; set; }
22}
23
24public class Address
25{
26 // ...
27}
28
29public class SiteOverview : BaseDocument
30{
31 public Address Address { get; set; } = new();
32}
33
34public class SensorDetail : BaseDocument
35{
36 public string Location { get; set; } = string.Empty;
37}
Ao ingestão de novas medições, tanto a visão geral do site quanto os detalhes do sensor são atualizados (para simplificar, não usamos uma transação de vários documentos):
1async Task IngestMeasurementAsync(
2 IMongoCollection<BaseDocument> overviewsColl,
3 string sensorId,
4 int value)
5{
6 var measurement = new Measurement()
7 {
8 Value = value,
9 Timestamp = DateTime.UtcNow
10 };
11 var sensorUpdate = Builders<SensorDetail>
12 .Update
13 .Set(x => x.Last, measurement);
14 var sensorDetail = await overviewsColl
15 .OfType<SensorDetail>()
16 .FindOneAndUpdateAsync(
17 x => x.Id == sensorId,
18 sensorUpdate,
19 new() { ReturnDocument = ReturnDocument.After });
20 if (sensorDetail != null)
21 {
22 var siteUpdate = Builders<SiteOverview>
23 .Update
24 .Set(x => x.Last, measurement);
25 var siteId = sensorDetail.SiteId;
26 await overviewsColl
27 .OfType<SiteOverview>()
28 .UpdateOneAsync(x => x.SiteId == siteId, siteUpdate);
29 }
30}
A amostra acima usa FindAndUpdateAsync para atualizar o documento de detalhes do sensor e também recuperar o documento resultante para que o ID do site possa ser determinado. Se o ID do site for conhecido de anteposição, uma atualização simples também poderá ser usada.
Ao recuperar os documentos para a visão geral do site, o código a seguir retorna todos os documentos relevantes:
1var siteOverviews = (await overviewsColl
2 .OfType<SiteOverview>()
3 .FindAsync(FilterDefinition<SiteOverview>.Empty))
4 .ToEnumerable();
Ao exibir dados detalhados de um site específico, a query a seguir recupera todos os documentos do site por seu ID em uma única solicitação:
1var siteDetails = await (await overviewsColl
2 .FindAsync(x => x.SiteId == siteId))
3 .ToListAsync();
O resultado da query pode conter objetos de diferentes tipos; você pode usar o método de extensão LINQ OfType<T> na lista para discernir entre os tipos, por exemplo, ao criar um modelo de visualização.
Essa abordagem permite uma consulta eficiente de diferentes perspectivas para que as visualizações centrais do aplicativo possam ser atendidas com carga mínima no servidor.

Resumo

O polimorfismo é uma funcionalidade importante das linguagens orientadas a objetos e há uma ampla variedade de casos de uso para ele. Como você pode ver, o driver C# do MongoDB fornece uma ponte sólida entre a orientação a objetos e o esquema de documentos flexíveis do MongoDB. Se você quiser se afundar no assunto do ponto de vista da modelagem de dados, não deixe de conferir a parte depadrões polimórficos da soberba série "Construindo com padrões" no MongoDB Developer Center.
Principais comentários nos fóruns
Ainda não há comentários sobre este artigo.
Iniciar a conversa

Ícone do FacebookÍcone do Twitterícone do linkedin
Avalie esse Tutorial
star-empty
star-empty
star-empty
star-empty
star-empty
Relacionado
Tutorial

Queries geoespaciais do MongoDB em C#


May 12, 2022 | 11 min read
Artigo

Como usar o Realm de forma eficaz em um aplicativo Xamarin.Forms


Sep 09, 2024 | 18 min read
Tutorial

Interaja com o MongoDB Atlas em uma função do AWS Lambda usando C#


Jan 23, 2024 | 5 min read
Tutorial

Unindo coleções no MongoDB com .NET Core e um pipeline de agregação


Feb 03, 2023 | 7 min read
Sumário