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 .

Saiba por que o MongoDB foi selecionado como um líder no 2024 Gartner_Magic Quadrupnt()
Desenvolvedor do MongoDB
Central de desenvolvedor do MongoDBchevron-right
Idiomaschevron-right
C#chevron-right

Como configurar mapas de classes do MongoDB para C# para otimizar o desempenho de query e o tamanho de armazenamento

Markus Wildgruber8 min read • Published Dec 07, 2023 • Updated Aug 05, 2024
.NETC#
Ícone do FacebookÍcone do Twitterícone do linkedin
Avalie esse Artigo
star-empty
star-empty
star-empty
star-empty
star-empty
Começando com MongoDB e C#? Essas dicas o ajudarão a fazer com que seus mapas de classes estejam corretos desde o início para dar suporte ao esquema desejado.
Ao iniciar meus primeiros projetos com MongoDB e C# há vários anos, o que mais me cativou foi a facilidade de armazenar objetos CLR antigos e simples (POCOs) em uma collection sem ter que criar uma estrutura relacional estática primeiro e mantê-la penosamente durante o curso de desenvolvimento.
Embora o MongoDB e o C# tenham seu próprio conjunto de tipos de dados e convenções de nomenclatura, o driverdoMongoDB C# conecta os dois de maneira muito perfeita. No centro disso, mapas de classe são usados para descrever os detalhes do mapeamento.
Esta postagem mostra como fazer o ajuste fino do mapeamento em áreas importantes e oferece soluções para cenários comuns.

Mapeamento automático

Mesmo que você não defina um mapa de classe explicitamente, o driver criará um assim que a classe for usada para uma coleção. Nesse caso, as propriedades do POCO são mapeadas para elementos no documento BSON com base no nome. O driver também tenta corresponder o tipo de propriedade ao tipo BSON do elemento no MongoDB.
Embora o mapeamento automático de uma classe garanta que os POCOs possam ser armazenados facilmente em uma coleção, o ajuste do mapeamento é recompensado por uma melhor eficiência de memória e melhor desempenho de consulta. Além disso, se você estiver trabalhando com dados existentes, personalizar o mapeamento permite que os POCOs sigam as convenções de nomenclatura C# e .NET sem alterar o esquema dos dados na coleção.

Mapeamento declarativo vs. imperativo

Ajustar o mapa de classes pode ser tão fácil quanto adicionar atributos à declaração de um POCO (mapeamento declarativo). Estes atributos são usados pelo driver quando o mapa de classe é mapeado automaticamente. Isso acontece quando a classe é usada pela primeira vez para acessar dados em uma collection:
1public class BlogPost
2{
3 // ...
4 [BsonElement("title")]
5 public string Title { get; set; } = string.Empty;
6 // ...
7}
A amostra acima mostra como o atributoBsonElement é utilizado para ajustar o nome da propriedadeTitle em um documento no MongoDB:
1{
2 // ...
3 "title": "Blog post title",
4 // ...
5}
No entanto, há cenários em que o mapeamento declarativo não é aplicável: se você não puder alterar os POCOs porque eles estão definidos em uma biblioteca de terceiros ou se quiser separar seus POCOs das partes de código relacionadas ao MongoDB, também há a opção de definir os mapas de classes imperativamente chamando métodos no código:
1BsonClassMap.RegisterClassMap<BlogPost>(cm =>
2{
3 cm.AutoMap();
4 cm.MapMember(x => x.Title).SetElementName("title");
5});
O código acima primeiro executa o mapeamento automático e, em seguida, inclui a propriedadeTitle no mapeamento como um elemento chamado title em BSON, substituindo o mapeamento automático para a propriedade específica.
Um aspecto a ser lembrado é que o mapa de classes precisa ser registrado antes que o driver inicie o processo de mapeamento automático de uma classe. É uma boa ideia incluí-lo no processo de inicialização do aplicativo.
Esta postagem usará mapeamento declarativo para melhor legibilidade, mas todos os ajustes também podem ser feitos usando o mapeamento imperativo. Você pode encontrar um mapa de classe obrigatório que contém todas as amostras no final da postagem.

