[转载]MongoDB C# 驱动教程

本教程基于C#驱动 v1.6.x 。 Api 文档见此处: http://api.mongodb.org/csharp/current/.

简介

本教程介绍由10gen支持的,用于MongoDB的C#驱动。C# 驱动由两个类库组成:BSON Library和C# Driver。 BSON Library 可以独立于 C# Driver 使用。 C# Driver 则必须需要 BSON Library。

你还可能对 C# 驱动序列化教程 感兴趣。它是一个另外的教程因为它涵盖了很多资料。

下载

C# 驱动既有源代码也有二进制文件。BSON Library 和 C# Driver 都存在同一个知识库里,而BSON Library可以独立使用。

原文件可以从 github.com 进行下载。

我们使用 msysgit 作为我们的 Windows git 客户端。可以到这里进行下载: http://msysgit.github.com/.

要复制知识库的话,从git bash shell里运行以下命令:

$ cd <parentdirectory>
$ git config --global core.autocrlf true
$ git clone git://github.com/mongodb/mongo-csharp-driver.git
$ cd mongo-csharp-driver
$ git config core.autocrlf true

复制知识库之前,必须将core.autocrlf的全局设置设为true。当复制完毕后,我们建议你将core.autocrlf的本地设置设为true(如上所示),这样将来core.autocrlf的全局设置更改了也不会影响到这个知识库。如果你到时候想把全局设置的core.autocrlf改为false,则运行:

$ git config --global core.autocrlf false

core.autocrlf设置的典型问题是git 报告整个文件都被修改了(由于行结尾的差异)。在知识库创建后更改core.autocrlf的设置是相当没劲的,所以在开始时就设好它是很重要的。

你可以通过点击以下链接的Downloads按钮来下载源文件的 zip 文件 (不用复制知识库):

http://github.com/mongodb/mongo-csharp-driver

你可以在以下链接下载二进制文件(.msi 和 .zip 两种格式) :

http://github.com/mongodb/mongo-csharp-driver/downloads

生成

目前我们使用 Visual Studio 2010 来生成C# 驱动。解决方案的名称是 CSharpDriver-2010.sln.

依赖项

单元测试依赖 NUnit 2.5.9,它已包含在知识库的依赖项文件夹中。你可以不用安装NUnit就生成C#驱动,不过要运行单元测试则必须安装NUnit(除非你用别的测试运行器)

运行单元测试

有三个工程包含单元测试:

1. BsonUnitTests
2. DriverUnitTests
3. DriverUnitTestsVB

BsonUnitTests 不连接 MongoDB 服务端。DriverUnitTests 和 DriverUnitTestsVB 连接一个运行在localhost上默认端口的MongoDB实例。

运行单元测试的一个简单方法是将其中一个单元测试工程设为启动项目并遵照以下说明配置工程(用 BsonUnitTests 做例子):

  • 在”调试“页签里:
    1. 将”启动操作“设为”启动外部程序“
    2. 将外部程序设为: C:\Program Files (x86)\NUnit 2.5.9\bin\net-2.0\nunit.exe
    3. 将“命令行参数”设为: BsonUnitTests.csproj /config:Debug /run
    4. 将“工作目录”设为:  BsonUnitTest.csproj 所在的目录

如果还想为单元测试运行在Release模式的话,为Release配置重复以上步骤 (使用 /config:Release 代替) 。

nunit.exe的实际路径可能根据你的机器有轻微不同。

要运行 DriverUnitTests 和 DriverUnitTestsVB 执行相同的步骤 (如有必要适当修改).

安装

如果你想安装C#驱动到你的机器上,你可以用安装程序 (见上面的下载说明)。安装程序很简单,只需把DLL复制到指定安装目录即可。

如果你下载了二进制zip文件,只需简单地解压文件并把它们放到任意地方。

注意:如果你下载的是.zip 文件,Windows 可能要你 "解除锁定" 帮助文件。当你双击CSharpDriverDocs.chm文件时,如果 Windows 问你 "是否要打开此文件?" ,将“每次打开此文件时都询问”旁的复选框勾掉,然后再点击“打开”按钮。或者还可以在 CSharpDriverDocs.chm 文件上右键,选择“属性”,然后在“常规”页签的顶部点击“解除锁定”按钮。如果“解除锁定”按钮没显示的话,就没必要解除锁定了。

引用和命名空间

要使用 C# 驱动,需要添加以下DLL引用:

  1. MongoDB.Bson.dll
  2. MongoDB.Driver.dll

至少要在你的源文件里加上以下using语句:

