MongoDB實戰開發

【目標】:本文將以實戰的形式,向您展現如何用C#訪問MongoDB,完成常見的數據庫操做任務, 同時,也將介紹MongoDB的客戶端(命令行工做模式)以及一些基礎的命令。git

【說明】:MongoDB是什麼?有什麼用?若是不清楚這些問題的,請本身google一下吧。github

【適合對象】:徹底沒有接觸MongoDB或對MongoDB有一點了解的C#開發人員。所以本文是一篇入門級的文章。mongodb

【示例項目】:本文的完整示例是一個簡單的【客戶,商品,訂單】業務場景, 預覽界面效果請點擊此處(但並不徹底相同),也包含下載示例項目的源碼。shell

讓咱們開始MongoDB的實戰入門吧。數據庫

回到頂部

下載MongoDB,並啓動它

您能夠在這個地址下載到MongoDB: http://www.mongodb.org/downloads, 本文將以【mongodb-win32-i386-1.8.2-rc2】來演示MongoDB的使用。tcp

下載好了嗎?咱們繼續吧。請解壓縮您剛纔下載的MongoDB的zip壓縮包,進入解包的bin目錄,會發現有一堆exe文件。 如今,請打開命令行窗口並切換到剛纔的bin目錄,而後輸入如下命令:ui

這裏,我運行了程序mongod.exe,並告訴它數據文件的保存目錄(這個目錄要事先建立好),至於mongod的更多命令行參數,請輸入命令: mongod /? 來得到。
若是您也看到了這個界面,那麼表示MongoDB的服務端已成功啓動了。this

順便提一下:運行mongod後,它會顯示一些有用的信息。好比:pid (進程ID), tcp port (監聽端口), http 端口(用於查看運行狀態), 操做系統版本,數據目錄,32 or 64位版本,若是是32位版本,它還會告訴你【數據有2G的限制】,因此正式使用建議運行64位版本。google

接下來,咱們還要去下載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});】來獲取這些信息:

 

好了,就說到這裏吧。接下來,您也能夠寫點代碼嘗試一下,或者下載我準備的示例項目參考一下。

點擊此處下載示例代碼

相關文章
相關標籤/搜索