Ajustando nomes de propriedades

Se estiver trabalhando com dados existentes ou quiser nomear as propriedades de forma diferente no BSON por outros motivos, poderá usar o atributoBsonElement("specificElementName")introduzido acima. Isso é especialmente útil se você quiser alterar apenas o nome de um conjunto limitado de propriedades.
Se quiser alterar o esquema de nomenclatura de forma ampla, você pode usar uma convenção que é aplicada ao mapear automaticamente as classes. O driver oferece uma série de convenções prontas para uso (consulte o namespace MongoDB.Bson.Serialization.Conventions) e oferece a flexibilidade de criar convenções personalizadas se elas não forem suficientes.
Um exemplo é nomear as propriedades POCO de acordo com as diretrizes de nomenclatura de C# em maiúsculas e minúsculas Pascal em C#, mas nomear os elementos em letras maiúsculas e minúsculas em BSON adicionando o camelCaseElementNameConvention:
1var pack = new ConventionPack();
2pack.Add(new CamelCaseElementNameConvention());
3ConventionRegistry.Register(
4 "Camel Case Convention",
5 pack,
6 t => true);
Observe o predicado no último parâmetro. Isso pode ser usado para ajustar se a convenção é aplicada a um tipo ou não. Na nossa amostra, é aplicado a todas as classes.
O código acima precisa ser executado antes que o mapeamento automático ocorra. Você ainda pode aplicar um atributoBsonElement aqui e ali se quiser substituir alguns dos nomes.

Usando ObjectIds como identificadores

O MongoDB usa ObjectIds como identificadores para documentos por padrão para o campo "_id ". Este é um tipo de dados que é exclusivo para uma probabilidade muito alta e precisa 12 bytes de memória. Se você estiver trabalhando com dados existentes, encontrará ObjectIds com certeza. Além disso, ao configurar novos documentos, ObjectIds são a opção preferida para identificadores. Em comparação com os GUIDs (UUIDs), eles exigem menos espaço de armazenamento e são ordenados de forma que os identificadores criados posteriormente recebam valores mais altos.
No C#, as propriedades podem usar ObjectId como seu tipo. No entanto, usar string como o tipo de propriedade em C# simplifica o manuseio dos identificadores e aumenta a interoperabilidade com outras estruturas que não são específicas do MongoDB (por exemplo, OData).
Por outro lado, o MongoDB deve serializar os identificadores com o tipo BSON específico ObjectId para reduzir o tamanho do armazenamento. Além disso, realizar uma comparação binária em ObjectIds é muito mais seguro do que comparar strings, pois você não precisa levar em consideração letras maiúsculas, minúsculas etc.
1public class BlogPost
2{
3 [BsonRepresentation(BsonType.ObjectId)]
4 public string Id { get; set; } = ObjectId.GenerateNewId().ToString();
5 // ...
6 [BsonRepresentation(BsonType.ObjectId)]
7 public ICollection<string> TopComments { get; set; } = new List<string>();
8}
Ao aplicar o atributo BsonRepresentation , a propriedadeId é serializada como ObjectId no BSON. Além disso, a array de identificadores em TopComments também usa ObjectIds como seu tipo de dados para os elementos de array:
1{
2 "_id" : ObjectId("6569b12c6240d94108a10d20"),
3 // ...
4 "TopComments" : [
5 ObjectId("6569b12c6240d94108a10d21"),
6 ObjectId("6569b12c6240d94108a10d22")
7 ]
8}

Serializar GUIDs de forma consistente