using MongoDB.Bson;
using MongoDB.Driver;

另外还可能经常用到以下using语句:

using MongoDB.Driver.Builders;
using MongoDB.Driver.GridFS;
using MongoDB.Driver.Linq;

在某些情况下如果你要使用C#驱动的某些可选部分的话,还可能用上以下某些using语句:

using MongoDB.Bson.IO;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Bson.Serialization.Conventions;
using MongoDB.Bson.Serialization.IdGenerators;
using MongoDB.Bson.Serialization.Options;
using MongoDB.Bson.Serialization.Serializers;
using MongoDB.Driver.Wrappers;

BSON Library

C# 驱动在 BSON Library之上创建的。BSON Library处理BSON格式的所有细节,包括:I/O,序列化以及BSON文档的内存中对象模型。

BSON对象模型的重要类有: BsonType, BsonValue, BsonElement, BsonDocument 和 BsonArray.

BsonType

这个枚举用来指定BSON值得类型,定义为:

public enum BsonType {
    Double = 0x01,
    String = 0x02,
    Document = 0x03,
    Array = 0x04,
    Binary = 0x05,
    Undefined = 0x06,
    ObjectId = 0x07,
    Boolean = 0x08,
    DateTime = 0x09,
    Null = 0x0a,
    RegularExpression = 0x0b,
    JavaScript = 0x0d,
    Symbol = 0x0e,
    JavaScriptWithScope = 0x0f,
    Int32 = 0x10,
    Timestamp = 0x11,
    Int64 = 0x12,
    MinKey = 0xff,
    MaxKey = 0x7f
}

BsonValue 及其子类

BsonValue 是一个抽象类,表示一个BSON类型的值。BsonType枚举中定义的每一个值都对应了一个具体的BsonValue子类。要获取一个BsonValue实例有几种方法:

  • 使用一个BsonValue的子类的公共构造函数 (如果有的话) 
  • 使用BsonValue的静态 Create 方法
  • 使用BsonValue的子类的静态 Create 方法
  • 使用BsonValue的子类的静态属性
  • 使用隐式转换到 BsonValue

使用静态 Create 方法的好处是它们可以为经常使用的值返回预创建实例。它们还可以返回null(构造函数却不能),当使用函数式构造创建BsonDocument处理可选元素时将很有用。静态属性引用经常使用的值的预创建实例。隐式转换允许你使用原生的.NET值,不管BsonValue是否需要,而且.NET值会自动转换为BsonValue。

BsonType 属性

BsonValue 有一个名为 BsonType 的属性,可以用来查询BsonValue的实际类型。以下例子演示判断BsonValue类型的几种方法:

BsonValue value;
if (value.BsonType == BsonType.Int32) {
    // 知道值是BsonInt32的实例
}
if (value is BsonInt32) {
    // 另一种知道值是BsonInt32的方法
}
if (value.IsInt32) {
    // 最简单的知道值是BsonInt32的方法
}

As[Type] 属性

BsonValue 有大量将BsonValue投射(cast)成它的子类或原生.NET类型的属性。注意很重要的一点是,这些属性都只是投射(cast),而不是转换(conversion)。如果BsonValue不是对应的类型的话,将抛出一个 InvalidCastException 异常。 参见 To[Type] 方法,它进行的就是转换操作,以及 Is[Type] 属性,你可以在尝试使用其中一个As[Type]属性前用它查询BsonValue的类型。

BsonDocument document;
string name = document["name"].AsString;
int age = document["age"].AsInt32;
BsonDocument address = document["address"].AsBsonDocument;
string zip = address["zip"].AsString;

Is[Type] 属性

BsonValue 有以下布尔属性,用来测试它是什么类型的BsonValue。这些可以如下使用:

BsonDocument document;
int age = -1;
if (document.Contains["age"] && document["age"].IsInt32) {
    age = document["age"].AsInt32;
}

To[Type] 转换方法

不像 As[Type] 方法,To[Type] 方法在可转换类型间,如int和double,执行同样的有限转换。

ToBoolean 方法从不失败。它使用 JavaScript 的似真非真定义: false, 0, 0.0, NaN, BsonNull, BsonUndefined 和 "" 都是false, 而其余的都是 true (包括字符串 "false").

ToBoolean 方法当处理的文档可能对记录true/false值时有不确定方法时就显得特别有用:

if (employee["ismanager"].ToBoolean()) {
    // we know the employee is a manager
    // works with many ways of recording boolean values
}

