MongoDB C# 驱动

http://www.mongodb.org/display/DOCS/CSharp+Driver+Tutorial#CSharpDriverTutorial-TheC%23Driver

读书笔记

C# Driver

之前看了Bson类库,现在学习C# Driver

Thread safety(多线程问题)

只有少部分的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 strings

最简单的数据库连接是使用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

SSL Support

这些不感兴趣,大概是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获取数据库以及进行一系列的数据库操作,并且保持数据库的一致性。

GetDatabase method

通过这个方法访问数据库

例子代码:

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文档

 

MongoCursor<TDocument>类

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 class

WriteConcern 有很多等级,这个类就是用来描述这个等级的。WriteConcern 应用只用来操作那些没有返回的操作。比如Insert 、Save、Update和Remove。

这个主要就是当Insert、Save、Update和Remove指令发送给服务器之后,会有一个GetLassError命令来确认这些操作是否成功执行了。

posted on 2012-12-29 17:34  烟波钓徒  阅读(2289)  评论(3编辑  收藏  举报