標籤:
【目標】:本文將以實戰的形式,向您展示如何用C#訪問MongoDB,完成常見的資料庫操作任務, 同時,也將介紹MongoDB的用戶端(命令列工作模式)以及一些基礎的命令。
【說明】:MongoDB是什嗎?有什麼用?如果不清楚這些問題的,請自己google一下吧。
【適合對象】:完全沒有接觸MongoDB或對MongoDB有一點瞭解的C#開發人員。因此本文是一篇入門級的文章。
【樣本項目】:本文的完整樣本是一個簡單的【客戶,商品,訂單】業務情境, 預覽介面效果請點擊此處(但並不完全相同),也包含下載樣本項目的源碼。
讓我們開始MongoDB的實戰入門吧。
回到頂部下載MongoDB,並啟動它
您可以在這個地址下載到MongoDB: http://www.mongodb.org/downloads, 本文將以【mongodb-win32-i386-1.8.2-rc2】來示範MongoDB的使用。
下載好了嗎?我們繼續吧。請解壓縮您剛才下載的MongoDB的zip壓縮包,進入解包的bin目錄,會發現有一堆exe檔案。 現在,請開啟命令列視窗並切換到剛才的bin目錄,然後輸入以下命令:
這裡,我運行了程式mongod.exe,並告訴它資料檔案的儲存目錄(這個目錄要事先建立好),至於mongod的更多命令列參數,請輸入命令: mongod /? 來獲得。
如果您也看到了這個介面,那麼表示MongoDB的服務端已成功啟動了。
順便提一下:運行mongod後,它會顯示一些有用的資訊。比如:pid (進程ID), tcp port (監聽連接埠), http 連接埠(用於查看運行狀態), 作業系統版本,資料目錄,32 or 64位版本,如果是32位版本,它還會告訴你【資料有2G的限制】,所以正式使用建議運行64位版本。
接下來,我們還要去下載MongoDB的C#驅動,它可以讓我們在C#中使用MongoDB 。: https://github.com/samus/mongodb-csharp
我下載到的壓縮包是:samus-mongodb-csharp-0.90.0.1-93-g6397a0f.zip 。這個壓縮包本身也包含了一個Sample,有興趣的可以看看它。
我們在C#訪問MongoDB所需的驅動就是項目MongoDB了。編譯這個項目就能得到了,檔案名稱:MongoDB.dll
回到頂部在C#使用MongoDB
好了,有了前面的準備工作,我們可以開始在C#中使用MongoDB了。不過,由於本樣本項目的代碼也不少,因此本文將只展示與MongoDB互動的相關代碼, 更完整的代碼請自行查閱樣本項目。
接下來,本文示範通過C#完成【客戶資料】的一些基本的資料操作,還是先來定義一個客戶資料的類型吧。
public sealed class Customer{ [MongoId] public string CustomerID { get; set; } public string CustomerName { get; set; } public string ContactName { get; set; } public string Address { get; set; } public string PostalCode { get; set; } public string Tel { get; set; }}
說明:這就是一個簡單的類,而且代碼中的[MongoId]也是可以不要的(這個後面再說)。
在操作資料庫之前,我要說明一下:MongoDB在使用前,並不要求您事先建立好相應的資料庫,設計資料表結構!
在MongoDB中,沒有【表】的概念,取而代之的是【集合】,也沒有【資料記錄】的概念,取而代之的是【文檔】, 我們可以把【文檔】理解成一個【對象】,任意的對象,甚至可以有複雜的嵌套層次。
因此,我們不用再寫代碼從【資料表欄位】到C#類的【屬性,欄位】的轉換了,現在直接就可以讀寫整個對象了。
而且MongoDB不支援Join操作,所以,如果有【關聯】操作,就需要你自己來處理。
再來定義二個變數:
private static readonly string _connectionString = "Server=127.0.0.1";private static readonly string _dbName = "MyNorthwind";
新增記錄
public void Insert(Customer customer){ customer.CustomerID = Guid.NewGuid().ToString("N"); // 首先建立一個串連 using( Mongo mongo = new Mongo(_connectionString) ) { // 開啟串連 mongo.Connect(); // 切換到指定的資料庫 var db = mongo.GetDatabase(_dbName); // 根據類型擷取相應的集合 var collection = db.GetCollection<Customer>(); // 向集合中插入對象 collection.Insert(customer); }}
上面的代碼中,每一行都有注釋,這裡就不再解釋了。
刪除記錄
public void Delete(string customerId){ using( Mongo mongo = new Mongo(_connectionString) ) { mongo.Connect(); var db = mongo.GetDatabase(_dbName); var collection = db.GetCollection<Customer>(); // 從集合中刪除指定的對象 collection.Remove(x => x.CustomerID == customerId); }}
更新記錄
public void Update(Customer customer){ using( Mongo mongo = new Mongo(_connectionString) ) { mongo.Connect(); var db = mongo.GetDatabase(_dbName); var collection = db.GetCollection<Customer>(); // 更新對象 collection.Update(customer, (x => x.CustomerID == customer.CustomerID)); }}
擷取記錄列表
public List<Customer> GetList(string searchWord, PagingInfo pagingInfo){ using( Mongo mongo = new Mongo(_connectionString) ) { mongo.Connect(); var db = mongo.GetDatabase(_dbName); var collection = db.GetCollection<Customer>(); // 先建立一個查詢 var query = from customer in collection.Linq() select customer; // 增加查詢過濾條件 if( string.IsNullOrEmpty(searchWord) == false ) query = query.Where(c => c.CustomerName.Contains(searchWord) || c.Address.Contains(searchWord)); // 先按名稱排序,再返回分頁結果. return query.OrderBy(x => x.CustomerName).GetPagingList<Customer>(pagingInfo); }}
擷取單個對象
public Customer GetById(string customerId){ using( Mongo mongo = new Mongo(_connectionString) ) { mongo.Connect(); var db = mongo.GetDatabase(_dbName); var collection = db.GetCollection<Customer>(); // 查詢單個對象 return collection.FindOne(x => x.CustomerID == customerId); }}
回到頂部重構(簡化)代碼
從上面代碼可以看出,操作MongoDB大致都是這樣一個操作過程。
// 首先建立一個串連using( Mongo mongo = new Mongo(_connectionString) ) { // 開啟串連 mongo.Connect(); // 切換到指定的資料庫 var db = mongo.GetDatabase(_dbName); // 根據類型擷取相應的集合 var collection = db.GetCollection<Customer>(); // 【訪問collection,做你想做的操作】}
針對這個問題,我提供一個封裝類來簡化MongoDB的使用。
簡化後的CRUD代碼如下:
public void Insert(Customer customer){ customer.CustomerID = Guid.NewGuid().ToString("N"); using( MyMongoDb mm = new MyMongoDb() ) { mm.GetCollection<Customer>().Insert(customer); }}public void Delete(string customerId){ using( MyMongoDb mm = new MyMongoDb() ) { mm.GetCollection<Customer>().Remove(x => x.CustomerID == customerId); }}public void Update(Customer customer){ using( MyMongoDb mm = new MyMongoDb() ) { mm.GetCollection<Customer>().Update(customer, (x => x.CustomerID == customer.CustomerID)); }}public Customer GetById(string customerId){ using( MyMongoDb mm = new MyMongoDb() ) { return mm.GetCollection<Customer>().FindOne(x => x.CustomerID == customerId); }}
看了上面這些代碼,您應該會覺得MongoDB的使用也很容易,對吧。
接下來,我來通過介面錄入一些資料,來看看我錄入的結果吧。
到這裡,你或許想知道:MongoDB有沒有一個自己的用戶端來查看資料呢?因為總不能只依賴自己寫代碼來查看資料吧?
是的,MongoDB也提供了一個用戶端來查看並維護資料庫。接下來,我們再來看看如何使用MongoDB的用戶端吧。
回到頂部使用MongoDB的用戶端查看資料
MongoDB內建一個Javascript shell,它可以從命令列與MongoDB執行個體互動。這個shell非常有用,通過它可以管理操作、檢查運行執行個體、查詢資料等操作。
讓我們再回到命令列視窗模式下吧(沒辦法,MongoDB只提供這種介面),運行mongo.exe ,如
這就是MongoDB的用戶端的命令列模式了。通常我們在操作資料庫前,要切換【當前資料】,
MongoDB也是一樣,我們可以運行如下命令來查看資料庫列表,並切換資料庫,然後再查看集合列表,請看(我運行了三條命令)
注意:MongoDB區分名字的大小寫。
在MongoDB中,查看【當前資料庫】,可以使用命令【db】,
查看集合Customer的記錄總數:【db.Customer.count();】
查看 CustomerId = 1 的記錄:【db.Customer.findOne({"_id" : "1"});】,注意:查詢條件是一個文檔,這就是MongoDB的特色。
顯示了上面三條命令的執行結果:
嗯,怎麼有亂碼?CustomerId = 1 的記錄應該是這樣的才對呀?
看到這一幕,您應該不要懷疑是MongoDB的錯了,這個錯誤是由於MongoDB的用戶端使用的編碼是UTF-8, 而Windows 命令列程式 cmd.exe 使用的gb2312(我目前使用中文語言) 造成的。
解決辦法:
1. 執行MongoDB的【exit】命令退回到Windows命令列狀態(或者重新開啟命令列視窗),
2. 運行命令:【chcp 65001】,切換到UTF-8編碼下工作。
3. 設定命令列視窗的屬性,請參考:
再運行 mongo 進入mongo命令列,切換資料庫,並執行命令:【db.Customer.findOne({"_id" : "1"});】
現在可以看到漢字能正常顯示了,但最後卻顯示"Failed to write to logfile",對於這個問題,我們如果執行命令 【db.Customer.findOne({"_id" : "91"});】(id=91的記錄就是我最後錄入的,全是a的那條,前面上有), 可以發現沒有任何異常發生,因此認為這個問題還是和cmd.exe有關的。 如果切換回 chcp 936 ,這時將看到亂碼,但沒有"Failed to write to logfile",所以我將忽略這個錯誤。
回到頂部使用MongoDB的用戶端維護資料
下面我來示範一下如何使用MongoDB的用戶端來執行一些基本的資料維護功能。還是從CRUD操作開始吧,請看,為了方便,我將在一個中執行多個命令。
注意:MongoDB的文檔使用的是一種稱為BSON格式的對象,與Javascript中的JSON類似。
在上面的樣本中,每個命令後,我加了一個紅圈。在樣本中,我先切換到 MyTest 資料庫(它並不存在,但沒關係), 然後我定義了一個文檔 item 並插入到集合 table1 中,然後又定義了一個文檔 item2,也插入到集合 table1 中。 注意:item , item2 的結構完全不同,但能放在一個集合中(不建議這樣做)。最後調用 find() 顯示集合中的所有文檔。
此時,您有沒有注意到:【每個文檔有一個名為 "_id" 的成員】,我可沒有定義啊。
其實,MongoDB會為每個文檔都建立這樣一個文檔成員,我們指定的 "key", "id" 對於MongoDB來說: 它們並不是【文檔的主鍵】,MongoDB只認 "_id",你可以指定,但如果不指定,MongoDB就自動添加。
此時,你可以看看前二張圖片,可以發現:在定義Customer類時,有一個成員CustomerID此時卻不存在! 我們可以再看一下Customer的定義:
public sealed class Customer{ [MongoId] public string CustomerID { get; set; } public string CustomerName { get; set; } public string ContactName { get; set; } public string Address { get; set; } public string PostalCode { get; set; } public string Tel { get; set; }}
此時,您應該發現CustomerID這個成員有一個[MongoId]的特性。正是由於這個特性,驅動程式將把CustomerID映射為"_id"來使用。
好了,再次回到命令列,我要示範其它的命令。請看:
為了要更新某個文檔,我們要使用findOne()方法找到要修改的文檔對象,並將它儲存一個變數t中,然後,修改它的屬性, 接著調用update()方法就可以更新文檔了,注意在調用update()時,第一個參數【修改範圍】是採用文檔的形式給出的, 第二參數才是要更新的新對象。在刪除時,刪除條件也是採用文檔的形式指定的。處處使用文檔,這就是MongoDB的特色。
前面的範例程式碼中,我使用了find()和findOne(),它們是有區別的:findOne()只返回一個文檔對象,find()返回一個集合列表, 如果不指定過濾範圍,它將返回整個集合,但在用戶端中最多隻顯示前20個文檔。
再來個複雜的查詢:搜尋日期範圍是 2011-06-25 到 2011-06-26 之間的訂單記錄。由於返回的結果太長,我的將不顯示它們。
注意:MongoDB的查詢條件中,並沒有 >, <, >= , <= 這些運算子,而是使用 "$lt", "$lte", "$gt", "$gte" 這種方式作為文檔的KEY來使用的, 因此一個簡單的 OrderDate >= "2006-06-25" and OrderDate < "2006-06-26" 要寫成如下方式:
如果遇到 or 就更麻煩了,如:CustomerId = 1 or CustomerId = 2 ,有二種寫法:
文法不難,相信能看懂JSON的人,也能看懂這二條命令。
再來個分頁的命令:
與LINQ的文法類似,好理解。
MongoDB用戶端還支援其它的文法,這裡就不一一介紹了。因為我們的目標是在C#中使用MongoDB,在MongoDB提供的C#驅動中, 我們並不需要寫那樣麻煩的查詢條件,只需要按LINQ的文法寫查詢就可以了,因此會很容易使用。 不過,有些維護性的操作,我們只能通過命令的方式去執行,比如:刪除集合,刪除資料庫。
執行【db.runCommand({"drop" : "table1"});】便可以刪除一個集合,也可以執行命令【db.table1.drop();】,二者的效果是一樣的。
再來看看如何刪除資料庫的命令:
注意:命令【db.runCommand({"dropDatabase": 1});】只能刪除【當前資料庫】,所以要先切換當前資料庫, 然後執行這個命令,執行刪除資料庫的命令後,我們再用命令【show dbs;】,探索資料庫【MyTest】已不存在,即刪除成功。 刪除資料庫還有一個方法:還記得我前面啟動mongod.exe時給它傳遞了一個參數 【-dbpath "H:\AllTempFiles\mongodb\data"】嗎? 我們現在去那個目錄看一下有什麼東西。
看了這張圖,您有沒有想過:這二個以【MyNorthwind】開頭的檔案會不會就是資料庫的檔案呢? 我現在就刪除看看,先停止mongod.exe,然後刪除檔案(由於我目前只有一個資料庫,我把目錄下的檔案全刪除了),刪除後:
現在,我再來啟動mongod.exe,然後在用戶端執行命令【show dbs;】看看:
現在可以發現我們之前的【MyNorthwind】資料庫沒有了,當然也就是刪除了。 說明一下:現在這二個資料庫,是MongoDB內建的,用於特殊用途的,我們可以不理會它們。
好了,我們還是再來看看MongoDB提供的C#驅動提供了什麼東西吧。
回到頂部MongoDB提供的C#驅動
我把MongoDB提供的C#驅動中認為比較重要的類做了個:
再來看看我前面給出一段操作MongoDB的代碼:
// 首先建立一個串連using( Mongo mongo = new Mongo(_connectionString) ) { // 開啟串連 mongo.Connect(); // 切換到指定的資料庫 var db = mongo.GetDatabase(_dbName); // 根據類型擷取相應的集合 var collection = db.GetCollection<Customer>(); // 【訪問collection,做你想做的操作】}
這段代碼大致也說明了在C#中操作MongoDB的一個過程,主要涉及中的前三個類,這三個類也是最核心的類。 這裡值得一提的是:LinqExtensions.Linq()方法可以讓我們在寫查詢時, 方便地使用LINQ的優雅文法,而不是一堆複雜的文檔條件!這也是我選擇這個驅動的原因。
還記得我前面舉過幾個在命令列中調用runCommand的樣本嗎?如果在C#中也需要執行這樣的操作,可以調用MongoDatabase.SendCommand() 方法。比如:刪除集合Category,我們可以寫成:
void DeleteCategoryCollection(){ using( MyMongoDb mm = new MyMongoDb() ) { mm.CurrentDb.SendCommand(new Document("drop", "Category")); }}
【MongoIdAttribute】:可以讓我們將一個C#類的資料成員映射到文檔的"_id"屬性。前面有樣本說明,這裡就不再多說了。
【MongoAliasAttribute】:可以讓我們將一個C#類的資料成員在映射到文檔時採用其它的屬性名稱。
比如:我希望將CustomerName成員在儲存到MongoDB時,採用CName來儲存。
[MongoAlias("CName")]public string CustomerName { get; set; }
【MongoIgnoreAttribute】:可以讓一個C#類在儲存到MongoDB時,忽略某些成員。請看下面的代碼:
public sealed class OrderItem : MyDataItem{ [MongoId] // 這個成員將映射到 "_id" public string OrderID { get; set; } [ScriptIgnore] // 在JSON序列化時,忽略這個成員 public DateTime OrderDate { get; set; } [MongoIgnore] // 在儲存到MongoDB時,忽略這個成員 public string CustomerName { get; set; } // .... 還有其它的屬性。 // 加這個屬性僅僅為了在用戶端中能更容易的顯示,要不然,用戶端處理格式轉換實在是麻煩。 // 它將不會被寫入到資料庫。 [MongoIgnore] public string OrderDateText { get { return this.OrderDate.ToString("yyyy-MM-dd HH:mm:ss"); } }}
回到頂部MongoDB不支援在查詢資料庫時使用Join操作
在MongoDB中,一個文檔就是一個完整的對象,所以擷取一個對象時,並不需要關聯式資料庫的那種JOIN文法。 在上面定義的OrderItem中,CustomerName並沒有儲存到資料庫,而是在載入時,採用了【引用】的設計方式, 根據CustomerID去訪問集合Customer來擷取對應的CustomerName ,這也算是JOIN的常見使用情境了。
樣本項目中有一個需求:根據一個日期範圍查詢訂單列表(支援分頁)。我是這樣實現這個查詢操作的。
/// <summary>/// 根據指定的查詢日期範圍及分頁參數,擷取訂單記錄列表/// </summary>/// <param name="dateRange">日期範圍</param>/// <param name="pagingInfo">分頁參數</param>/// <returns>訂單記錄列表</returns>public List<OrderItem> Search(QueryDateRange dateRange, PagingInfo pagingInfo){ dateRange.EndDate = dateRange.EndDate.AddDays(1); using( MyMongoDb mm = new MyMongoDb() ) { var collection = mm.GetCollection<OrderItem>(STR_Orders); var query = from ord in collection.Linq() where ord.OrderDate >= dateRange.StartDate && ord.OrderDate < dateRange.EndDate orderby ord.OrderDate descending select new OrderItem { OrderID = ord.OrderID, CustomerID = ord.CustomerID, OrderDate = ord.OrderDate, SumMoney = ord.SumMoney, Finished = ord.Finished }; // 擷取訂單列表,此時將返回符合分頁的結果。 List<OrderItem> list = query.GetPagingList<OrderItem>(pagingInfo); // 擷取訂單列表中所有的客戶ID string[] cids = (from ord in list where string.IsNullOrEmpty(ord.CustomerID) == false select ord.CustomerID) .Distinct() .ToArray(); // 找到所有客戶記錄 Dictionary<string, Customer> customers = (from c in mm.GetCollection<Customer>().Linq() where cids.Contains(c.CustomerID) select new Customer { CustomerID = c.CustomerID, CustomerName = c.CustomerName }) .ToDictionary(x => x.CustomerID); // 為訂單列表結果設定CustomerName foreach( OrderItem ord in list ) { Customer c = null; if( string.IsNullOrEmpty(ord.CustomerID) == false && customers.TryGetValue(ord.CustomerID, out c) ) ord.CustomerName = c.CustomerName; else ord.CustomerName = string.Empty; } return list; }}
回到頂部擷取MongoDB服務端狀態
我們再來看一下當時啟動服務端的截屏吧:
注意最後一行,它告訴我們它有一個WEB介面,連接埠是 28017 ,現在我就去看看那是個什麼樣子的。
可以看到它提供了一些服務端的狀態資訊。 我們還可以通過訪問【http://localhost:28017/_status】來獲得以JSON方式的統計資訊。
我們還可以通過運行用戶端的命令【db.runCommand({"serverStatus" : 1});】來擷取這些資訊:
好了,就說到這裡吧。接下來,您也可以寫點代碼嘗試一下,或者下載我準備的樣本項目參考一下。
點擊此處下載範例程式碼
MongoDB實戰開發