ToDoubleToInt32, 和 ToInt64 方法当在数字类型间转换时从不失败,但是值如果不适合目标类型的话可能会被截断。字符串可以转换为数字类型,但如果字符串不能解析为目标类型的值的话将抛出异常。

静态Create方法

由于 BsonValue 是一个抽象类,你不能创建BsonValue的实例(只能创建具体子类的实例)。 BsonValue 有一个静态 Create 方法,参数只有一个,是object类型的,并在运行时确定要创建的BsonValue的实际类型。BsonValue的子类也有静态的 Create 方法,由它们的需求而定制。

隐式转换

隐式转换是从以下.NET类型到BsonValue定义的:

  • bool
  • byte[]
  • DateTime
  • double
  • Enum
  • Guid
  • int
  • long
  • ObjectId
  • Regex
  • string

这就不用再调用BsonValue构造函数或者Create方法了。例如:

BsonValue b = true; // b是一个 BsonBoolean 的实例
BsonValue d = 3.14159; // d 是一个 BsonDouble 的实例
BsonValue i = 1; // i 是一个 BsonInt32 的实例
BsonValue s = "Hello"; // s 是一个 BsonString 的实例

BsonMaxKey, BsonMinKey, BsonNull 和 BsonUndefined

这几个类都是单例的,所以每个类只有一个实例存在。要引用这些实例,使用每个类的静态 Value 属性:

document["status"] = BsonNull.Value;
document["priority"] = BsonMaxKey.Value;

注意 C# 的 null 和 BsonNull.Value 是两个不同的东西。后者其实是一个 C# 对象,表示一个 BSON null 值 (这是很细微的区别,但在功能结构里却扮演很重要的角色).

ObjectId 和 BsonObjectId

ObjectId 是一个结构体,维持一个BSON ObjectId的原始值。 BsonObjectId 是 BsonValue 的一个子类,其 Value 属性是ObjectId类型。

这里是一些创建ObjectId值的常用方法:

var id1 = new ObjectId(); // 和 ObjectId.Empty 一样
var id2 = ObjectId.Empty; // 全是0
var id3 = ObjectId.GenerateNewId(); // 生成新的唯一Id
var id4 = ObjectId.Parse("4dad901291c2949e7a5b6aa8"); // 解析24位十六进制数字串

注意第一个例子C#与JavaScript有不同的表现。在C#里它创建了一个全是0的 ObjectId ,不过在 JavaScript 里它生成一个新的唯一Id。这个差异无法避免,因为在C#里值类型的构造函数总是把值初始化为全0。

BsonElement

BsonElement 是一个键值对,值是一个 BsonValue。它好比BsonDocument的积木,由0或者更多的元素组成。一般很少直接创建 BsonElements ,因为它们通常是不需要直接创建的。例如:

document.Add(new BsonElement("age", 21)); // 没问题,但下一行更简短
document.Add("age", 21); // 自动创建BsonElement

BsonDocument

BsonDocument 是键值对(BsonElement)的集合 。 它是BSON文档的内存中对象模型。有三种方法创建并填充 BsonDocument:

  1. 创建一个新的文档并调用Add和Set方法
  2. 创建一个新的文档并连续调用Add和Set方法
  3. 创建一个新的文档并使用C#的集合初始化语法(推荐)

BsonDocument 构造函数

BsonDocument 有以下构造函数:

  • BsonDocument()
  • BsonDocument(string name, BsonValue value)
  • BsonDocument(BsonElement element)
  • BsonDocument(Dictionary<string, object> dictionary)
  • BsonDocument(Dictionary<string, object> dictionary, IEnumerable<string> keys)
  • BsonDocument(IDictionary dictionary)
  • BsonDocument(IDictionary dictionary, IEnumerable<string> keys)
  • BsonDocument(IDictionary<string, object> dictionary)
  • BsonDocument(IDictionary<string, object> dictionary, IEnumerable<string> keys)
  • BsonDocument(IEnumerabe<BsonElement> elements)
  • BsonDocument(params BsonElement[] elements)
  • BsonDocument(bool allowDuplicateNames)

前两个是最可能用到的。第一个创建一个空的文档,第二个以一个元素创建文档(这两种情况当然都可以添加更多元素)。

所有构造函数(除了那个有allowDuplicateNames参数的之外)都简单地调用了同参数的Add方法,所以参考相应的Add方法以了解新文档是如何初始化填充的。

BsonDocument 一般不允许有重复的名称,但如果你想有重复的名称的话,就调用带allowDuplicateNames参数的构造函数并传入true。不建议你使用重复的名称,此选项只是为了处理已有的可能存在重复名称的BSON文档。 MongoDB 不保证是否支持带重复名称的文档,所以发送此类文档到服务器是务必小心。

