http://www.mongodb.org/display/DOCS/CSharp+Driver+Tutorial#CSharpDriverTutorial-TheC%23Driver
讀書筆記
C# Driver
之前看了Bson類庫,現在學習C# Driver
只有少部分的C# Driver類是多線程安全的。比如MongoClient,MongoServer,MongoDatabase, MongoCollection 以及MongoGridFS。一般常用的類存在多線程問題,包括MongoCursor以及Bson類庫中的所有類(除了其中的BsonSymbolTable是線程安全的)。
所有的類的靜態屬性值和函數方法都不會引起多線程問題。
MongoClient類
這個類提供使用MongoDB server的基本對象。與MongoDB server服務進行鏈接的時候,client自動進行連接。(使用了連接池來進行更有效的連接)
在連接一個副本集的時候,有且只用一個MongoClient實例。
When you are connecting to a replica set you will still use only one instance of MongoClient, which represents the replica set as a whole. The driver automatically finds all the members of the replica set and identifies the current primary.
這個類的實例不會引起多線程問題。
除非其他設置,在默認設置情況下,所有操作需要一個WriteConcern,一個寫入確定語句。另外,默認情況下,所有的寫操作會鎖定,直到server知道要進行寫操作。
最簡單的數據庫連接是使用Connection string。標准的Connection string如下:
mongodb://[username:password@]hostname[:port][/[database][?options]]
在使用認證的mongodb服務器上,username和password必須填寫。
port號碼是可選的。默認的是27017.
如果要連接多個服務器,可以直接填寫多個服務器名(以及需要的端口號),並且以‘,’分割。如下:
mongodb://server1,server2:27017,server2:27018
上面這段connection string 連接了三個數據庫服務,由於多數據庫服務是模糊不清的,不能分辨服務是否復本集,或者是多數據庫服務。drive驅動會跳過connection string的語法檢查,直接連接進數據庫服務器,讓server自己檢查他們的類別。還有一些辦法在連接的時候就指定數據服務器的類別,就是在connection string里面直接描述。如下:
mongodb://server1,server2:27017,server2:27018/?connect=replicaset
可用的連接模式包括:automatic (默認), direct, replica set, 以及shardrouter。連接的規則如下:
1、如果指定了某種連接模式,則直接使用否則使用默認的automatic。
2、如果在connection string中有replica set name,則使用replica set模式
3、如果connection string中僅有一個服務器,則使用direct模式
4、另外,連接服務之后,服務決定連接的模式
注意:如果有多服務器列表連接,其中有一個是復本集的一個,而其他不是,則連接模式將成為non-deterministic(未決定)。確定connection string中沒有混合服務類型。
當連接模式指定成為replica set,但是driver接口還是會找到primary服務器,即使該服務器不在connection連接列表中。直到connection列表中的一個服務器的回應(這個回應包括replica set以及現有的primary服務)。另外,即使在初始化語句完成之后,其他次級服務器也會被發現,並且自動加入到混合集群。這樣,如果你有添加以及刪除,移動replica set,driver接口會自己處理這些改變。
順便提到,假設你想要直接連接入一個replica set並且無論它是否是現在的primary(也許只是想監控下它的運行狀態或者進行只讀語句),可以使用下面連接語句:
mongodb://server2/?connect=direct;readpreference=nearest
可以在下面的鏈接獲取比較齊全的connection string文檔
http://www.mongodb.org/display/DOCS/Connections
更加深入地額:
http://docs.mongodb.org/manual/applications/replication/#replica-set-read-preference
這些不感興趣,大概是driver連接的一個設置
通過在connection string里面加入“ssl=true”選項來設置
mongodb://server2/?ssl=true
在默認的情況下,server是通過本地的受信任的證書機構獲取許可。在一些測試環境下面,測試server沒有簽署證書,為了緩解這個情況,可以使用在connection string里面添加“sslverifycertificate=false”來屏蔽所有certificate errors(認證錯誤)。
Authentication
MongoDB支持兩種認證方式。一種是在程序執行時,調用特定的方法。在執行特定的方法時,認證將會被使用。另外一種健壯的方法是在MongoCredentialsStore存儲認證信息。
下面是一個例子,使用credential store來確定admin和“foo”數據庫的認證信息。除了使用“admin”以及“foo”連接入數據庫,還可以使用默認的認證“test”。
var url = new MongoUrl("mongodb://test:user@localhost:27017"); var settings = MongoClientSettings.FromUrl(url); var adminCredentials = new MongoCredentials("admin", "user", true); settings.CredentialsStore.Add("admin", adminCredentials); var fooCredentials = new MongoCredentials("foo", "user", false); settings.CredentialsStore.Add("foo", fooCredentials); var client = new MongoClient(settings); 我感覺類似SQL語句:
GRANT ALL PRIVILEGES ON foo.* TO 'test'@'localhost' WITH GRANT OPTION; GetServer method
在MongoClient實例中調用GetServer方法獲取MongoServer的實例。
MongoServer class
使用MongoServer類可以進行更多的控制操作。它使用了先進的技術通過一個單個的socket獲取數據庫以及進行一系列的數據庫操作,並且保持數據庫的一致性。
通過這個方法訪問數據庫
例子代碼:
MongoClient client = new MongoClient(); // connect to localhost MongoServer server = client.GetServer(); MongoDatabase test = server.GetDatabase("test"); MongoCredentials credentials = new MongoCredentials("username", "password"); MongoDatabase salaries = server.GetDatabase("salaries", credentials);
大多數的數據庫設置從server對象中繼承過來,並且提供了GetDatabase的重載。要override其他設置,可以調用CreateDataBaseSetting,在調用GetDataBase之前,改變設置。比如下面這樣:
var databaseSettings = server.CreateDatabaseSettings("test"); databaseSettings.SlaveOk = true; var database = server.GetDatabase(databaseSettings);
GetDataBse會維持它返回的數據庫實例,如果你再次調用這個函數,它會返回一個與之前完全一樣的回來。
RequestStart/RequestDone methods
有些時候在系列操作執行的時候,為了保證正確的結果,需要在同一個connection上執行。但是這個是極少數的案例,在大多數情況下,沒有必要調用RequestStart/RequestDone。使用RequestStart可以將寫操作在同一個connection里面成操作列。直到server被要求執行時一起執行。
在調用RequestStart和RequestDone的時候,系統將向線程池預約一個線程,如下:
using(server.RequestStart(database)) { // 一系列的操作將在同一個connection中一起執行 } 這個database參數簡單的表明了你的request要使用的數據庫。
RequestStart在當前線程使用一個漸增的計數器,在任務完成時,計數器再遞減。保留着的connection不會直接結束還給連接池,而是等到計數器遞減至零。這個意味着RequestStart能夠被嵌套地調用。
This means that calls to RequestStart can be nested and the right thing will happen.
注意:RequestStart返回一個IDisposable(接口)。如果你在使用RequestStart的時候不使用鎖定塊,RequestDone就必須調用來釋放connection。
Other properties and methods
可以通過API文檔來查看更加深入的屬性和方法。
MongoDatabase class
這是一個描述MongoDB Server的類。通常情況下,一個database只有一個這個類的實例,除非你使用了不同的設置連接了相同的database。因為在這個情況下面,針對每個設置,都有一個實例。
這個類的實例不會產生多線程問題。
GetCollection method
這個方法返回的是一個database連接對象。當我們對這個collection對象發送請求時,我們也對這個collection做了默認的文檔類型設置。比如:
MongoDatabase hr = server.GetDatabase("hr"); MongoCollection<Employee> employees = hr.GetCollection<Employee>("employees");
一個collection不會限制成只有一個文檔類型。默認的文檔類型只是為了更加方便的使用這中文檔類型。當你需要的時候,你完全可以定義不同的文檔。
大多數的collection設置都是繼承了collection對象,並且提供了GetCollection的多態性來方便你來重寫一些常用的使用設置。要重寫其他的設置,先調用CreateCollectionSetting來改變設置,然后再調用GetCollection方法。比如下面代碼:
var collectionSettings = database.CreateCollectionSettings<TDocument>("test"); collectionSettings.SlaveOk = true; var collection = database.GetCollection(collectionSettings);
GetCollection維持一個表的實例,如果你再次調用這個GetCollection,它會返回一樣的內容。
Other properties and methods
查看api文檔
MongoCollection<TDefaultDocument> class
這個類代表一個MongoDB database的collection。這個<TDefaultDocument>為這個collection定義了默認的文檔。
這個類的實例不會造成多線程問題。
Insert<TDefaultDocument> method
插入函數。插入的對象可以是BsonDocument的實例對象,也可以是任何成功轉換成BSON文檔的類實例。例如:
MongoCollection<Book> books = database.GetCollection<Book>("books"); Book book = new Book { Author = "Ernest Hemingway", Title = "For Whom the Bell Tolls" }; books.Insert(book);
如果你要插入多重文檔,InsertBatch要比Insert有效。
FindOne以及FindOneAs方法
要從collection中檢索文檔,可以使用這個方法。FindOne是最簡單的。它會返回結果的第一個文檔。例如:
MongoCollection<Book> books; Book book = books.FindOne();
如果你想檢索一個文檔,但是它不是<TDefaultDocument>類型的,你需要用到FindOneAs方法。它允許你返回你需要的文檔類型。例如:
MongoCollection<Book> books; BsonDocument document = books.FindOneAs<BsonDocument>();
這個默認的類型是Book類型,但是我們返回的是BsonDocument類型的實例。
Find以及FindAs方法
Find和FindAs方法是用query語句來告訴服務器返回什么的文檔。這個query(查詢語句)的類型是IMongoQuery。IMongoQuery是一個標記接口,被類識別后可以用來作為查詢語言。最常見的方法是,我們可以使用Query創建類或者是QueryDocument類來創建query語句。另外如果使用QueryWrapper封裝任何類型query語句,query都可以被轉變成BSON文檔類型。
使用QueryDocument
MongoCollection<BsonDocument> books; var query = new QueryDocument("author", "Kurt Vonnegut"); foreach (BsonDocument book in books.Find(query)) { // do something with book }
使用Query Builder
MongoCollection<BsonDocument> books; var query = Query.EQ("author", "Kurt Vonnegut"); foreach (BsonDocument book in books.Find(query)) { // do something with book }
使用其他任何類型,然后封裝成query語句
MongoCollection<BsonDocument> books; var query = Query.Wrap(new { author = "Kurt Vonnegut" }); foreach (BsonDocument book in books.Find(query)) { // do something with book }
使用FindAs來獲取非默認類型的返回文檔
MongoCollection<BsonDocument> books; var query = Query<Book>.EQ(b => b.Author, "Kurt Vonnegut"); foreach (Book book in books.FindAs<Book>(query)) { // do something with book }
Save<TDocument>方法
Save方法是Insert和Update的組合。如果文檔的屬性是有值的,它會成為Update,來對文檔更新。否則將會創建一個新文檔調用Insert方法。
例如下面:糾正書的標題
MongoCollection<BsonDocument> books; var query = Query.And( Query.EQ("author", "Kurt Vonnegut"), Query.EQ("title", "Cats Craddle") ); BsonDocument book = books.FindOne(query); if (book != null) { book["title"] = "Cat's Cradle"; books.Save(book); }
TDocument必須要有個ID元素,否則你將調用Insert,將文檔插入。
Update方法
用來更新文檔。上面的Save代碼可以一下面的方式重寫
MongoCollection<BsonDocument> books; var query = new QueryDocument { { "author", "Kurt Vonnegut" }, { "title", "Cats Craddle" } }; var update = new UpdateDocument { { "$set", new BsonDocument("title", "Cat's Cradle") } }; BsonDocument updatedBook = books.Update(query, update); 或者使用Query和Update builders
MongoCollection<BsonDocument> books; var query = Query.And( Query.EQ("author", "Kurt Vonnegut"), Query.EQ("title", "Cats Craddle") ); var update = Update.Set("title", "Cat's Cradle"); BsonDocument updatedBook = books.Update(query, update);
FindAndModify方法
使用FindAndModify方法,你可以在一個原子操作里面查找一個匹配的文檔並且修改更新.FindAndModify通常用於單個的文檔,如果匹配了多個文檔,可以使用標准的排序方法匹配到你自己想要修改的文檔。
可以看下面文檔使用FindAndModify
http://www.mongodb.org/display/DOCS/findAndModify+Command
調用FindAndModify的方法如下:
var jobs = database.GetCollection("jobs"); var query = Query.And( Query.EQ("inprogress", false), Query.EQ("name", "Biz report") ); var sortBy = SortBy.Descending("priority"); var update = Update. .Set("inprogress", true) .Set("started", DateTime.UtcNow); var result = jobs.FindAndModify( query, sortBy, update, true // return new document ); var chosenJob = result.ModifiedDocument;
MapReduce方法
Map/Reduce是一種從collection中聚合數據的方法。每個文檔(或者使用選擇query語句產生的是子集)被發送至map函數,map函數會產生一個中間的值。這個中間的值會傳送至reduce函數進行數據的聚合。
下面的例子采集自MongoDB:The Definitive Guide(MongDB權威解析)的87頁。它計算在collection中找到的每個key要被計算多少次
var map = "function() {" + " for (var key in this) {" + " emit(key, { count : 1 });" + " }" + "}"; var reduce = "function(key, emits) {" + " total = 0;" + " for (var i in emits) {" + " total += emits[i].count;" + " }" + " return { count : total };" + "}"; var mr = collection.MapReduce(map, reduce); foreach (var document in mr.GetResults()) { Console.WriteLine(document.ToJson()); }
map/reduce是JavaScript程序
深入了解看api文檔
Find函數不會直接返回確切的查詢結果。而是它返回一個游標來枚舉檢索查詢結果。這個query查詢語句實際上在你嘗試檢索第一個結果的時候才會送至服務器。這意味着,你可以通過控制游標檢索結果來得到你感興趣的內容。
MongoCursor類的實例會造成多線程問題。至少在它被凍結之前不要使用。一旦它是凍結狀態下,它就不會造成多線程安全問題。因為它是只讀狀態。
Enumerating a cursor檢索游標
使用C#中的foreach是最方便的檢索方式
var query = Query.EQ("author", "Ernest Hemingway"); var cursor = books.Find(query); foreach (var book in cursor) { // do something with book } 你也可以使用使用LINQ定義的方法來檢索游標
var query = Query.EQ("author", "Ernest Hemingway"); var cursor = books.Find(query); var firstBook = cursor.FirstOrDefault(); var lastBook = cursor.LastOrDefault();
Modifying a cursor before enumerating it在檢索之前改變游標
1、直接修改游標的屬性值
2、通過流暢的接口設置屬性值
例如:我要跳過前100個結果,並且將顯示結果限制在10個
var query = Query.EQ("status", "pending"); var cursor = tasks.Find(query); cursor.Skip = 100; cursor.Limit = 10; foreach (var task in cursor) { // do something with task }
或者使用流暢的接口
var query = Query.EQ("status", "pending"); foreach (var task in tasks.Find(query).SetSkip(100).SetLimit(10)) { // do something with task }
Modifiable properties of a cursor
下面的屬性值是可修改的
- BatchSize (SetBatchSize)
- Fields (SetFields)
- Flags (SetFlags)
- Limit (SetLimit)
- Options (SetOption and SetOptions)
- SerializationOptions (SetSerializationOptions)
- Skip (SetSkip)
- SlaveOk (SetSlaveOk)
WriteConcern 有很多等級,這個類就是用來描述這個等級的。WriteConcern 應用只用來操作那些沒有返回的操作。比如Insert 、Save、Update和Remove。
這個主要就是當Insert、Save、Update和Remove指令發送給服務器之后,會有一個GetLassError命令來確認這些操作是否成功執行了。