Embora ObjectId seja o tipo padrão de identificador do MongoDB, os GUIDs ou UUIDs são um tipo de dados usado para identificar objetos em várias linguagens de programação. Para armazená-los e consultá-los com eficiência, também é preferível usar um formato binário em vez de cadeias de caracteres.
No passado, os GUIDs/UUIDs eram armazenados como binários do tipo BSON do subtipo 3; drivers para diferentes ambientes de programação serializavam o valor de forma diferente. Portanto, a leitura de GUIDs com o driver C# que foi serializado com um driver Java não gerou o mesmo valor. Para corrigir isso, o novo subtipo binário 4 foi introduzido pelo MongoDB. Os GUIDs/UUIDs são então serializados da mesma maneira entre drivers e idiomas.
Para fornecer a flexibilidade para lidar com valores existentes e novos valores em um nível de propriedade, o Driver C# do MongoDB introduziu uma nova maneira de lidar com GUIDs. Isso é conhecido como GuidRepresentationMode.V3. Para compatibilidade com versões anteriores, ao usar a versão 2.x do MongoDB C# Driver, oGuidRepresentationMode é V2 por padrão (resultando no subtipo binário 3). Isso está definido para ser alterado com a versão 3 do driver C# do MongoDB . É uma boa ideia optar por usar V3 agora e especificar o subtipo que deve ser usado para GUIDs em um nível de propriedade. Para novos GUIDs, o subtipo 4 deve ser usado.
Isso pode ser feito executando o código a seguir antes de criar o cliente:
1BsonDefaults.GuidRepresentationMode
2= GuidRepresentationMode.V3;
Lembre-se de que essa configuração exige que a representação do GUID seja especificada em um nível de propriedade. Caso contrário, um BsonSerializationException será lançado informando que "OGuidSerializer não pode serializar um Guia quandoGuidRepresentation não for especificado." Para corrigir isso, adicione um atributoBsonGuidRepresentation à propriedade:
1[BsonGuidRepresentation(GuidRepresentation.Standard)]
2public Guid MyGuid { get; set; } = Guid.NewGuid();
Existem várias configurações disponíveis para GuidRepresentation. Para novos GUIDs, Standard é o valor preferido, enquanto os outros valores (por exemplo, CSharpLegacy) suportam a serialização de valores existentes no subtipo binário 3.
Para uma visão geral detalhada, consulte a documentação do driver.

Processando elementos extras

Talvez você esteja trabalhando com dados existentes e apenas uma parte dos elementos seja relevante para o seu caso de uso. Ou você tem documentos mais antigos em sua collection que contêm elementos que não são mais relevantes. Seja qual for o motivo, você deseja manter o POCO mínimo para que ele compreenda apenas as propriedades relevantes.
Por padrão, o driver C# do MongoDB é rigoroso e gera um FormatException se encontrar elementos em um documento BSON que não possam ser mapeados para uma propriedade no POCO:
"Element '[...]' does not match any field or property of class [...]." Esses elementos são chamados de "elementos extras".
Uma maneira de lidar com isso é simplesmente ignorar elementos extras aplicando o atributoBsonIgnoreExtraElementsao POCO:
1[BsonIgnoreExtraElements]
2public class BlogPost
3{
4 // ...
5}
Se você quiser usar esse comportamento em grande escala, poderá registrar novamente uma convenção:
1var pack = new ConventionPack();
2pack.Add(new IgnoreExtraElementsConvention(true));
3ConventionRegistry.Register(
4 "Ignore Extra Elements Convention",
5 pack,
6 t => true);
Esteja ciente de que, se você usar replace ao armazenar o documento, as propriedades extras que o C# não conhece serão perdidas.
Por outro lado, o esquema flexível do MongoDB foi criado para lidar com documentos com elementos diferentes. Se você estiver interessado nas propriedades extras ou quiser protegê-las para uma substituição, poderá adicionar um dicionário ao seu POCO e marcá-lo com um atributoBsonExtraElements. O dicionário é preenchido com o conteúdo das propriedades na desserialização:
1public class BlogPost
2{
3 // ...
4 [BsonExtraElements()]
5 public IDictionary<string, object> ExtraElements { get; set; } = new Dictionary<string, object>();
6}
Mesmo ao substituir um documento que contém um dicionário extra-elements, os pares chave-valor do dicionário são serializados como elementos para que seu conteúdo não seja perdido (ou mesmo atualizado se o valor no dicionário tiver sido alterado).

Serializando propriedades calculadas