创建新文档并调用Add和Set方法

这是传统的用多行C#语句一步步创建并填充文档的方法。例如:

BsonDocument book = new BsonDocument();
book.Add("author", "Ernest Hemingway");
book.Add("title", "For Whom the Bell Tolls");

创建新文档并连续使用Add和Set方法

这个和前一个方法很类似,不过连续调用Add方法只需一行C#语句即可。例如:

BsonDocument book = new BsonDocument()
    .Add("author", "Ernest Hemingway")
    .Add("title", "For Whom the Bell Tolls");

创建新文档并使用C#集合初始化语法(推荐使用)

这是在一行语句中创建和初始化BsonDocument的推荐方法。它使用C#集合初始化语法:

BsonDocument book = new BsonDocument {
    { "author", "Ernest Hemingway" },
    { "title", "For Whom the Bell Tolls" }
};

编译器将这行代码翻译为与之匹配的Add方法:

    BsonDocument book = new BsonDocument();
    book.Add("author", "Ernest Hemingway");
    book.Add("title", "For Whom the Bell Tolls");

常见的错误时忘了写里面的花括弧。这会导致一个编译错误。例如:

BsonDocument bad = new BsonDocument {
    "author", "Ernest Hemingway"
};

会被编译器翻译成:

BsonDocument bad = new BsonDocument();
bad.Add("author");
bad.Add("Ernest Hemingway");

会导致编译错误因为没有Add方法是只接受一个string参数的。

创建嵌套的 BSON 文档

嵌套 BSON 文档通过设置元素的值为BSON文档来进行创建。例如:

BsonDocument nested = new BsonDocument {
    { "name", "John Doe" },
    { "address", new BsonDocument {
        { "street", "123 Main St." },
        { "city", "Centerville" },
        { "state", "PA" },
        { "zip", 12345}
    }}
};

这个创建了一个顶级文档,有两个元素 ("name" 和 "address")。  "address" 的值是一个嵌套的 BSON 文档。

Add 方法

BsonDocument 有如下重载的Add方法

  • Add(BsonElement element)
  • Add(Dictionary<string, object> dictionary)
  • Add(Dictionary<string, object> dictionary, IEnumerable<string> keys)
  • Add(IDictionary dictionary)
  • Add(IDictionary dictionary, IEnumerable<string> keys)
  • Add(IDictionary<string, object> dictionary)
  • Add(IDictionary<string, object> dictionary, IEnumerable<string> keys)
  • Add(IEnumerable<BsonElement> elements)
  • Add(string name, BsonValue value)
  • Add(string name, BsonValue value, bool condition)

要注意的很重要的一点是有时候Add方法不会添加新元素。如果提供的值是null(或者最后一个重载中提供的condition是false)的话那元素就不会被添加。这在处理可选元素时就不用写任何if语句或者条件表达式了。

例如:

BsonDocument document = new BsonDocument {
    { "name", name },
    { "city", city }, // 如果city是null就不添加
    { "dob", dob, dobAvailable } // 如果 dobAvailable是false则不添加
};

就比下面更简洁和可读性强:

BsonDocument document = new BsonDocument();
document.Add("name", name);
if (city != null) {
    document.Add("city", city);
}
if (dobAvailable) {
    document.Add("dob", dob);
}

如果你想在值缺失的情况下添加一个BsonNull,你可能会这么做。但更简单的方法是使用C#的??运算符:

BsonDocument = new BsonDocument {
    { "city", city ?? BsonConstants.Null }
};

IDictionary 重载从一个字典初始化一个 BsonDocument 。字典里的每一个键都变成元素的name,每一个值都映射为匹配的 BsonValue 并变成新元素的value。带keys参数的重载让你选择加载哪个字典入口(还可能用keys参数来控制从字典里加载元素的顺序)。

访问 BsonDocument 元素

访问 BsonDocument 元素的推荐做法是使用下面的索引:

  • BsonValue this[int index]
  • BsonValue this[string name]
  • BsonValue this[string name, BsonValue defaultValue]

注意索引的返回值是 BsonValue,而不是 BsonElement。这其实让 BsonDocuments 更容易使了 (如果你需要获取实际的 BsonElements ,那就用 GetElement方法).

我们已经看过访问 BsonDocument 元素的例子了。这里再来几个:

BsonDocument book;
string author = book["author"].AsString;
DateTime publicationDate = book["publicationDate"].AsDateTime;
int pages = book["pages", -1].AsInt32; // 默认值是 -1

