Используя атрибуты, мы можем управлять настройкой классов моделей C# и их сериализацией в документы mongodb. Все классы атрибутов расположены в пространстве имен MongoDB.Bson.Serialization.Attributes. Рассмотрим, как мы их можем использовать.
Каждый объект в базе данных имеет поле _id
, которое выполняет роль уникального идентификатора объекта. Используя атрибут BsonId
мы можем явно установить свойство, которое будет выполнять роль идентификатора:
using MongoDB.Bson.Serialization.Attributes; class Person { [BsonId] public int PersonId { get; set; } public string Name { get; set; } = ""; }
Хотя в данном случае свойство называется PersonId и имеет тип int, при создании документа данное свойство будет представлять в документе поле
_id
Атрибут BsonIgnore позволяет не учитывать свойство при сериализации объекта в документ. Например:
using MongoDB.Bson; using MongoDB.Bson.Serialization.Attributes; Person tom = new Person { Name = "Tom", Email = "tom@somemail.com" }; Console.WriteLine(tom.ToBsonDocument()); // { "Name" : "Tom" } class Person { public string Name { get; set; } = ""; [BsonIgnore] public string Email { get; set; } = ""; }
В примере выше исключается из сериализации в BsonDocument свойство Email.
По умолчанию при сериализации поля документа будут называться также, как и свойства класса C#. Атрибут BsonElement позволяет изменить название:
using MongoDB.Bson; using MongoDB.Bson.Serialization.Attributes; Person tom = new Person { Name = "Tom", Email = "tom@somemail.com" }; Console.WriteLine(tom.ToBsonDocument()); // { "Name" : "Tom", "Login" : "tom@somemail.com" } class Person { public string Name { get; set; } = ""; [BsonElement("Login")] public string Email { get; set; } = ""; }
Здесь при сериализации в BsonDocument значение свойства Email будет передаваться полю с именем "Login".
Атрибут BsonIgnoreIfNull позволяет игнорировать при сериализации свойства со значениями null. А атрибут BsonIgnoreIfDefault позволяет исключить из сериализации свойства со значениями по умолчанию. Зачем они могут пригодиться? Рассмотрим следующую ситуацию::
using MongoDB.Bson; Person tom = new Person { Name = "Tom"}; Console.WriteLine(tom.ToBsonDocument()); // { "Name" : "Tom", "Age" : 0, "Company" : null } class Person { public string Name { get; set; } = ""; public int Age { get; set; } public Company? Company { get; set; } } class Company { public string Name { get; set; } = ""; }
В этом примере для объекта Person задается объект Company. Однако в какой-то ситуации для объекта Person данный объект может отсутствовать. Например,
человек не работает ни в какой компании. Однако даже если мы не укажем компанию, такой документ все равно будет содержать данный элемент,
только у него будет значение null
.
Также в примере определено свойство Age, которое представляет возраст человека. Однако в каких-то ситуациях, возможно, не потребуется это свойство. Но даже если мы не указали значение для свойства Age, оно будет присутствовать в документе со значением 0 - то есть значением по умолчанию.
То есть для объекта
Person tom = new Person { Name = "Tom"};
Мы получим документ
{ "Name" : "Tom", "Age" : 0, "Company" : null }
Чтобы избежать этих проблем, используем атрибуты BsonIgnoreIfNull и BsonIgnoreIfDefault:
using MongoDB.Bson; using MongoDB.Bson.Serialization.Attributes; Person tom = new Person { Name = "Tom"}; Console.WriteLine(tom.ToBsonDocument()); // { "Name" : "Tom" } class Person { public string Name { get; set; } = ""; [BsonIgnoreIfDefault] public int Age { get; set; } [BsonIgnoreIfNull] public Company? Company { get; set; } } class Company { public string Name { get; set; } = ""; }
Теперь мы получим следующий документ:
{ "Name" : "Tom" }
Еще один атрибут BsonRepresentation отвечает за представление свойства в базе данных. Например:
class Person { public string Name { get; set; } = ""; [BsonRepresentation(BsonType.String)] public int Age { get; set; } }
В этом случае целочисленному свойству Age в базе данных будет соответствовать строковое поле Age из-за применения атрибута [BsonRepresentation(BsonType.String)]
.
Для настройки сопоставления классов C# с коллекциями MongoDB можно использовать класс BsonClassMap, который регистрирует принципы сопоставления. Например, возьмем тот же класс Person:
using MongoDB.Bson; using MongoDB.Bson.Serialization; BsonClassMap.RegisterClassMap<Person>(cm => { cm.AutoMap(); cm.MapMember(p => p.Name).SetElementName("username"); }); Person tom = new Person { Name = "Tom", Age = 38}; Console.WriteLine(tom.ToBsonDocument()); // { "username" : "Tom", "Age" : 38 } class Person { public string Name { get; set; } = ""; public int Age { get; set; } }
С помощью метода RegisterClassMap() определяется карта сопоставления объектов Person и BsonDocument. В частности, в данном случае для свойство Name будет сопоставляться с полем username.
Стоит отметить, что регистрация маппингов должна происходить до установки соединения с сервером MongoDB. Иначе приложение будет падать с ошибкой.
Конвенции наряду с атрибутами и BsonClassMap представляют еще один способ определения сопоставления классов и объектов BsonDocument. Конвенции определяются в виде набора - объекта ConventionPack. Этот объект может содержать набор конвенций. Каждая конвенция представляет объект класса, производного от ConventionBase. Например, переведем все имена элементов в BsonDocument в нижний регистр:
using MongoDB.Bson; using MongoDB.Bson.Serialization.Conventions; var conventionPack = new ConventionPack { new CamelCaseElementNameConvention() }; ConventionRegistry.Register("camelCase", conventionPack, t => true); Person tom = new Person { Name = "Tom", Age = 38}; Console.WriteLine(tom.ToBsonDocument()); // { "name" : "Tom", "age" : 38 } class Person { public string Name { get; set; } = ""; public int Age { get; set; } }
В данном случае создается объект ConventionPack - набор конвенций, в котором по умолчанию определяется одна конвенция -
CamelCaseElementNameConvention
, которая переводит названия свойства в camel case - имена начинаются со строчной буквы.
Но чтобы конвенция сработала, необходимо вызвать метод ConventionRegistry.Register(), который зарегистрирует конвенцию. Первый параметр этого метода представляет название конвенции, второй - объект ConventionPack, а третий - условие, при котором применяется конвенция. Здесь в качестве условия просто установлено ключевое слово true, то есть конвенция будет применяться ко всем свойствам.
В результате при выводе на консоль названия всех ключей в документе будут переведены в нижний регистр:
{ "name" : "Tom", "age" : 38 }