MongoDB C# 驱动
http://www.mongodb.org/display/DOCS/CSharp+Driver+Tutorial#CSharpDriverTutorial-TheC%23Driver
读书笔记
C# Driver
之前看了Bson类库,现在学习C# Driver
只有少部分的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 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
这些不感兴趣,大概是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获取数据库以及进行一系列的数据库操作,并且保持数据库的一致性。
通过这个方法访问数据库
例子代码:
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文档
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 有很多等级,这个类就是用来描述这个等级的。WriteConcern 应用只用来操作那些没有返回的操作。比如Insert 、Save、Update和Remove。
这个主要就是当Insert、Save、Update和Remove指令发送给服务器之后,会有一个GetLassError命令来确认这些操作是否成功执行了。