BsonArray

这个类用来表示 BSON 数组。由于BSON文档(元素用特殊命名惯例)恰巧对外表示为数组, BsonArray 类跟 BsonDocument 类是无关的,因为它俩用起来很不一样。

构造函数

BsonArray 有以下构造函数:

  • BsonArray()
  • BsonArray(IEnumerable<bool> values)
  • BsonArray(IEnumerable<BsonValue> values)
  • BsonArray(IEnumerable<DateTime> values)
  • BsonArray(IEnumerable<double> values)
  • BsonArray(IEnumerable<int> values)
  • BsonArray(IEnumerable<long> values)
  • BsonArray(IEnumerable<ObjectId> values)
  • BsonArray(IEnumerable<string> values)
  • BsonArray(IEnumerable values)

所有带参数的构造函数都调用匹配的Add方法。由于C#不提供自动从IEnumerable<T> 到 IEnumerable<object>的转换,所以多重载是有必要的。

Add 和 AddRange 方法

BsonArray 具有以下的 Add 方法:

  • BsonArray Add(BsonValue value)
  • BsonArray AddRange(IEnumerable<bool> values)
  • BsonArray AddRange(IEnumerable<BsonValue> values)
  • BsonArray AddRange(IEnumerable<DateTime> values)
  • BsonArray AddRange(IEnumerable<double> values)
  • BsonArray AddRange(IEnumerable<int> values)
  • BsonArray AddRange(IEnumerable<long> values)
  • BsonArray AddRange(IEnumerable<ObjectId> values)
  • BsonArray AddRange(IEnumerable<string> values)
  • BsonArray AddRange(IEnumerable values)

注意Add方法只有一个参数。要创建并以多个值初始化一个 BsonArray ,请使用以下方法之一:

// 传统方法
BsonArray a1 = new BsonArray();
a1.Add(1);
a2.Add(2);

// 连续调用
BsonArray a2 = new BsonArray().Add(1).Add(2);

// values参数
int[] values = new int[] { 1, 2 };
BsonArray a3 = new BsonArray(values);

// 集合初始化语法
BsonArray a4 = new BsonArray { 1, 2 };

索引

数组元素用整型索引进行访问。比如 BsonDocument,元素的类型是 BsonValue。比如:

BsonArray array = new BsonArray { "Tom", 39 };
string name = array[0].AsString;
int age = array[1].AsInt32;

C# 驱动

知道现在我们讨论的都是 BSON 类库。剩下的我们来说一下 C# 驱动。

线程安全

只有一小部分C#驱动是线程安全的。它们是: MongoClient, MongoServer, MongoDatabase, MongoCollection 和 MongoGridFS。通常用的比较多的类都不是线程安全的,包括 MongoCursor 和所有BSON类库里的类 (除了 BsonSymbolTable 是线程安全的). 不特别标记为线程安全的都是线程非安全的。

所有类的所有静态属性和方法都是线程安全的。

MongoClient 类

这个类是操控MongoDB服务器的根对象。与服务器的连接是自动在后台处理的 (用了一个连接池来提升效率).

当连接到一个副本是,用的仍然只有一个MongoClient的实例,它代表一个完整的副本。驱动会自动找出所有副本里的成员并识别出当前的主服务器。

这个类的实例是线程安全的。

操作默认情况下,除非设置了,否则,所有的操作需要WriteConcern使用W = 1。换句话说,默认情况下,所有的写操作都会阻塞,直到服务器已经确认。

连接字符串

连接MongoDB服务器的最简单方法就是使用连接字符串。标准的连接字符串格式为:

mongodb://[username:password@]hostname[:port][/[database][?options]]

username 和 password 只有在MongoDB服务器使用了身份验证时才出现。这些凭证信息将是所有数据库的默认凭证。要验证admin数据库,在username里加上 "(admin)" 。如果要根据不同数据库使用不同的凭证,在GetDatabase方法里传入正确的凭证即可。

端口号是可选的,默认为 27017.

要连接多个服务器的话,用逗号分隔多个主机名(和端口号,如果有的话)。例如:

mongodb://server1,server2:27017,server2:27018

这个连接字段串指定了三个服务器 (其中两个在同一台机器上,但端口号不一样)。由于指定多个服务器会引起歧义,不知道到底是副本还是多个mongo(分片安装中),服务器会进入一个连接的发现阶段来确定它们的类型。这对于连接时间而言有点过头了,不过可以通过在连接字符串里指定连接模式来避免:

mongodb://server1,server2:27017,server2:27018/?connect=replicaset

可选的模式有:自动 automatic (默认), 直接 direct, 副本replica set, 和 分片路由 shardrouter。连接模式的规则如下:

1. 如果连接模式指定为自动以外的,则使用之。
2. 如果在连接字符串里指定了副本名称 (replicaset), 那么将使用副本模式。
3. 如果连接字符串里只列出了一个服务器,那么将使用直接模式。
4. 否则,将查找第一个响应的服务器,确定连接模式。

如果列出了多个服务器,而且其中一个是副本,别的不是,那么连接模式就不可确定了。确保别在连接字符串里混用服务器类型。

如果连接模式设为副本模式,驱动会去找主服务器,即使它不在字符串里列出,只要字符串里至少有一个服务器响应了(响应里将包含完整的副本和当前的主服务器名称)。另外,其它服务器也会被找到并自动添加(或移除),甚至在初始化连接之后。这样你就可以从副本里添加和移除服务器,驱动会自动处理这些变更。

正像上面所说的,连接字符串的可选部分是用来设置各种连接选项的。假设你想要直接连接到副本的一个成员不管它是不是当前主服务器(可能想监控它的状态或者仅仅读查询)。你可以这么写:

mongodb://server2/?connect=direct;readpreference=nearest

连接字符串的完整文档可以看下面的连接:

http://www.mongodb.org/display/DOCS/Connections

和:

http://docs.mongodb.org/manual/applications/replication/#replica-set-read-preference

对SSL 的支持

驱动里已经支持了SSL。可以通过在连接字符串里加上 "ssl=true" 选项来进行配置。

mongodb://server2/?ssl=true

默认情况下,服务器证书会对本地受信任证书存储进行验证。这经常会在测试服务器没有签名证书的测试环境里引起一些问题。要缓和这个问题,可以添加另一个连接字符串选项 "sslverifycertificate=false" 来忽略任何证书错误。

身份认证

MongoDB 支持简单直接的身份认证机制。你可以在 security and authentication docs page 了解更多。

C# 驱动有多种方法支持身份验证。上面提到的连接字符串,可以指定默认凭证信息。通常在没有提供其它凭证的时候都会用默认凭证。

有两种方法来提供凭证。第一种,可以在运行时通过特定方法提供。这些凭证就会被用来执行想要的功能。第二种,也是更健壮的方法,是把凭证存储在 MongoCredentialsStore 里。存在里面的 MongoCredentials 由数据库作为键值,所以如果不同的数据库需要不同的用户,那么凭证存储先去找第一个,如果没找着,就退而求其次,看连接字符串里有没有提供默认凭证,有则用之。

下面的例子使用了凭证存储来定义"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);

GetServer 方法

要从 MongoClient 的实例取得 MongoServer 的实例可以使用 GetServer 方法。

MongoServer 类

MongoServer 类是用来对驱动进行更多的控制。包含了获取数据库和通过简单的socket发布一系列操作的高级方法,为的是保证一致性。

GetDatabase 方法

从 MongoServer 实例取得 MongoDatabase 实例(见下一节) 可以使用以下的 GetDatabase 方法或索引之一:

  • MongoDatabase GetDatabase(MongoDatabaseSettings settings)
  • MongoDatabase GetDatabase(string databaseName)
  • MongoDatabase GetDatabase(string databaseName, MongoCredentials credentials)
  • MongoDatabase GetDatabase(string databaseName, MongoCredentials credentials, WriteConcern writeConcern)
  • MongoDatabase GetDatabase(string databaseName, WriteConcern writeConcern)

样例代码:

MongoClient client = new MongoClient(); // 连接到 localhost
MongoServer server = client.GetServer();
MongoDatabase test = server.GetDatabase("test");
MongoCredentials credentials = new MongoCredentials("username", "password");
MongoDatabase salaries = server.GetDatabase("salaries", credentials);

大多数的数据设置都是从服务器对象继承来的, GetDatabase 提供的重载可以对经常使用的设置进行覆盖。要覆盖其它设置,调用 CreateDatabaseSettings 并在调用 GetDatabase 之前更改任何你想要的设置,像这样:

var databaseSettings = server.CreateDatabaseSettings("test");
databaseSettings.SlaveOk = true;
var database = server.GetDatabase(databaseSettings);

GetDatabase 维系了一个 MongoDatabase 之前返回过的 实例表,因此如果以同样的参数再次调用 GetDatabase 会再次得到相同的实例。

RequestStart/RequestDone 方法