O pré-cálculo é fundamental para um ótimo desempenho de query e é um padrão comum ao trabalhar com o MongoDB. Em POCOs, isso é suportado pela adição de propriedades somente leitura, por exemplo:
1public class BlogPost
2{
3 // ...
4 public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
5 public DateTime? UpdatedAt { get; set; }
6 public DateTime LastChangeAt => UpdatedAt ?? CreatedAt;
7}
Por padrão, o driver exclui propriedades somente leitura da serialização. Isso pode ser corrigido facilmente aplicando um atributoBsonElement à propriedade — você não precisa alterar o nome:
1public class BlogPost
2{
3 // ...
4 public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
5 public DateTime? UpdatedAt { get; set; }
6 [BsonElement()]
7 public DateTime LastChangeAt => UpdatedAt ?? CreatedAt;
8}
Após essa alteração, a propriedade read-only é incluída no documento e pode ser usada em índices e queries:
1{
2 // ...
3 "CreatedAt" : ISODate("2023-12-01T12:16:34.441Z"),
4 "UpdatedAt" : null,
5 "LastChangeAt" : ISODate("2023-12-01T12:16:34.441Z")
6}

Serializadores personalizados

Os cenários comuns são muito bem suportados pelo MongoDB C# Driver. Se isso não for suficiente, você pode criar um serializador personalizado que ofereça suporte ao seu cenário específico.
Serializadores personalizados podem ser usados para lidar com documentos com dados diferentes para o mesmo elemento. Por exemplo, se alguns documentos armazenam o ano como um número inteiro e outros como uma string, um serializador personalizado poderá analisar o tipo BSON durante a desserialização e ler o valor adequadamente.
No entanto, este é um último recurso que você raramente precisará usar, pois as opções existentes oferecidas pelo Driver C# do MongoDB abrangem a grande maioria dos casos de uso.

Conclusão

Como você viu, o Driver MongoDB C# oferece muitas opções para ajustar o mapeamento entre documentos POCOs e BSON. Os POCOs podem seguir as convenções do C# e, ao mesmo tempo, criar um esquema que ofereça bom desempenho de query e reduza o consumo de armazenamento.
Se você tiver dúvidas ou comentários, participe da Comunidade de Desenvolvedores do MongoDB!

Apêndice: amostra para mapa de classe obrigatória

1BsonClassMap.RegisterClassMap<BlogPost>(cm =>
2{
3 // Perform auto-mapping to include properties
4 // without specific mappings
5 cm.AutoMap();
6 // Serialize string as ObjectId
7 cm.MapIdMember(x => x.Id)
8 .SetSerializer(new StringSerializer(BsonType.ObjectId));
9 // Serialize ICollection<string> as array of ObjectIds
10 cm.MapMember(x => x.TopComments)
11 .SetSerializer(
12 new IEnumerableDeserializingAsCollectionSerializer<ICollection<string>, string, List<string>>(
13 new StringSerializer(BsonType.ObjectId)));
14 // Change member name
15 cm.MapMember(x => x.Title).SetElementName("title");
16 // Serialize Guid as binary subtype 4
17 cm.MapMember(x => x.MyGuid).SetSerializer(new GuidSerializer(GuidRepresentation.Standard));
18 // Store extra members in dictionary
19 cm.MapExtraElementsMember(x => x.ExtraElements);
20 // Include read-only property
21 cm.MapMember(x => x.LastChangeAt);
22});
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 Artigo
star-empty
star-empty
star-empty
star-empty
star-empty
Relacionado
Tutorial

Construindo um AI agente de IA com semântica Kernel, C# C#, OpenAI e MongoDB Atlas


Dec 02, 2024 | 13 min read
Tutorial

Fundamentos do pipeline de agregação do MongoDB C#


Oct 11, 2024 | 5 min read
Artigo

MongoDB Provider para EF Core: As atualizações mais recentes


Aug 29, 2024 | 6 min read
Tutorial

Introdução ao Unity para criar um jogo 2D


Apr 02, 2024 | 9 min read
Sumário