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});】来获取这些信息:

 

好了,就说到这里吧。接下来,您也能够写点代码尝试一下,或者下载我准备的示例项目参考一下。

点击此处下载示例代码

相关文章
相关标签/搜索