有时候为了保证结果正确,需要在同一个连接里执行一系列操作。这比较少见,而且大多数时候没有必要去调用 RequestStart/RequestDone。有必要这么做的一个例子是在w=0的WriteConcern的快速会话中调用了一系列的Insert,然后紧接着马上查询出这些数据来(在w=0的WriteConcern下,服务器里的写操作会排队,而且可能不会马上对其它连接可见)。使用 RequestStart 可以在同一个连接里在写的时候强制查询,因此查询直到服务器捕获了写操作之后才会执行。

通过使用RequestStart 和 RequestDone,线程可以从连接池里暂时保留一个连接,例如:

using(server.RequestStart(database)) {
// 在同一个连接里需要执行一系列操作
}

database 参数只是简单地说明你要在这个请求期间要用哪些数据库。这使服务器能够对已经身份验证通过的数据库拿来就用 (如果没用身份验证那这个优化就没关系了)。在这个请求期间你可以任意地使用其它数据库了。

RequestStart (为这个线程)增加了一个计数,在完成后再减掉。保留的连接实际不是返回到连接池里,直到计数再次变为0。这说明嵌套调用 RequestStart 是没有问题的。

RequestStart 返回了一个 IDisposable。如果是在using块里用 RequestStart ,为了释放连接最好尽快调用 RequestDone 。

其它的属性和方法

参考其它的属性和方法,请参阅api文档。

MongoDatabase 类

这个类表示 MongoDB 服务器的数据库。通常每个数据库只有一个实例,除非你是用不同的设置来访问同一个数据库,这样就是每个设置都有一个实例。

这个类的实例是线程安全的。

GetCollection 方法

此方法返回一个表示数据库里集合的对象。当请求一个集合对象时,要同时制定集合的默认文档类型。例如:

MongoDatabase hr = server.GetDatabase("hr");
MongoCollection<Employee> employees =
    hr.GetCollection<Employee>("employees");

集合并不限于只有一种文档。默认的文档类型在处理那种文档时能更方便一点,但在需要时你完全可以指定另一种文档。

大多数的集合设置是从数据库继承的,GetCollection 提供的重载可以对常用的设置进行覆盖。要覆盖其它的设置,调用 CreateCollectionSettings 并在调用 GetCollection 之前更改任何你想要的设置,像这样:

var collectionSettings = database.CreateCollectionSettings<TDocument>("test");
collectionSettings.SlaveOk = true;
var collection = database.GetCollection(collectionSettings);

GetCollection 维系了之前返回过的一个实例表,因此如果以同样的参数再次调用 GetCollection 会得到同一个实例。

其它属性和方法

参考其它的属性和方法,请参阅api文档。

MongoCollection<TDefaultDocument> 类

此类表示 MongoDB 数据库里的集合。 <TDefaultDocument> 泛型参数指定了此集合默认文档的类型。

此类的实例是线程安全的。

Insert<TDocument> 方法

要在集合里插入一个文档,创建一个表示该文档的对象并调用 Insert。对象可以是BsonDocument 的实例或者是可以成功序列化为BSON文档的任何类的实例。例如:

MongoCollection<BsonDocument> books =
    database.GetCollection<BsonDocument>("books");
BsonDocument book = new BsonDocument {
    { "author", "Ernest Hemingway" },
    { "title", "For Whom the Bell Tolls" }
};
books.Insert(book);

如果有一个名为 Book 的类,代码如下:

MongoCollection<Book> books = database.GetCollection<Book>("books");
Book book = new Book {
    Author = "Ernest Hemingway",
    Title = "For Whom the Bell Tolls"
};
books.Insert(book);

InsertBatch 方法

使用InserBatch方法可以一次性插入多个文档,例如:

MongoCollection<BsonDocument> books;
BsonDocument[] batch = {
    new BsonDocument {
        { "author", "Kurt Vonnegut" },
        { "title", "Cat's Cradle" }
    },
    new BsonDocument {
        { "author", "Kurt Vonnegut" },
        { "title", "Slaughterhouse-Five" }
    }
};
books.InsertBatch(batch);

插入多个文档时,使用 InsertBatch 比 Insert 效率更高。

FindOne 和 FindOneAs 方法

要从集合里获取文档,使用Find方法之一。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 参数是 IMongoQuery 类型的。 IMongoQuery 接口标记了类可以用来进行查询。构建查询的最常用方法是要么使用Query建造类,要么自己创建一个QueryDocument (QueryDocument 是BsonDocument 的子类,同时实现了 IMongoQuery 因此可以用作查询对象)。同时,通过使用 QueryWrapper 类,查询可以是任何能序列化为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
}

