类型映射
这里在C#中所使用的连接MongoDB数据库的依赖库为MongoDB.Driver
,使用前先到Nuget中进行安装。
默认情况下,在我们对MongoDB数据库进行CRUD时,MongoDB.Driver(以后简称驱动库)会自动为我们根据属性、属性类型,将实体类型转换为对应的BSON。
-
实体类
csharppublic class Clothing { public ObjectId Id { get; set; } public string Name { get; set; } public bool InStock { get; set; } public double Price { get; set; } public List<string> ColorSelection { get; set; } }
-
对应的BSON
csharp{ "_id": ObjectId("..."), "Name": "Long Sleeve Shirt", "InStock": true, "Price": 17.99, "ColorSelection": [ "black", "navy", "red" ] }
一、常用特性与类型映射器配置
1、主键Id
默认情况下,驱动映射器将任何名为Id
、id
或_id
的公共属性映射为BSON的_id
字段。如果要显式选择要映射到_id
字段的属性,可以使用[BsonId]
特性。
- 注意,如果自己指定
_id
字段,那么实体类中不能再包含Id
或_id
属性。而且,[BsonId]
只能使用在一个属性上。 Id
属性建议使用ObjectId
或者GUID
类型,这样在存储数据时MongoDB会自动为我们维护唯一的ID,我们不用操心。
csharp
public class House
{
[BsonId]
public ObjectId Identifier { get; set; }
}
2、只读属性的处理
驱动库的自动映射器不会包含实体类中的只读属性,如果我们希望类中的只读属性在存储数据时候也一起作为数据字段存储到数据库中,那么可以有两种方式进行处理。
方式一 :使用 [BsonElement]
特性
在该属性上使用[BsonElement]
特性。
-
实体类
csharppublic class Clothing { public ObjectId Id { get; set; } public string Name { get; set; } public bool InStock { get; set; } public double Price { get; set; } public List<string> ColorSelection { get; set; } [BsonElement] public int Upc { get; } }
方式二:注册类型映射器
-
注册
csharpBsonClassMap.RegisterClassMap<Clothing>(classMap => { classMap.AutoMap(); //这里表示其他属性按照默认的自动序列化 classMap.MapProperty(c => c.Upc); });
注意
- 由于该属性只读,所以可以进行序列化后存入数据库中,但是却无法在查询后进行反序列化。
- 类型映射器的注册建议在行初始化MongoDB连接时进行。
3、字段名称设置
默认情况下,会将属性名称作为数据库字段名保存,如果想要设置别的字段名,可以使用[BsonElement("fieldName")]
进行设置。
csharp
public class House
{
public Guid Id { get; set; }
[BsonElement("year_built")]
public int YearBuilt { get; set; }
}
4、类型设置
如果要将C#属性的类型转为对应的BSON类型,可以使用[BsonRepresentation(type)]
特性,需要注意的是,这只在C#基本类型可以转为指定的BSON类型时才有效。驱动库已经为我们提供了BsonType
枚举里面定义了BSON的各种类型。
csharp
public class House
{
public Guid Id { get; set; }
[BsonRepresentation(BsonType.Int32)]
public char YearBuilt { get; set; }
}
5、字段排序
默认情况下,会根据实体类的字段顺序进行映射保存,如果想要指定属性在数据库中的字段顺序,可以通过[BsonElement(Order = index)]
来指定。
csharp
public class House
{
public Guid Id { get; set; }
[BsonElement(Order = 2)]
public int YearBuilt { get; set; }
[BsonElement(Order = 1)]
public string Style { get; set; }
}
6、忽略属性
直接忽略属性
如果希望忽略实体类中的某个属性,在存储时候不进行序列化和数据存储,可以在属性上使用[BsonIgnore]
特性。
-
示例
csharppublic class House { public Guid Id { get; set; } [BsonIgnore] public int YearBuilt { get; set; } public string Style { get; set; } }
忽略默认值属性
如果希望属性在使用默认值时(也就是属性没有进行赋值时),映射器忽略掉该属性,可以使用[BsonIgnoreIfDefault]
特性
-
示例
csharppublic class House { public Guid Id { get; set; } [BsonIgnoreIfDefault] public int YearBuilt { get; set; } }
这里也可以使用注册类型映射器的方式
-
示例
csharpBsonClassMap.RegisterClassMap<House>(classMap => { classMap.AutoMap();//这里表示其他属性按照默认的自动序列化 classMap.MapMember(h => h.YearBuilt).SetIgnoreIfDefault(true); });
7、默认值
默认情况下,在进行反序列化时,从数据库中如果找不到我们实体类某个属性的对应字段(这里是直接找不到,不存在该字段)时,会直接给该属性赋值对应类型的默认值,如果希望进行默认值的设置,可以通过两种方法来实现。
- 注意,这里是反序列化的过程中设置默认值,而不是往数据库保存数据库的过程。
方式一 :使用[BsonDefaultValue(value)]
特性
-
示例
csharppublic class House { public Guid Id { get; set; } [BsonDefaultValue(1900)] public int YearBuilt { get; set; } }
方式二:注册类型映射器
-
示例
csharpBsonClassMap.RegisterClassMap<House>(classMap => { classMap.AutoMap();//这里表示其他属性按照默认的自动序列化 classMap.MapMember(h => h.YearBuilt).SetDefaultValue(1900); });
结合使用[BsonDefaultValue(value)]
和[BsonIgnoreIfDefault]
可以巧妙的通过结合使用[BsonDefaultValue(value)]
和[BsonIgnoreIfDefault]
特性,来实现指定忽略指定值属性的效果。使用后发现,这样结合使用,对保存数据时能起效果。
-
示例
下例子中,当YearBuilt属性为1900时,就会忽略该属性
csharppublic class House { public Guid Id { get; set; } [BsonDefaultValue(1900)] [BsonIgnoreIfDefault] public int YearBuilt { get; set; } }
8、时间字段的处理
如果我们的实体类中包含DateTime
类型的属性,在进行数据保存时,映射器会对该属性自动进行时区的转换处理转为UTC时间。而我们可以通过[BsonDateTimeOptions]
特性设置在进行反序列化时,时间数据以什么形式展示。
只包含日期
如果我们的DateTime
属性只包含日期,而不包含时间,可以直接使用[BsonDateTimeOptions(DateOnly = true)]
特性,让映射器不对该属性进行时区的转换处理。
-
注意,此时不能直接赋值
DateTime.Now
,而是要用DateTime.Now.Date
,主要是不要让属性含有时间,否则转换时会报错。 -
示例
csharppublic class PatientRecord { public Guid Id { get; set; } [BsonDateTimeOptions(DateOnly = true)] public DateTime DateOfBirth { get; set; } = DateTime.Now.Date; }
包含时间
如果DateTime
属性包含了时间,由于保存到MongoDB时会转换为UTC时间,所以需要使用[BsonDateTimeOptions(Kind = DateTimeKind.Local)]
让驱动库在进行反序列化时,将时间转换为本地时间。
-
示例
csharppublic class PatientRecord { public Guid Id { get; set; } [BsonDateTimeOptions(Kind = DateTimeKind.Local)] public DateTime AppointmentTime { get; set; } }
9、忽略额外字段
在驱动库进行反序列化时,会将核对文档的字段与类型的属性,如果文档中出现了在类型中找不到对应属性的字段,默认情况下会直接抛出异常。
如果希望在反序列化时,忽略数据库中的额外字段,只映射类中属性对应的字段,可以通过两种方式来实现。
方式一:对类型使用[BsonIgnoreExtraElements]
特性
-
示例
csharp[BsonIgnoreExtraElements] public class Person { public string Name { get; set; } public int Age { get; set; } public List<string> Hobbies {get; set;} }
方式一:注册类型映射器
-
示例
csharpBsonClassMap.RegisterClassMap<Person>(classMap => { classMap.AutoMap();//这里表示其他属性按照默认的自动序列化 classMap.SetIgnoreExtraElements(true); });
10、支持额外字段
在驱动库进行反序列化时,会将核对文档的字段与类型的属性,如果文档中出现了在类型中找不到对应属性的字段,默认情况下会直接抛出异常。
如果我们希望在反序列化时,即使出现了额外的字段也要将其一起保存到我们的类型中时,可以通过两种方式来实现。
方式一:使用[BsonExtraElements]
特性来指定一个属性作为额外字段的载体。
-
注意,这个属性必须是
BsonDocument
类型。 -
示例
csharppublic class Person { public string Name { get; set; public int Age { get; set; } public List<string> Hobbies {get; set;} [BsonExtraElements] public BsonDocument ExtraElements {get; set;} }
方式二:注册类型映射器
-
示例
csharpBsonClassMap.RegisterClassMap<Person>(classMap => { classMap.AutoMap();//这里表示其他属性按照默认的自动序列化 classMap.MapExtraElementsMember(p => p.ExtraElements); });
11、带参构造函数
默认情况下,驱动库只有在类具有不带参数的构造函数时才能自动映射类。如果希望驱动库在反序列化时使用带参构造函数,可以通过两种方式来实现。
方式一:对指定的构造函数使用[BsonConstructor]
特性。
-
注意,如果同时对多个构造函数使用了此特性,那么驱动库会使用参数最多的构造函数。
-
示例
csharppublic class Person { public string Name { get; set; } public int Age { get; set; } public List<string> Hobbies {get; set;} [BsonConstructor] public Person(string name, string age) { Name = name; Age = age; } }
方式二:注册类型映射器
-
示例
csharpBsonClassMap.RegisterClassMap<Person>(classMap => { classMap.AutoMap(); //这里表示其他属性按照默认的自动序列化 classMap.MapCreator(p => new Person(p.Name, p.Age)); });
二、动态序列化
有时候,我们需要在程序运行过程中根据我们类型的属性值去判断是不是要让驱动库对其进行序列化进行数据存储,也就是进行动态的序列化。
对于这一点,MongoDB.Driver
规定了当我们的实体类中出现了返回bool
结果且名为ShouldSerializePropertyName
的方法时,驱动库在进行序列化时会根据这个方法返回的结果来决定是否对对应的属性进行序列化。
- 强调一下方法名称是
ShouldSerialize
+PropertyName
csharp
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public List<string> Hobbies {get; set;}
public bool ShouldSerializeAge()
{
return Age != 0;
}
}
此外,如果不想在类中使用方法的形式,那可以通过注册类型映射器的方式来实现。
csharp
BsonClassMap.RegisterClassMap<Employee>(classMap =>
{
classMap.AutoMap();
classMap.MapMember(p => c.Age).SetShouldSerializeMethod(
obj => ((Person) obj).Age != 0
);
});