简介
作为一个数据库,基本的操作就是 CRUD。MongoDB 的 CRUD,不使用 SQL 来写,而是提供了更简单的方式。
BsonDocument方式
BsonDocument 方式,适合能熟练使用 MongoDB Shell 的开发者。MongoDB Driver 提供了完全覆盖 Shell 命令的各种方式,来处理用户的 CRUD 操作。
这种方法自由度很高,可以在不需要知道完整数据集结构的情况下,完成数据库的CRUD操作。
数据映射方式
数据映射是最常用的一种方式。准备好需要处理的数据类,直接把数据类映射到 MongoDB,并对数据集进行 CRUD 操作。
字段映射
ID字段
MongoDB 数据集中存放的数据,称之为文档(Document)。每个文档在存放时,都需要有一个ID,而这个 ID 的名称,固定叫 _id,类型是 MongoDB.Bson.ObjectId。
当建立映射时,如果给出 _id 字段,则 MongoDB 会采用这个 ID 做为这个文档的 ID ,如果不给出,MongoDB 会自动添加一个 _id 字段。在使用上是完全一样的。唯一的区别是,如果映射类中不写 _id,则 MongoDB 自动添加 _id 时,会用 ObjectId 作为这个字段的数据类型。
ObjectId 是一个全局唯一的数据。
MongoDB 允许使用其它类型的数据作为 ID,例如:string,int,long,GUID 等,但这就需要你自己去保证这些数据不超限并且唯一。
可以在类中修改 _id 名称为别的名称,但需要加一个描述属性 BsonId,BsonId 属性会告诉映射,topic_id 就是这个文档数据的ID 。MongoDB在保存时,会将这个 topic_id 转成 _id 保存到数据集中。
1 2 3 4 5 6 7
| public class CollectionModel { [BsonId] public ObjectId topic_id { get; set; } public string title { get; set; } public string content { get; set; } }
|
注:在 MongoDB 数据集中,ID 字段的名称固定叫 _id。为了代码的阅读方便,可以在类中改为别的名称,但这不会影响 MongoDB 中存放的 ID 名称。
2. 特殊类型 - Decimal
MongoDB 在早期,是不支持 Decimal 的。直到 MongoDB v3.4 开始,数据库才正式支持 Decimal。
所以,如果使用的是 v3.4 以后的版本,可以直接使用,而如果是以前的版本,需要用以下的方式:
1 2
| [BsonRepresentation(BsonType.Double, AllowTruncation = true)] public decimal price { get; set; }
|
其实就是把 Decimal 通过映射,转为 Double 存储。
类字段
添加两个类 Contact 和 Author:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| public class Contact { public string mobile { get; set; } } public class Author { public string name { get; set; } public List<Contact> contacts { get; set; } }
public class CollectionModel { [BsonId] public ObjectId topic_id { get; set; } public string title { get; set; } public string content { get; set; } public int favor { get; set; } public Author author { get; set; } }
private static async Task Demo() { CollectionModel new_item = new CollectionModel() { title = "Demo", content = "Demo content", favor = 100, author = new Author { name = "WangPlus", contacts = new List<Contact>(), } };
Contact contact_item1 = new Contact() { mobile = "13800000000", }; Contact contact_item2 = new Contact() { mobile = "13811111111", }; new_item.author.contacts.Add(contact_item1); new_item.author.contacts.Add(contact_item2);
await _collection.InsertOneAsync(new_item); }
|
文档结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| { "_id" : ObjectId("5ef1e635ce129908a22dfb5e"), "title" : "Demo", "content" : "Demo content", "favor" : NumberInt(100), "author" : { "name" : "WangPlus", "contacts" : [ { "mobile" : "13800000000" }, { "mobile" : "13811111111" } ] } }
|
枚举字段
创建一个枚举 TagEnumeration:
1 2 3 4 5
| public enum TagEnumeration { CSharp = 1, Python = 2, }
|
加到 CollectionModel 中:
1 2 3 4 5 6 7 8 9 10
| public class CollectionModel { [BsonId] public ObjectId topic_id { get; set; } public string title { get; set; } public string content { get; set; } public int favor { get; set; } public Author author { get; set; } public TagEnumeration tag { get; set; } }
|
Demo代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| private static async Task Demo() { CollectionModel new_item = new CollectionModel() { title = "Demo", content = "Demo content", favor = 100, author = new Author { name = "WangPlus", contacts = new List<Contact>(), }, tag = TagEnumeration.CSharp, }; Contact contact_item1 = new Contact() { mobile = "13800000000", }; Contact contact_item2 = new Contact() { mobile = "13811111111", }; new_item.author.contacts.Add(contact_item1); new_item.author.contacts.Add(contact_item2);
await _collection.InsertOneAsync(new_item); }
|
保存后的文档:注意,tag 保存了枚举的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| { "_id" : ObjectId("5ef1eb87cbb6b109031fcc31"), "title" : "Demo", "content" : "Demo content", "favor" : NumberInt(100), "author" : { "name" : "WangPlus", "contacts" : [ { "mobile" : "13800000000" }, { "mobile" : "13811111111" } ] }, "tag" : NumberInt(1) }
|
可以保存枚举的字符串。只要在 CollectionModel 中,tag 声明上加个属性:
1 2
| [BsonRepresentation(BsonType.String)] public TagEnumeration tag { get; set; }
|
数据会变成:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| { "_id" : ObjectId("5ef1ec448f1d540919d15904"), "title" : "Demo", "content" : "Demo content", "favor" : NumberInt(100), "author" : { "name" : "WangPlus", "contacts" : [ { "mobile" : "13800000000" }, { "mobile" : "13811111111" } ] }, "tag" : "CSharp" }
|
日期字段
CollectionModel 中增加一个时间字段:
1
| public DateTime post_time { get; set; }
|
MongoDB 的 datetime 存储的是 unixtimestamp ,所以默认只能是 utc0 时区的,它问题出在 C# 的 DateTime 对时区的处理上遗留的问题,可以换成 DateTimeOffset。
如果只是保存(像上边这样),或者查询时使用时间作为条件(例如查询 post_time < DateTime.Now 的数据)时,是可以使用的,不会出现问题。
但是,如果是查询结果中有时间字段,那这个字段,会被 DateTime 默认设置为 DateTimeKind.Unspecified 类型。而这个类型,是无时区信息的,输出显示时,会造成混乱。
为了避免这种情况,在进行时间字段的映射时,需要加上属性:
1 2
| [BsonDateTimeOptions(Kind = DateTimeKind.Local)] public DateTime post_time { get; set; }
|
这样做,会强制 DateTime 类型的字段为 DateTimeKind.Local 类型。这时候,从显示到使用就正确了。
数据集中存放的是 UTC 时间,跟我们正常的时间有8小时时差,如果我们需要按日统计,比方每天的销售额/点击量。
按年月日时分秒拆开存放
1 2 3 4 5 6 7 8 9
| class Post_Time { public int year { get; set; } public int month { get; set; } public int day { get; set; } public int hour { get; set; } public int minute { get; set; } public int second { get; set; } }
|
MyDateTimeSerializer
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class MyDateTimeSerializer : DateTimeSerializer { public override DateTime Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) { var obj = base.Deserialize(context, args); return new DateTime(obj.Ticks, DateTimeKind.Unspecified); } public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, DateTime value) { var utcValue = new DateTime(value.Ticks, DateTimeKind.Utc); base.Serialize(context, args, utcValue); } }
|
注意:使用这个方法,一定不要添加时间的属性 [BsonDateTimeOptions(Kind = DateTimeKind.Local)]
对某个特定映射的特定字段使用,比方只对 CollectionModel 的 post_time 字段来使用,可以这么写:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| [BsonSerializer(typeof(MyDateTimeSerializer))] public DateTime post_time { get; set; }
BsonSerializer.RegisterSerializer(typeof(DateTime), new MongoDBDateTimeSerializer());
static async Task Main(string[] args) { BsonSerializer.RegisterSerializer(typeof(DateTime), new MyDateTimeSerializer());
await Demo(); Console.ReadKey(); }
|
Dictionary字段
数据声明很简单:
public Dictionary<string, int> extra_info { get; set; }
MongoDB 定义了三种保存属性:Document、ArrayOfDocuments、ArrayOfArrays,默认是 Document。
属性写法是这样的:
1 2
| [BsonDictionaryOptions(DictionaryRepresentation.ArrayOfDocuments)] public Dictionary<string, int> extra_info { get; set; }
|
这三种属性下,保存在数据集中的数据结构有区别。多数情况用 DictionaryRepresentation.ArrayOfDocuments。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| { "extra_info" : { "type" : NumberInt(1), "mode" : NumberInt(2) } }
{ "extra_info" : [ { "k" : "type", "v" : NumberInt(1) }, { "k" : "mode", "v" : NumberInt(2) } ] }
{ "extra_info" : [ [ "type", NumberInt(1) ], [ "mode", NumberInt(2) ] ] }
|
这三种方式,从数据保存上并没有什么区别,但从查询来讲,如果这个字段需要进行查询,那三种方式区别很大。
如果采用 BsonDocument 方式查询,DictionaryRepresentation.Document 无疑是写着最方便的。
如果用 Builder 方式查询,DictionaryRepresentation.ArrayOfDocuments 是最容易写的。
DictionaryRepresentation.ArrayOfArrays 就算了。数组套数组,查询条件写死人。
BsonElement属性
用来改数据集中的字段名称。
1 2
| [BsonElement("pt")] public DateTime post_time { get; set; }
|
在不加 BsonElement 的情况下,通过数据映射写到数据集中的文档,字段名就是变量名,上面这个例子,字段名就是 post_time 。
加上 BsonElement 后,数据集中的字段名会变为 pt。
BsonDefaultValue属性
设置字段的默认值。
当写入的时候,如果映射中不传入值,则数据库会把这个默认值存到数据集中。
1 2
| [BsonDefaultValue("This is a default title")] public string title { get; set; }
|
BsonRepresentation属性
用来在映射类中的数据类型和数据集中的数据类型做转换。
1 2
| [BsonRepresentation(BsonType.String)] public int favor { get; set; }
|
表示,在映射类中,favor 字段是 int 类型的,而存到数据集中,会保存为 string 类型。
前边 Decimal 转换和枚举转换,就是用的这个属性。
BsonIgnore属性
用来忽略某些字段。
忽略的意思是:映射类中某些字段,不希望被保存到数据集中。
1 2
| [BsonIgnore] public string ignore_string { get; set; }
|
这样,在保存数据时,字段 ignore_string 就不会被保存到数据集中。
参考:
MongoDB via Dotnet Core数据映射详解