还有另一种查询的方法是使用匿名类,不过这样我们得把匿名对象进行封装:

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的组合。如果文档的 Id 有值,那么就假定这是一个已经存在的文档,Save就会在文档上调用Update(设置Upsert标记以防止它实际上是个新文档)。否则就假定这是一个新文档,Save会在首先将新生成的唯一值设到Id上,然后调用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);
}

调用Save方法的时候,TDocument 类必须要有Id。如果没有的话可以调用Insert来插入文档。

Update 方法

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 是从集合里汇总数据的一种方法。集合里的每一个文档(或者某些子集,如果可选查询提供了的话)都被传到map函数,该函数调用emit来产生中间值。然后中间值被传到reduce函数进行汇总。

下面的例子摘选自Kristina Chodorow 和 Michael Dirolf写的《MongoDB权威指南》第87页。它计算了集合里的每一个键值被找到了多少次。

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());
}

其它属性和方法

参考其它的属性和方法,请参阅api文档。

MongoCursor<TDocument> 类

Find 方法(以及它的各个变种) 不是马上返回查询的实际结果。而是返回一个能获取到查询结果的可遍历的游标。查询实际上并不是传到服务器,直到尝试获取第一个结果(技术上而言,就是在由GetEnumerator返回的枚举器第一次调用MoveNext时)。这说明了我们可以在获取到结果之前以各种有趣的方式来控制查询的结果。

MongoCursor 的实例不是线程安全的,至少在它们冻结(见下面)前是不安全的。一旦它们冻结了,它们就是线程安全的了,因为它们是只读的(尤其是,GetEnumerator是线程安全的,所以同一个游标可以被多个线程使用)。

遍历游标

要使用查询结果最方便的方法就是用C#的foreach语句。例如:

var query = Query.EQ("author", "Ernest Hemingway");
var cursor = books.Find(query);
foreach (var book in cursor) {
    // do something with book
}

还可以用LINQ为IEnumerable<T>定义的扩展方法来遍历游标:

var query = Query.EQ("author", "Ernest Hemingway");
var cursor = books.Find(query);
var firstBook = cursor.FirstOrDefault();
var lastBook = cursor.LastOrDefault();
在上边的例子,查询实际上传到服务器两次(分别是 FirstOrDefault 和 LastOrDefault 调用的时候).

很重要的一点是游标将其引用的资源都释放干净了。要保证这一点的关键是确保调用了枚举器的Dispose方法。foreach语句和LINQ扩展方法都保证了Dispose会被调用。除非你手动遍历右边,那就得自己负责调用 Dispose。

遍历游标前修改它

游标有几个属性可以在遍历控制返回结果前进行修改。有两种修改游标的方法:

  1. 直接修改属性
  2. 使用平滑接口来设置属性

例如,如果想要取第101到110个结果,可以这样写:

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
}

平滑接口在只设置少部分值时用着很爽。当设置比较多时可能用属性方式更好一点。

一旦开始遍历游标,它就变成“冻结”,你就不能再更改任何属性了。所以要在遍历前就设置好所有的属性。

游标的可修改属性

以下是游标的可修改属性:

  • BatchSize (SetBatchSize)
  • Fields (SetFields)
  • Flags (SetFlags)
  • Limit (SetLimit)
  • Options (SetOption and SetOptions)
  • SerializationOptions (SetSerializationOptions)
  • Skip (SetSkip)
  • SlaveOk (SetSlaveOk)

括号里的方法名是对应的平滑接口方法。

平滑接口还支持额外的不常使用的选项,这些选项没有对应的属性:

  • SetHint
  • SetMax
  • SetMaxScan
  • SetMin
  • SetShowDiskLoc
  • SetSnapshot
  • SetSortOrder

其它方法

MongoCursor 有一些方法用于某些特殊操作目的:

  • Clone
  • Count
  • Explain
  • Size

WriteConcern 类

WriteConcern 有好几级,这个类就是用来表示这些级次的。 WriteConcern 只是应用在那些没有返回值的操作 (所以它不应用在查询和命令中)。它应用于这几个 MongoCollection 方法: Insert, Remove, Save 和 Update.

WriteConcern 的要领是在 Insert, Remove, Save 或者 Update之后,紧接着调用GetLastError命令将消息发送到服务器,这样驱动就可以操作成功了。另外,当使用副本时,有可能确认信息被复制到最少量的辅服务器上去。

posted on 2014-02-04 12:33  iackjee  阅读(653)  评论(0编辑  收藏  举报

导航