用好數據映射,MongoDB via Dotnet Core開發變會成一件超級快樂的事。html
MongoDB這幾年已經成爲NoSQL的頭部數據庫。git
因爲MongoDB free schema的特性,使得它在互聯網應用方面優於常規數據庫,成爲了至關一部分大廠的主數據選擇;而它的快速佈署和開發簡單的特色,也吸引着大量小開發團隊的支持。github
關於MongoDB快速佈署,我在15分鐘從零開始搭建支持10w+用戶的生產環境(二)裏有寫,須要了能夠去看看。web
做爲一個數據庫,基本的操做就是CRUD。MongoDB的CRUD,不使用SQL來寫,而是提供了更簡單的方式。mongodb
方式1、BsonDocument方式數據庫
BsonDocument方式,適合能熟練使用MongoDB Shell的開發者。MongoDB Driver提供了徹底覆蓋Shell命令的各類方式,來處理用戶的CRUD操做。json
這種方法自由度很高,能夠在不須要知道完整數據集結構的狀況下,完成數據庫的CRUD操做。c#
方式2、數據映射方式數組
數據映射是最經常使用的一種方式。準備好須要處理的數據類,直接把數據類映射到MongoDB,並對數據集進行CRUD操做。bash
下面,對數據映射的各個部分,我會逐個說明。
爲了防止不提供原網址的轉載,特在這裏加上原文連接:http://www.javashuo.com/article/p-mhcfbzyw-kn.html
這個Demo的開發環境是:Mac + VS Code + Dotnet Core 3.1.2。
創建工程:
% dotnet new sln -o demo
The template "Solution File" was created successfully.
% cd demo
% dotnet new console -o demo
The template "Console Application" was created successfully.
Processing post-creation actions...
Running 'dotnet restore' on demo/demo.csproj...
Determining projects to restore...
Restored demo/demo/demo.csproj (in 162 ms).
Restore succeeded.
% dotnet sln add demo/demo.csproj
Project `demo/demo.csproj` added to the solution.
創建工程完成。
下面,增長包mongodb.driver
到工程:
% cd demo
% dotnet add package mongodb.driver
Determining projects to restore...
info : Adding PackageReference for package 'mongodb.driver' into project 'demo/demo/demo.csproj'.
info : Committing restore...
info : Writing assets file to disk. Path: demo/demo/obj/project.assets.json
log : Restored /demo/demo/demo.csproj (in 6.01 sec).
項目準備完成。
看一下目錄結構:
% tree .
.
├── demo
│ ├── Program.cs
│ ├── demo.csproj
│ └── obj
│ ├── demo.csproj.nuget.dgspec.json
│ ├── demo.csproj.nuget.g.props
│ ├── demo.csproj.nuget.g.targets
│ ├── project.assets.json
│ └── project.nuget.cache
└── demo.sln
mongodb.driver
是MongoDB官方的數據庫SDK,從Nuget上安裝便可。
建立數據映射的模型類CollectionModel.cs
,如今是個空類,後面全部的數據映射相關內容會在這個類進行說明:
public class CollectionModel
{
}
並修改Program.cs
,準備Demo
方法,以及鏈接數據庫:
class Program
{
private const string MongoDBConnection = "mongodb://localhost:27031/admin";
private static IMongoClient _client = new MongoClient(MongoDBConnection);
private static IMongoDatabase _database = _client.GetDatabase("Test");
private static IMongoCollection<CollectionModel> _collection = _database.GetCollection<CollectionModel>("TestCollection");
static async Task Main(string[] args)
{
await Demo();
Console.ReadKey();
}
private static async Task Demo()
{
}
}
從上面的代碼中,咱們看到,在生成Collection
對象時,用到了CollectionModel
:
IMongoDatabase _database = _client.GetDatabase("Test");
IMongoCollection<CollectionModel> _collection = _database.GetCollection<CollectionModel>("TestCollection");
這兩行,其實就完成了一個映射的工做:把MongoDB
中,Test
數據庫下,TestCollection
數據集(就是SQL中的數據表),映射到CollectionModel
這個數據類中。換句話說,就是用CollectionModel
這個類,來完成對數據集TestCollection
的全部操做。
保持CollectionModel
爲空,咱們往數據庫寫入一行數據:
private static async Task Demo()
{
CollectionModel new_item = new CollectionModel();
await _collection.InsertOneAsync(new_item);
}
執行,看一下寫入的數據:
{
"_id" : ObjectId("5ef1d8325327fd4340425ac9")
}
OK,咱們已經寫進去一條數據了。由於映射類是空的,因此寫入的數據,也只有_id
一行內容。
可是,爲何會有一個_id
呢?
MongoDB數據集中存放的數據,稱之爲文檔(Document
)。每一個文檔在存放時,都須要有一個ID,而這個ID的名稱,固定叫_id
。
當咱們創建映射時,若是給出_id
字段,則MongoDB會採用這個ID作爲這個文檔的ID,若是不給出,MongoDB會自動添加一個_id
字段。
例如:
public class CollectionModel
{
public ObjectId _id { get; set; }
public string title { get; set; }
public string content { get; set; }
}
和
public class CollectionModel
{
public string title { get; set; }
public string content { get; set; }
}
在使用上是徹底同樣的。惟一的區別是,若是映射類中不寫_id
,則MongoDB自動添加_id
時,會用ObjectId
做爲這個字段的數據類型。
ObjectId
是一個全局惟一的數據。
固然,MongoDB容許使用其它類型的數據做爲ID,例如:string
,int
,long
,GUID
等,但這就須要你本身去保證這些數據不超限而且惟一。
例如,咱們能夠寫成:
public class CollectionModel
{
public long _id { get; set; }
public string title { get; set; }
public string content { get; set; }
}
咱們也能夠在類中修改_id
名稱爲別的內容,但須要加一個描述屬性BsonId
:
public class CollectionModel
{
[BsonId]
public ObjectId topic_id { get; set; }
public string title { get; set; }
public string content { get; set; }
}
這兒特別要注意:BsonId
屬性會告訴映射,topic_id
就是這個文檔數據的ID。MongoDB在保存時,會將這個topic_id
轉成_id
保存到數據集中。
在MongoDB數據集中,ID字段的名稱固定叫_id
。爲了代碼的閱讀方便,能夠在類中改成別的名稱,但這不會影響MongoDB中存放的ID名稱。
修改Demo代碼:
private static async Task Demo()
{
CollectionModel new_item = new CollectionModel()
{
title = "Demo",
content = "Demo content",
};
await _collection.InsertOneAsync(new_item);
}
跑一下Demo,看看保存的結果:
{
"_id" : ObjectId("5ef1e1b1bc1e18086afe3183"),
"title" : "Demo",
"content" : "Demo content"
}
就是常規的數據字段,直接寫就成。
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; }
}
保存後的數據:
{
"_id" : ObjectId("5ef1e9caa9d16208de2962bb"),
"title" : "Demo",
"content" : "Demo content",
"favor" : NumberInt(100)
}
說Decimal特殊,是由於MongoDB在早期,是不支持Decimal的。直到MongoDB v3.4開始,數據庫才正式支持Decimal。
因此,若是使用的是v3.4之後的版本,能夠直接使用,而若是是之前的版本,須要用如下的方式:
[BsonRepresentation(BsonType.Double, AllowTruncation = true)]
public decimal price { get; set; }
其實就是把Decimal經過映射,轉爲Double存儲。
把類做爲一個數據集的一個字段。這是MongoDB做爲文檔NoSQL數據庫的特點。這樣能夠很方便的把相關的數據組織到一條記錄中,方便展現時的查詢。
咱們在項目中添加兩個類Contact
和Author
:
public class Contact
{
public string mobile { get; set; }
}
public class Author
{
public string name { get; set; }
public List<Contact> contacts { get; set; }
}
而後,把Author
加到CollectionModel
中:
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; }
}
嗯,開始變得有點複雜了。
完善Demo代碼:
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);
}
保存的數據是這樣的:
{
"_id" : ObjectId("5ef1e635ce129908a22dfb5e"),
"title" : "Demo",
"content" : "Demo content",
"favor" : NumberInt(100),
"author" : {
"name" : "WangPlus",
"contacts" : [
{
"mobile" : "13800000000"
},
{
"mobile" : "13811111111"
}
]
}
}
這樣的數據結構,用着不要太爽!
枚舉字段在使用時,跟類字段類似。
建立一個枚舉TagEnumeration
:
public enum TagEnumeration
{
CSharp = 1,
Python = 2,
}
加到CollectionModel
中:
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代碼:
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,
};
/* 後邊代碼略過 */
}
運行後看數據:
{
"_id" : ObjectId("5ef1eb87cbb6b109031fcc31"),
"title" : "Demo",
"content" : "Demo content",
"favor" : NumberInt(100),
"author" : {
"name" : "WangPlus",
"contacts" : [
{
"mobile" : "13800000000"
},
{
"mobile" : "13811111111"
}
]
},
"tag" : NumberInt(1)
}
在這裏,tag
保存了枚舉的值。
咱們也能夠保存枚舉的字符串。只要在CollectionModel
中,tag
聲明上加個屬性:
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; }
[BsonRepresentation(BsonType.String)]
public TagEnumeration tag { get; set; }
}
數據會變成:
{
"_id" : ObjectId("5ef1ec448f1d540919d15904"),
"title" : "Demo",
"content" : "Demo content",
"favor" : NumberInt(100),
"author" : {
"name" : "WangPlus",
"contacts" : [
{
"mobile" : "13800000000"
},
{
"mobile" : "13811111111"
}
]
},
"tag" : "CSharp"
}
日期字段會稍微有點坑。
這個坑其實並不源於MongoDB,而是源於C#的DateTime
類。咱們知道,時間根據時區不一樣,時間也不一樣。而DateTime
並不許確描述時區的時間。
咱們先在CollectionModel
中增長一個時間字段:
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; }
[BsonRepresentation(BsonType.String)]
public TagEnumeration tag { get; set; }
public DateTime post_time { get; set; }
}
修改Demo:
private static async Task Demo()
{
CollectionModel new_item = new CollectionModel()
{
/* 前邊代碼略過 */
post_time = DateTime.Now, /* 2020-06-23T20:12:40.463+0000 */
};
/* 後邊代碼略過 */
}
運行看數據:
{
"_id" : ObjectId("5ef1f1b9a75023095e995d9f"),
"title" : "Demo",
"content" : "Demo content",
"favor" : NumberInt(100),
"author" : {
"name" : "WangPlus",
"contacts" : [
{
"mobile" : "13800000000"
},
{
"mobile" : "13811111111"
}
]
},
"tag" : "CSharp",
"post_time" : ISODate("2020-06-23T12:12:40.463+0000")
}
對比代碼時間和數據時間,會發現這兩個時間差了8小時 - 正好的中國的時區時間。
MongoDB規定,在數據集中存儲時間時,只會保存UTC時間。
若是隻是保存(像上邊這樣),或者查詢時使用時間做爲條件(例如查詢post_time < DateTime.Now
的數據)時,是可使用的,不會出現問題。
可是,若是是查詢結果中有時間字段,那這個字段,會被DateTime
默認設置爲DateTimeKind.Unspecified
類型。而這個類型,是無時區信息的,輸出顯示時,會形成混亂。
爲了不這種狀況,在進行時間字段的映射時,須要加上屬性:
[BsonDateTimeOptions(Kind = DateTimeKind.Local)]
public DateTime post_time { get; set; }
這樣作,會強制DateTime
類型的字段爲DateTimeKind.Local
類型。這時候,從顯示到使用就正確了。
可是,別高興的太早,這兒還有一個可是。
這個可是是這樣的:數據集中存放的是UTC時間,跟咱們正常的時間有8小時時差,若是咱們須要按日統計,比方天天的銷售額/點擊量,怎麼搞?上面的方式,解決不了。
固然,基於MongoDB自由的字段處理,能夠把須要統計的字段,按年月日時分秒拆開存放,像下面這樣的:
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; }
}
能解決,可是Low哭了有沒有?
下面,終極方案來了。它就是:改寫MongoDB中對於DateTime
字段的序列化類。噹噹噹~~~
先建立一個類MyDateTimeSerializer
:
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)]
必定不要添加,要否則就等着哭吧:P
建立完了,怎麼用?
若是你只想對某個特定映射的特定字段使用,比方只對CollectionModel
的post_time
字段來使用,能夠這麼寫:
[BsonSerializer(typeof(MyDateTimeSerializer))]
public DateTime post_time { get; set; }
或者全局使用:
BsonSerializer.RegisterSerializer(typeof(DateTime), new MongoDBDateTimeSerializer());
BsonSerializer
是MongoDB.Driver的全局對象。因此這個代碼,能夠放到使用數據庫前的任何地方。例如在Demo中,我放在Main
裏了:
static async Task Main(string[] args)
{
BsonSerializer.RegisterSerializer(typeof(DateTime), new MyDateTimeSerializer());
await Demo();
Console.ReadKey();
}
這回看數據,數據集中的post_time
跟當前時間顯示徹底同樣了,你統計,你分組,能夠隨便霍霍了。
這個需求很奇怪。咱們但願在一個Key-Value的文檔中,保存一個Key-Value的數據。但這個需求又是真實存在的,比方保存一個用戶的標籤和標籤對應的命中次數。
數據聲明很簡單:
public Dictionary<string, int> extra_info { get; set; }
MongoDB定義了三種保存屬性:Document
、ArrayOfDocuments
、ArrayOfArrays
,默認是Document
。
屬性寫法是這樣的:
[BsonDictionaryOptions(DictionaryRepresentation.ArrayOfDocuments)]
public Dictionary<string, int> extra_info { get; set; }
這三種屬性下,保存在數據集中的數據結構有區別。
DictionaryRepresentation.Document
:
{
"extra_info" : {
"type" : NumberInt(1),
"mode" : NumberInt(2)
}
}
DictionaryRepresentation.ArrayOfDocuments
:
{
"extra_info" : [
{
"k" : "type",
"v" : NumberInt(1)
},
{
"k" : "mode",
"v" : NumberInt(2)
}
]
}
DictionaryRepresentation.ArrayOfArrays
:
{
"extra_info" : [
[
"type",
NumberInt(1)
],
[
"mode",
NumberInt(2)
]
]
}
這三種方式,從數據保存上並無什麼區別,但從查詢來說,若是這個字段須要進行查詢,那三種方式區別很大。
若是採用BsonDocument方式查詢,DictionaryRepresentation.Document
無疑是寫着最方便的。
若是用Builder方式查詢,DictionaryRepresentation.ArrayOfDocuments
是最容易寫的。
DictionaryRepresentation.ArrayOfArrays
就算了。數組套數組,查詢條件寫死人。
我本身在使用時,多數狀況用DictionaryRepresentation.ArrayOfDocuments
。
上一章介紹了數據映射的完整內容。除了這些內容,MongoDB還給出了一些映射屬性,供你們看心情使用。
這個屬性是用來改數據集中的字段名稱用的。
看代碼:
[BsonElement("pt")]
public DateTime post_time { get; set; }
在不加BsonElement
的狀況下,經過數據映射寫到數據集中的文檔,字段名就是變量名,上面這個例子,字段名就是post_time
。
加上BsonElement
後,數據集中的字段名會變爲pt
。
看名稱就知道,這是用來設置字段的默認值的。
看代碼:
[BsonDefaultValue("This is a default title")]
public string title { get; set; }
當寫入的時候,若是映射中不傳入值,則數據庫會把這個默認值存到數據集中。
這個屬性是用來在映射類中的數據類型和數據集中的數據類型作轉換的。
看代碼:
[BsonRepresentation(BsonType.String)]
public int favor { get; set; }
這段表明表示,在映射類中,favor
字段是int
類型的,而存到數據集中,會保存爲string
類型。
前邊Decimal
轉換和枚舉轉換,就是用的這個屬性。
這個屬性用來忽略某些字段。忽略的意思是:映射類中某些字段,不但願被保存到數據集中。
看代碼:
[BsonIgnore]
public string ignore_string { get; set; }
這樣,在保存數據時,字段ignore_string
就不會被保存到數據集中。
數據映射自己沒什麼新鮮的內容,但在MongoDB中,若是用好了映射,開發過程從效率到爽的程度,都不是SQL能夠相比的。正所謂:
一入Mongo深似海,今後SQL是路人。
謝謝你們!
(全文完)
本文的配套代碼在https://github.com/humornif/Demo-Code/tree/master/0015/demo
![]() |
微信公衆號:老王Plus 掃描二維碼,關注我的公衆號,能夠第一時間獲得最新的我的文章和內容推送 本文版權歸做者全部,轉載請保留此聲明和原文連接 |